Я пытался использовать Ваше изображение с цветным колесом и получить цвет пикселя, подобного предложенному mrdoob здесь , но безуспешно.Я думаю, что изображения (в целом) содержат приближения (из-за сжатия), поэтому я получил странные результаты.
Вот почему я выбрал другой подход, так как предлагаемый a-painter или colorwheel компонент являются идеальными источниками для достижения того, что вы хотите.Я постараюсь сломать их немного.Прочитав это, я надеюсь, что вы сможете настроить эту скрипку .
Выбор цвета состоит из:
1) колеса цвета
2) кода, переводящего положение
click
в определенный цветовой код
0) HSL
Модель освещения оттенок - насыщенность - позволяет преобразовать угол между 0: 2PI (круг) и двумя значениями 0 - 100% в цвет.Из-за соотношения угла и цвета эта модель является основой цветового колеса.Вы можете повеселиться и познакомиться с ним здесь .Также это изображение может быть полезным:
* на самом деле это 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) координат в полярные единицы (радиус, угол).Также у меня есть еще одно полезное изображение из вики:
При этом мы можем представить каждую позицию пикселя (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>
Если вы не видите ничего во фрагменте, попробуйте переместить камеру вниз.Также этот код находится в этой
скрипке .
Я надеюсь, что это полезно, и получайте удовольствие!