我正在使用A-FRAME的项目。我正在为hololens做一个菜单,但是我遇到了颜色选择器的问题。我创建了这个:Color picker我需要选择一种颜色并将其输入。我必须改变场景中某些物体的颜色,所以我想做的是,用光标从纹理中取一个像素(颜色选择器,它是一个图像)并将它放入输入。它必须是three.js或webgl或A-FRAME不支持它。 这是我的菜单: Menu
答案 0 :(得分:0)
我尝试使用你的色轮图像,并获得像mrdoob建议的here像素的颜色,但没有成功。我认为图像(通常)包含近似值(因为压缩),所以我得到了奇怪的结果。
这就是为什么我采用另一种方法,因为建议的a-painter或colorwheel component是实现您想要的完美来源。我会试着把它们分解一下。阅读本文后,我希望您能够自定义this fiddle。
<小时/> 颜色选择器包括:click
位置转换为某个颜色代码
0)HSL
hue - saturation - light model允许将0:2PI(圆圈)和两个0 - 100%值之间的角度转换为颜色。由于角度 - 颜色的关系,这个模型是色轮的支柱。你可以玩得开心,熟悉它here。此图片也可能有所帮助:
*它实际上是hsv(值而不是亮度),但“同样的规则适用”
1)ColorWheel
我不会使用图像,而是用shader制作整个色轮 如果你没有使用着色器的经验,你最好检查任何基础知识的教程,因为它起初可能看起来令人困惑和难以理解(我自己的印象)。着色器使用称为GlSl(openGL着色语言)的语言编写。它基于C,我认为更好地了解基础知识,因为它用于三,或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
,用于不同的结果。然后我们有一个函数将hsb转换为Inigo Quilez创建的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>
的本地位置。它可能适得其反,但我认为最好使用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
。它感觉很整洁,并且消除了亮度值的另一个控制条(作为快速解决方法)。
<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>
我希望它有用,并且玩得开心!