Using Three.js, I have written a vertex shader that colors each point in a plane according to which quadrant of the screen a pixel is rendered.
// vertex shader
uniform float width;
uniform float height;
varying float x;
varying float y;
void main()
{
vec4 v = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
x = v.x;
y = v.y;
gl_Position = v;
}
// fragment shader
varying float x;
varying float y;
void main()
{
gl_FragColor = vec4(x, y, 0.0, 1.0);
}
Live example at jsFiddle: /
I would like to modify this shader so that x
is equal to 0 on the left side of the screen and 1 on the right side of the screen, and similarly y
equal to 0 on the top and 1 on the bottom. I (incorrectly) assumed that this would involve changing the values of x
and y
to x = v.x / width
and y = v.y / height
(where width
and height
store the window's width and height). However, with this change the colors at each screen pixel seem to change when zooming in and out on the plane.
How can I fix the shader code to have the desired effect?
Using Three.js, I have written a vertex shader that colors each point in a plane according to which quadrant of the screen a pixel is rendered.
// vertex shader
uniform float width;
uniform float height;
varying float x;
varying float y;
void main()
{
vec4 v = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
x = v.x;
y = v.y;
gl_Position = v;
}
// fragment shader
varying float x;
varying float y;
void main()
{
gl_FragColor = vec4(x, y, 0.0, 1.0);
}
Live example at jsFiddle: http://jsfiddle/Stemkoski/ST4aM/
I would like to modify this shader so that x
is equal to 0 on the left side of the screen and 1 on the right side of the screen, and similarly y
equal to 0 on the top and 1 on the bottom. I (incorrectly) assumed that this would involve changing the values of x
and y
to x = v.x / width
and y = v.y / height
(where width
and height
store the window's width and height). However, with this change the colors at each screen pixel seem to change when zooming in and out on the plane.
How can I fix the shader code to have the desired effect?
Share Improve this question asked Nov 4, 2013 at 20:30 StemkoskiStemkoski 9,0433 gold badges48 silver badges63 bronze badges 5- It's not really clear to me what you mean... Is this it? jsfiddle/ST4aM/2 – WestLangley Commented Nov 4, 2013 at 20:59
- I dont have a lot of clue either, but atm, your x already is zero on the left and 1 on the right quarter, isn't it? because of the colors black and red? and if you want to have y to be 0 at the top, just invert the y-ponent? btw. as for performance, bine the two floats into one vec2? :) btw: i left a "ment-question" on your last question here, would be cool if you could answer it :) – GuyGood Commented Nov 4, 2013 at 21:08
- Sorry, I don't seem to be explaining this well. @WestLangley: your changes fix the coloring on the plane, but I am looking for the pattern to be fixed with respect to the window/screen (not the plane). For example, the color of the pixel on the lower right corner of the screen should be r=1,g=0,b=0 no matter what point of the plane mesh lies beyond it. Basically same effect as the jsFiddle I posted but there should be a smooth gradient for the colors across the screen. – Stemkoski Commented Nov 5, 2013 at 2:02
- @GuyGood I would like to have the color values change smoothly/linearly across the screen and attain maximum/minimum red/green values only at the edges of the window. In the original jsFiddle code, the gradient is pressed near the central horizontal and vertical lines. I tried multiplying by a scalar to "stretch it out", but then the pattern changes relative to the screen when the mesh is repositioned. – Stemkoski Commented Nov 5, 2013 at 2:06
- @WestLangley here's a different way to think about it: imagine a (invisible) THREE.Sprite with useScreenCoordinates = true, filling up the screen, red=0% on the left border, red=100% on the right border, green=0% on the top border, green=100% on the bottom border, linearly interpolated in between. Then I use a Raycaster at each pixel on the screen, perpendicular to the near plane of the camera. If the ray hits the plane mesh, color that point of the mesh with the color of the screen pixel the ray originated at. Repeat for every render, recoloring the plane as it (or the camera) moves around. – Stemkoski Commented Nov 5, 2013 at 2:12
2 Answers
Reset to default 6Your variable v
is in clip space. You need to apply the perspective divide to map that to normalized device coordinate ( NDC ) space, which takes values in the range [ -1, 1 ]. Then, you need to map that value to the range [ 0, 1 ] like so:
vec4 v = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
x = v.x / v.z;
y = v.y / v.z;
gl_FragColor = vec4( 0.5 * x + 0.5, - 0.5 * y + 0.5, 0.0, 1.0 );
fiddle: http://jsfiddle/ST4aM/4/
three.js r.62
Just an addition to WestLangley's answer.
I encountered a small bug that my vertex position was being calculated slightly wrong. Seemed that it was in range of (-1.1, 1.1). So after experimenting it turned out that v.z
is not exactly correct, it is dependent on near field clip plane, which people usually leave as 1 in camera setup. So adding 2 to v.z
seems have fixed it. I don't understand why 2 and not 1.
x = v.x / (v.z + 2.0); // depth + 2 x near clip space plane dist (camera setting)
So if you are working with large objects like in WestLangley's example, you won't notice it, but if your objects are small and close to camera then the error will be noticeable.
I've modified above example to clip color if position is not within (0,1) for Y and fixed calculation for X. Plane size is 20, camera distance is 30.
jsFiddle
It's still not perfect. If object gets very close to the camera it still messes up the position, but at least this makes it work with medium sized objects.