Note: I understand the basic math. I understand that the typical perspective
function in various math libraries produces a matrix that converts z values from -zNear to -zFar back into -1 to +1 but only if the result is divided by w
The specific question is what is gained by the GPU doing this for you rather than you having to do it yourself?
In other words, lets say the GPU did not magically divide gl_Position
by gl_Position.w
and that instead you had to do it manually as in
attribute vec4 position;
uniform mat4 worldViewProjection;
void main() {
gl_Position = worldViewProjection * position;
// imaginary version of GL where we must divide by W ourselves
gl_Position /= gl_Position.w;
}
What breaks in this imaginary GL because of this? Would it work or is there something about passing in the value before it's been divided by w
that provides extra needed info to the GPU?
Note that if I actually do it the texture mapping perspective breaks.
"use strict";
var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2);
var tex = twgl.createTexture(gl, {
min: gl.NEAREST,
mag: gl.NEAREST,
src: [
255, 255, 255, 255,
192, 192, 192, 255,
192, 192, 192, 255,
255, 255, 255, 255,
],
});
var uniforms = {
u_diffuse: tex,
};
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
var projection = m4.perspective(
30 * Math.PI / 180,
gl.canvas.clientWidth / gl.canvas.clientHeight,
0.5, 10);
var eye = [1, 4, -6];
var target = [0, 0, 0];
var up = [0, 1, 0];
var camera = m4.lookAt(eye, target, up);
var view = m4.inverse(camera);
var viewProjection = m4.multiply(projection, view);
var world = m4.rotationY(time);
uniforms.u_worldInverseTranspose = m4.transpose(m4.inverse(world));
uniforms.u_worldViewProjection = m4.multiply(viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
<script id="vs" type="notjs">
uniform mat4 u_worldViewProjection;
uniform mat4 u_worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texcoord;
varying vec2 v_texcoord;
varying vec3 v_normal;
void main() {
v_texcoord = texcoord;
v_normal = (u_worldInverseTranspose * vec4(normal, 0)).xyz;
gl_Position = u_worldViewProjection * position;
gl_Position /= gl_Position.w;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
varying vec2 v_texcoord;
varying vec3 v_normal;
uniform sampler2D u_diffuse;
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texcoord);
vec3 a_normal = normalize(v_normal);
float l = dot(a_normal, vec3(1, 0, 0));
gl_FragColor.rgb = diffuseColor.rgb * (l * 0.5 + 0.5);
gl_FragColor.a = diffuseColor.a;
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas id="c"></canvas>
But, is that because the GPU actually needs z and w to be different or is it just GPU design and a different design could derive the info it needed if we did the w
divide ourselves?
Update:
After asking this question I ended up writing this article that illustrates the perspective interpolation.