I'm working with a project base on A-FRAME. I'm doing a menu for hololens but I have some problems with a color picker. I create this: Color picker and I need to pick a color and have it in input. I have to change color of some objects that will be in the scene, so what I would like to do is, with the cursor, take a pixel from the texture (color picker, it's an image) and have it in input. It must be three.js or webgl or A-FRAME will not support it. This is my menu: Menu
-
1Do you have some code to post that you've written already? – alex May 21 '18 at 14:50
-
Yes, look here : https://glitch.com/edit/#!/wood-saw?path=index.html:53:29 – Roberta Galici May 21 '18 at 15:01
-
2Welcome to Stack Overflow. Please take a few minutes to look through the Help Center, specifically [`How do I ask a good question?`](https://stackoverflow.com/help/how-to-ask). Be sure to include and supporting code and errors with your question. Also, consider creating a [`Minimal, Complete, and Verifiable example`](https://stackoverflow.com/help/mcve) using [snippets](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/). – TheJim01 May 21 '18 at 16:31
-
1There’s this example for color picker in VR (demo at the top).. You may want to add tags for A-FRAME and VR.. https://github.com/mokargas/aframe-colorwheel-component – boateng May 21 '18 at 16:31
-
Also see A-Painter's codebase. https://github.com/aframevr/a-painter – ngokevin May 21 '18 at 22:17
1 Answers
I've tried to use Your colorwheel image, and get the color of a pixel like mrdoob suggested here, but with no success. I think the images (in general) contain approximations (because of compression), so i got weird results.
Thats why I went with another approach, as the suggested 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.
The color picker consists of:
1) the colorwheel
2) the code tranlating the
click
position to a certain color code
0) HSL
The 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. Also this image could be helpful:
*it's actually the hsv (value instead of lightness), but "same rules apply"
1 ) The ColorWheel
I won't be using images, instead i'll make the entire colorwheel with a shader.
If You have no experience with shaders, You'd better check any tutorial for the basics, as it may seem confusing and incomprehensible at first (my very own impressions). Shaders are written in a language called GlSl (openGL shading language). It's based on C, and i think its better to know the basics since it's used in Three, or Unity (UE4 uses HlSl though)
1.1 The Vertex Shader
Vertex shaders manipulate the models vertices, and set their positions:
var vertexShader = '\
varying vec2 vUv;\
void main() {\
vUv = uv;\
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\
gl_Position = projectionMatrix * mvPosition;\
}\
';
This is the easy part: we multiply each vertex by the model-view matrix and the projection matrix provided by Three.js to get a final position for the vertex.
1.2 The Fragment Shader
In short - they define the color of a fragment (the polygon between three vertices). This will be the heart of the colorwheel, since we'll use the fragment shader to draw it.
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);\
}\
';
There's a bit more going on here. First of all, there is a variable (called uniform) brightness
which we will manipulate, for different outcomes. Then we've got a function converting hsb to rgb created by Inigo Quilez.
The most important thing here is converting the cartesian (x,y) coordinates to polar ones(radius, angle). Also i've got another helpful image from wiki:
This being said, we can represent each pixel position (x,y) by a color from the HSL model, for we have an angle and a radius(distance) from the center.
1.3 Creating the wheel
we can use a-frame's <a-circle>
, but we need to create the material using the mentioned shaders:
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;
Check it out here. I threw the code in an a-frame component.
2. The picker's code
The idea is almost the same, as when creating the fragment shader. Get the clicked point (x, y), and find its distance, and angle regarding the middle of the circle. You can use any library to convert hsl to hex, i've used this anwser.
Lets start by creating the a-frame component:
AFRAME.registerComponent("foo", {
init: function() {
let box = document.querySelector(a-box) // the colored element
this.el.addEventListener("click", (e)=> {
the callback from the click provides us with lots of information. The exact click location can be found in e.detail.intersection.point
. But you would have to calculate how does the point world position translates to the <a-circle>
s local position. It may backfire, but i think its a good idea to use the el.detail.intersection.uv
, where you've got the position on the texture (ranging from 0 to 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)
I get the position, and convert it to an (angle, radius) pair as i did before. The only new thing: i also use the saturation
for the lightness
value (1 - s * 0.6
). When i click the center s
= 0, so the lightness
is 1 (white), when i click at the border lightness
is 0.4
. It feels neat, and eliminated another control bar for the lightness value (as a quick workaround ofc).
3.0 Putting it all together
<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>
I hope its helpful, and have fun !

- 14,150
- 3
- 21
- 42
-
Based on the mentioned `a-painter` and `colorwheel component`s code , i hope it's quite understandable and helpful :) – Piotr Adam Milewski May 26 '18 at 22:33