Выберите цвет из палитры цветов Three.js - PullRequest
0 голосов
/ 21 мая 2018

Я работаю с базой проектов на A-FRAME.Я делаю меню для hololens, но у меня есть некоторые проблемы с палитрой цветов.Я создаю это: Выбор цвета , и мне нужно выбрать цвет и получить его на входе.Я должен изменить цвет некоторых объектов, которые будут присутствовать на сцене, поэтому я хотел бы с помощью курсора взять пиксель из текстуры (средство выбора цвета, это изображение) и получить его на входе.Это должен быть three.js или webgl, иначе A-FRAME не будет его поддерживать.Это мое меню: Меню

1 Ответ

0 голосов
/ 27 мая 2018

Я пытался использовать Ваше изображение с цветным колесом и получить цвет пикселя, подобного предложенному mrdoob здесь , но безуспешно.Я думаю, что изображения (в целом) содержат приближения (из-за сжатия), поэтому я получил странные результаты.

Вот почему я выбрал другой подход, так как предлагаемый a-painter или colorwheel компонент являются идеальными источниками для достижения того, что вы хотите.Я постараюсь сломать их немного.Прочитав это, я надеюсь, что вы сможете настроить эту скрипку .


Выбор цвета состоит из:
1) колеса цвета
2) кода, переводящего положение click в определенный цветовой код

0) HSL

Модель освещения оттенок - насыщенность - позволяет преобразовать угол между 0: 2PI (круг) и двумя значениями 0 - 100% в цвет.Из-за соотношения угла и цвета эта модель является основой цветового колеса.Вы можете повеселиться и познакомиться с ним здесь .Также это изображение может быть полезным: hsv
* на самом деле это hsv (значение вместо яркости), но «применяются те же правила»


1) ColorWheel

Я не буду использовать изображения, вместо этого я сделаю весь colorwheel с шейдером .
Если у вас нет опыта работы с шейдерами, выЛучше проверить любой учебник по основам, так как поначалу он может показаться запутанным и непонятным (мои собственные впечатления).Шейдеры написаны на языке GlSl (язык шейдинга openGL).Он основан на C, и я думаю, что лучше знать основы, так как он используется в Three или Unity (хотя UE4 использует HlSl)

1.1 Vertex Shader

Вершинные шейдеры управляют вершинами моделей и устанавливают их позиции:

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 в rgb, созданная Inigo Quilez .

Самым важным здесь является преобразование декартовых (x, y) координат в полярные единицы (радиус, угол).Также у меня есть еще одно полезное изображение из вики:
cart2polar

При этом мы можем представить каждую позицию пикселя (x, y) цветомиз модели HSL, поскольку у нас есть угол и радиус (расстояние) от центра.

1.3 Создавая колесо

мы можем использовать <a-circle> a-frame, но нам нужно создать материал, используя упомянутые шейдеры:

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;   

Проверьте это здесь .Я бросил код в компоненте a-frame.

2.Код сборщика
Идея практически та же, что и при создании фрагментного шейдера.Получите точку щелчка (x, y) и найдите ее расстояние и угол относительно середины круга.Вы можете использовать любую библиотеку для преобразования hsl в hex, я использовал это anwser .

Давайте начнем с создания компонента a-frame:

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

theобратный вызов от клика предоставляет нам много информации.Точное местоположение клика можно найти в 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 (белый), а когда я щелкаю границу, lightness равен 0.4.Это чувствует себя опрятным, и устранена другая панель управления для значения легкости (как быстрый обходной путь ofc).


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>
Если вы не видите ничего во фрагменте, попробуйте переместить камеру вниз.Также этот код находится в этой скрипке .

Я надеюсь, что это полезно, и получайте удовольствие!

...