【问题标题】:Pick a color from color picker Three.js从颜色选择器 Three.js 中选择颜色
【发布时间】:2018-05-27 06:31:51
【问题描述】:

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

【问题讨论】:

标签: javascript html three.js aframe


【解决方案1】:

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

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


颜色选择器包括:
1) 色轮
2) 将click 位置转换为特定颜色代码的代码

0) HSL

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


1 ) 色轮

我不会使用图像,而是使用shader 制作整个色轮。
如果您没有使用着色器的经验,您最好查看任何教程以了解基础知识,因为一开始可能看起来令人困惑和难以理解(我自己的印象)。着色器是用一种称为GlSl(openGL 着色语言)的语言编写的。它基于 C,我认为最好了解基础知识,因为它用于 Three 或 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,我们将针对不同的结果对其进行操作。然后我们有一个由Inigo Quilez 创建的将hsb 转换为rgb 的函数。

这里最重要的是converting 笛卡尔(x,y)坐标到极坐标(半径,角度)。我还从 wiki 获得了另一张有用的图片:

话虽如此,我们可以用 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>s 本地位置。这可能会适得其反,但我认为使用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(白色),当我点击边框lightness0.4。感觉很整洁,并消除了亮度值的另一个控制栏(作为 c 的快速解决方法)。


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>
如果您在 sn-p 中看不到任何内容,请尝试将相机向下移动。同样的代码也在这个fiddle中。

我希望它有帮助,玩得开心!

【讨论】:

  • 基于上面提到的a-paintercolorwheel components 代码,我希望它很容易理解和有帮助:)
猜你喜欢
  • 1970-01-01
  • 2016-11-28
  • 1970-01-01
  • 2017-12-04
  • 1970-01-01
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多