从颜色选择器Three.js中选择一种颜色

时间:2018-05-21 14:49:14

标签: javascript html three.js aframe

我正在使用A-FRAME的项目。我正在为hololens做一个菜单,但是我遇到了颜色选择器的问题。我创建了这个:Color picker我需要选择一种颜色并将其输入。我必须改变场景中某些物体的颜色,所以我想做的是,用光标从纹理中取一个像素(颜色选择器,它是一个图像)并将它放入输入。它必须是three.js或webgl或A-FRAME不支持它。 这是我的菜单: Menu

1 个答案:

答案 0 :(得分:0)

我尝试使用你的色轮图像,并获得像mrdoob建议的here像素的颜色,但没有成功。我认为图像(通常)包含近似值(因为压缩),所以我得到了奇怪的结果。

这就是为什么我采用另一种方法,因为建议的a-paintercolorwheel component是实现您想要的完美来源。我会试着把它们分解一下。阅读本文后,我希望您能够自定义this fiddle

<小时/> 颜色选择器包括:
1)色轮
2)代码将click位置转换为某个颜色代码

0)HSL

hue - saturation - light model允许将0:2PI(圆圈)和两个0 - 100%值之间的角度转换为颜色。由于角度 - 颜色的关系,这个模型是色轮的支柱。你可以玩得开心,熟悉它here。此图片也可能有所帮助: hsv
*它实际上是hsv(值而不是亮度),但“同样的规则适用”

1)ColorWheel

我不会使用图像,而是用shader制作整个色轮 如果你没有使用着色器的经验,你最好检查任何基础知识的教程,因为它起初可能看起来令人困惑和难以理解(我自己的印象)。着色器使用称为GlSl(openGL着色语言)的语言编写。它基于C,我认为更好地了解基础知识,因为它用于三,或Unity(UE4使用HlSl)

1.1顶点着色器

顶点着色器操纵模型顶点,并设置它们的位置:

var vertexShader = '\
    varying vec2 vUv;\
    void main() {\
      vUv = uv;\
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\
      gl_Position = projectionMatrix * mvPosition;\
    }\
';

这是一个简单的部分:我们将每个顶点乘以模型视图矩阵和 由Three.js提供的投影矩阵,以获得顶点的最终位置。

1.2片段着色器

简而言之 - 它们定义了一个片段的颜色(三个顶点之间的多边形)。这将是色轮的核心,因为我们将使用片段着色器来绘制它。

var fragmentShader = '\
  #define M_PI2 6.28318530718\n \
  uniform float brightness;\
  varying vec2 vUv;\
  vec3 hsb2rgb(in vec3 c){\
      vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, \
                       0.0, \
                       1.0 );\
      rgb = rgb * rgb * (3.0 - 2.0 * rgb);\
      return c.z * mix( vec3(1.0), rgb, c.y);\
  }\
  \
  void main() {\
    vec2 toCenter = vec2(0.5) - vUv;\
    float angle = atan(toCenter.y, toCenter.x);\
    float radius = length(toCenter) * 2.0;\
    vec3 color = hsb2rgb(vec3((angle / M_PI2) + 0.5, radius, brightness));\
    gl_FragColor = vec4(color, 1.0);\
  }\
  ';

这里还有更多。首先,我们将操纵一个变量(称为统一)brightness,用于不同的结果。然后我们有一个函数将hsb转换为Inigo Quilez创建的rgb。

这里最重要的是converting 笛卡尔(x,y)坐标到(半径,角度)。另外我还有来自wiki的另一张有用图片:
cart2polar

这就是说,我们可以用HSL模型中的颜色表示每个像素位置(x,y),因为我们有一个角度和距中心的半径(距离)。

1.3创建方向盘

我们可以使用a-frame的<a-circle>,但我们需要使用提到的着色器创建材质:

var material = new THREE.ShaderMaterial({
  uniforms: {
    brightness: {
      type: 'f',
      value: 0.9 // you can manipulate the uniform brightness value !
    }
  },
  vertexShader: vertexShader,
  fragmentShader: fragmentShader
});
this.mesh = this.el.getObject3D('mesh');
this.mesh.material = material;   

查看here。我把代码放在一个框架组件中。

<强> 2。选择器的代码
与创建片段着色器时的想法几乎相同。获取单击的点(x,y),并找到它的距离,以及关于圆的中间的角度。您可以使用任何库将hsl转换为十六进制,我已使用此anwser

让我们从创建a-frame组件开始:

AFRAME.registerComponent("foo", {
  init: function() {
    let box = document.querySelector(a-box) // the colored element
    this.el.addEventListener("click", (e)=> {

来自点击的回调为我们提供了大量信息。确切的点击位置可以在e.detail.intersection.point中找到。但你必须计算点世界位置如何转换为<a-circle>的本地位置。它可能适得其反,但我认为最好使用el.detail.intersection.uv,你在纹理上有位置(从0到1):

let point = e.detail.intersection.uv // get the event data
point.x = point.x * 2 - 1 // so its range is -1 to 1
point.y = point.y * 2 - 1 // same here
var theta = Math.PI + Math.atan2(point.y, point.x) // cart -> polar: angle
var h, s, l
h = (theta / (2 * Math.PI) + 0.5) % 1; // the shader also has the hue shifted by 0.5 radians
s = Math.sqrt(point.x * point.x + point.y * point.y); //cart -> polar: radius
l = 0.6 // whatever value for the lightness
var color = this.hslToHex(h, s, 1 - s * 0.6)
box.setAttribute("material", "color", color)

我得到了位置,并将其转换为(角度,半径)对,就像我之前做的那样。唯一的新事物:我还使用saturation作为lightness值(1 - s * 0.6)。当我点击中心s = 0时,lightness为1(白色),当我点击边框lightness时为0.4。它感觉很整洁,并且消除了亮度值的另一个控制条(作为快速解决方法)。

<小时/> 3.0全部放在一起

<script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
<script>
  AFRAME.registerComponent("foo", {
    init: function() {
      var box = document.querySelector("a-box")
      var vertexShader = '\
      varying vec2 vUv;\
      void main() {\
        vUv = uv;\
        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\
        gl_Position = projectionMatrix * mvPosition;\
      }\
      ';

      var fragmentShader = '\
      #define M_PI2 6.28318530718\n \
      uniform float brightness;\
      varying vec2 vUv;\
      vec3 hsb2rgb(in vec3 c){\
          vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, \
                           0.0, \
                           1.0 );\
          rgb = rgb * rgb * (3.0 - 2.0 * rgb);\
          return c.z * mix( vec3(1.0), rgb, c.y);\
      }\
      \
      void main() {\
        vec2 toCenter = vec2(0.5) - vUv;\
        float angle = atan(toCenter.y, toCenter.x);\
        float radius = length(toCenter) * 2.0;\
        vec3 color = hsb2rgb(vec3((angle / M_PI2) + 0.5, radius, brightness));\
        gl_FragColor = vec4(color, 1.0);\
      }\
      ';

      var material = new THREE.ShaderMaterial({
        uniforms: {
          brightness: {
            type: 'f',
            value: 0.9
          }
        },
        vertexShader: vertexShader,
        fragmentShader: fragmentShader
      });
      console.log(this.el.object3D)
      this.mesh = this.el.getObject3D('mesh');

      this.mesh.material = material;

      this.el.addEventListener("click", (e) => {
        let point = e.detail.intersection.uv
        point.x = point.x * 2 - 1
        point.y = point.y * 2 - 1
        var theta = Math.PI + Math.atan2(point.y, point.x)
        var h, s, l
        h = (theta / (2 * Math.PI) + 0.5) % 1;
        s = Math.sqrt(point.x * point.x + point.y * point.y);
        l = 0.6
        var color = this.hslToHex(h, s, 1 - s * 0.6)
        box.setAttribute("material", "color", color)
      })
    },
    hslToHex: function(h, s, l) {
      let r, g, b;
      if (s === 0) {
        r = g = b = l; // achromatic
      } else {
        const hue2rgb = (p, q, t) => {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1 / 6) return p + (q - p) * 6 * t;
          if (t < 1 / 2) return q;
          if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
          return p;
        };
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
        r = hue2rgb(p, q, h + 1 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1 / 3);
      }
      const toHex = x => {
        const hex = Math.round(x * 255).toString(16);
        return hex.length === 1 ? '0' + hex : hex;
      };
      return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
    }
  })

</script>

<body>
  <a-scene cursor="rayOrigin: mouse">
    <a-circle position="-1 1.5 -3" rotation="0 0 0" material foo></a-circle>

    <a-box position="1 1.5 -3"></a-box>
  </a-scene>
如果您在代码段中没有看到任何内容,请尝试向下移动相机。同样的代码也在fiddle

我希望它有用,并且玩得开心!