我尝试使用您的色轮图像,并获得像 mrdoob 建议的 here 这样的像素颜色,但没有成功。我认为图像(通常)包含近似值(因为压缩),所以我得到了奇怪的结果。
这就是我采用另一种方法的原因,因为建议的 a-painter 或 colorwheel 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(白色),当我点击边框lightness 是0.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中。
我希望它有帮助,玩得开心!