WebGL clipspace point visualization

Above you should see a black square with a little crosshair. The crosshair marks a point in space. This is drawn with WebGL. You can change the position of the point using the sliders to the right, representing four components of the point called X, Y, Z, and W.

By playing around with these sliders, you can discover many things about WebGL!

• The X component moves the point left/right, and the Y component moves the point up/down. So far, so expected.
• Increasing Y moves the point up, not down! WebGL’s Y is opposite to CSS’s Y.
• The mysterious W component acts as an inverse scale. As W increases, the point shrinks towards the origin. As W decreases towards zero, the point moves out to infinity. When W is negative, the point doesn’t show (what could negative mean here?).
• The Z component doesn’t make any difference. Z represents the axis perpendicular to the screen. But this distance does not affect the point’s “size” or distance to the origin, as might happen in a perspective view. (The W component is more like a perspective transformation than the Z component!)
• When Z goes out of the [-1, 1] range, the point disappears entirely! It is “clipped” because it’s no longer in “clip space”, the two-unit cube. This clipping happens after W-scaling: a point with Z=2 and W=2 still displays, because the final Z = Z/W = 1.

I implemented the above with a passthrough vertex shader, which passes on the vertex attributes as the vertex position:

attribute vec4 coord;
void main(void) {
gl_Position = coord;
}

Then, whenever the sliders change, I pass in the six vertices of a 3D crosshair:

function redraw() {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
pos.x-CROSSHAIR_SIZE,  pos.y,                 pos.z,                pos.w,
pos.x+CROSSHAIR_SIZE,  pos.y,                 pos.z,                pos.w,
pos.x,                 pos.y-CROSSHAIR_SIZE,  pos.z,                pos.w,
pos.x,                 pos.y+CROSSHAIR_SIZE,  pos.z,                pos.w,
pos.x,                 pos.y,                 pos.z-CROSSHAIR_SIZE, pos.w,
pos.x,                 pos.y,                 pos.z+CROSSHAIR_SIZE, pos.w,
]), gl.STATIC_DRAW);

gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.LINES, 0, 6);
}