从颜色选择器中选择一种颜色 Three.js
Pick a color from color picker Three.js
我正在处理一个基于 A-FRAME 的项目。我正在为 hololens 做一个菜单,但我在颜色选择器上遇到了一些问题。我创建了这个:Color picker 并且我需要选择一种颜色并将其输入。我必须更改场景中某些对象的颜色,所以我想做的是,使用光标,从纹理(颜色选择器,它是一个图像)中取出一个像素并将其输入。它必须是 three.js 否则 webgl 或 A-FRAME 将不支持它。
这是我的菜单:
Menu
我试过使用您的色轮图像,并像 mrdoob 建议的那样获取像素的颜色 here,但没有成功。我认为图像(通常)包含近似值(由于压缩),所以我得到了奇怪的结果。
这就是为什么我按照建议 a-painter, or colorwheel component are perfect sources to achieve what you want. I'll try break them down a bit. After reading this i hope you'll be able to customize this fiddle 采用另一种方法的原因。
颜色选择器包括:
1) 色轮
2) 将 click
位置转换为特定颜色代码 的代码
0) HSL
hue - saturation - light model allows to convert an angle between 0 : 2PI (circle), and two 0 - 100% values to a color. Because of the angle - color relationship, this model is the backbone of the colorwheel. You can have fun, and get familiar with it here。此图片也可能有帮助:
*它实际上是 hsv(值而不是亮度),但是 "same rules apply"
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;\
}\
';
这是简单的部分:我们将每个顶点乘以 model-view 矩阵和
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);\
}\
';
这里还有更多内容。首先,有一个变量(称为 uniform)brightness
,我们将针对不同的结果对其进行操作。然后我们有一个由 Inigo Quilez 创建的将 hsb 转换为 rgb 的函数。
这里最重要的是 converting 笛卡尔 (x,y) 坐标到 极坐标 (半径,角度)。我还从 wiki 获得了另一张有用的图片:
也就是说,我们可以用 HSL 模型中的颜色表示每个像素位置 (x,y),因为我们有一个角度和一个距中心的半径(距离)。
1.3 造轮子
我们可以使用 a-frame 的 <a-circle>
,但我们需要使用提到的着色器创建 material:
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。我把代码放在 a-frame 组件中。
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
。感觉很整洁,并且消除了亮度值的另一个控制栏(作为一种快速解决方法)。
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.
中也有相同的代码
希望对您有所帮助,祝您玩得开心!
我正在处理一个基于 A-FRAME 的项目。我正在为 hololens 做一个菜单,但我在颜色选择器上遇到了一些问题。我创建了这个:Color picker 并且我需要选择一种颜色并将其输入。我必须更改场景中某些对象的颜色,所以我想做的是,使用光标,从纹理(颜色选择器,它是一个图像)中取出一个像素并将其输入。它必须是 three.js 否则 webgl 或 A-FRAME 将不支持它。 这是我的菜单: Menu
我试过使用您的色轮图像,并像 mrdoob 建议的那样获取像素的颜色 here,但没有成功。我认为图像(通常)包含近似值(由于压缩),所以我得到了奇怪的结果。
这就是为什么我按照建议 a-painter, or colorwheel component are perfect sources to achieve what you want. I'll try break them down a bit. After reading this i hope you'll be able to customize this fiddle 采用另一种方法的原因。
颜色选择器包括:
1) 色轮
2) 将
click
位置转换为特定颜色代码 的代码
0) HSL
hue - saturation - light model allows to convert an angle between 0 : 2PI (circle), and two 0 - 100% values to a color. Because of the angle - color relationship, this model is the backbone of the colorwheel. You can have fun, and get familiar with it here。此图片也可能有帮助:
*它实际上是 hsv(值而不是亮度),但是 "same rules apply"
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;\
}\
';
这是简单的部分:我们将每个顶点乘以 model-view 矩阵和 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);\
}\
';
这里还有更多内容。首先,有一个变量(称为 uniform)brightness
,我们将针对不同的结果对其进行操作。然后我们有一个由 Inigo Quilez 创建的将 hsb 转换为 rgb 的函数。
这里最重要的是 converting 笛卡尔 (x,y) 坐标到 极坐标 (半径,角度)。我还从 wiki 获得了另一张有用的图片:
也就是说,我们可以用 HSL 模型中的颜色表示每个像素位置 (x,y),因为我们有一个角度和一个距中心的半径(距离)。
1.3 造轮子
我们可以使用 a-frame 的 <a-circle>
,但我们需要使用提到的着色器创建 material:
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。我把代码放在 a-frame 组件中。
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
。感觉很整洁,并且消除了亮度值的另一个控制栏(作为一种快速解决方法)。
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>
中也有相同的代码
希望对您有所帮助,祝您玩得开心!