MENU
Projection Matrix
There are two ways by which 3D objects in the visible viewing volume (the frustum) are projected onto the 2D screen.
By orthographic projection (the default), the light rays travel in a parallel fashion, covering a viewing area as large as the screen.
By perspective projection, the light rays converge and meet at the eye point.
Orthographic projection is used for many technical drawings, whereas perspective projection simulates the human vision and is thus used for characters in games.
To use orthographic projection, six parameters need to be supplied to 'orthographic()': 'left', 'right', 'bottom', 'top', 'near', and 'far'.
There are two ways to use perspective projection. The first form, called 'frustum()' here, takes in the same six parameters as the orthographic projection. The second form, called 'perspective()' here, takes in four parameters: 'fovy', 'aspect', 'near', and 'far'.
(Use the full-screen mode for a better testing experience.)
RESETRUNFULL
// /shared/webgl-library.js
Matrix4.prototype.orthographic = function(left, right, bottom, top, near, far) {
var e, rw, rh, rd;
if (left === right || bottom === top || near === far) throw 'null frustum';
rw = 1 / (right - left);
rh = 1 / (top - bottom);
rd = 1 / (far - near);
e = this.entries;
e[0] = 2 * rw;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = 2 * rh;
e[6] = 0;
e[7] = 0;
e[8] = 0;
e[9] = 0;
e[10] = -2 * rd;
e[11] = 0;
e[12] = -(right + left) * rw;
e[13] = -(top + bottom) * rh;
e[14] = -(far + near) * rd;
e[15] = 1;
return this;
};
Matrix4.prototype.frustum = function(left, right, bottom, top, near, far) {
var e, rw, rh, rd;
if (left === right || top === bottom || near === far) throw 'null frustum';
if (near <= 0) throw 'near <= 0';
if (far <= 0) throw 'far <= 0';
rw = 1 / (right - left);
rh = 1 / (top - bottom);
rd = 1 / (far - near);
e = this.entries;
e[ 0] = 2 * near * rw;
e[ 1] = 0;
e[ 2] = 0;
e[ 3] = 0;
e[ 4] = 0;
e[ 5] = 2 * near * rh;
e[ 6] = 0;
e[ 7] = 0;
e[ 8] = (right + left) * rw;
e[ 9] = (top + bottom) * rh;
e[10] = -(far + near) * rd;
e[11] = -1;
e[12] = 0;
e[13] = 0;
e[14] = -2 * near * far * rd;
e[15] = 0;
return this;
};
Matrix4.prototype.perspective = function(fovy, aspect, near, far){
var e, rd, s, ct;
if (near === far || aspect === 0) throw 'null frustum';
if (near <= 0) throw 'near <= 0';
if (far <= 0) throw 'far <= 0';
fovy = Math.PI * fovy / 180 / 2;
s = Math.sin(fovy);
if (s === 0) throw 'null frustum';
rd = 1 / (far - near);
ct = Math.cos(fovy) / s;
e = this.entries;
e[0] = ct / aspect;
e[1] = 0;
e[2] = 0;
e[3] = 0;
e[4] = 0;
e[5] = ct;
e[6] = 0;
e[7] = 0;
e[8] = 0;
e[9] = 0;
e[10] = -(far + near) * rd;
e[11] = -1;
e[12] = 0;
e[13] = 0;
e[14] = -2 * near * far * rd;
e[15] = 0;
return this;
};
<!DOCTYPE html><html>
<head>
<script src="/shared/webgl-library.js"></script>
<script>
var VS_SOURCE=`
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
attribute vec4 p;
attribute vec4 c;
varying vec4 c2;
void main(){
c2=c;
gl_Position=projectionMatrix*viewMatrix*modelMatrix*p;
}`;
var FS_SOURCE=`
precision mediump float;
varying vec4 c2;
void main(){
gl_FragColor=c2;
}`;
var eyeX=0,eyeY=0,eyeZ=0.5,near=0.25,far=10,ortho=true;var gl,mm,mmL,vm,vmL,pm,pmL;
function keydown(ev){
switch (ev.keyCode){
case 37: eyeX-=0.01; // left arrow key
break; case 38: eyeY+=0.01; // up arrow key
break; case 39: eyeX+=0.01; // right arrow key
break; case 40: eyeY-=0.01; // down arrow key
break; case 49: ortho=true; // '1' key
break; case 50: ortho=false; // '2' key
break; case 81: near+=0.01; // 'Q' key
break; case 65: near-=0.01; // 'A' key
}
vm.lookAt(eyeX,eyeY,eyeZ, 0,0,0, 0,1,0);
gl.uniformMatrix4fv(vmL,false,vm.entries);
if (ortho) pm.orthographic(-0.25,0.25,-0.25,0.25,near,far);
else pm.frustum(-0.25,0.25,-0.25,0.25,near,far);
gl.uniformMatrix4fv(pmL,false,new Float32Array(pm.entries));
}
function draw_colored_triangle(){
var points=new Array();
var vc= new Float32Array([ 0.0,-0.5, 1.0, 0.0, 0.0,
-0.5, 0.5, 0.0, 1.0, 0.0,
0.5, 0.5, 0.0, 0.0, 1.0]);
var ESIZE=vc.BYTES_PER_ELEMENT;
var b=gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER,b);
gl.bufferData(gl.ARRAY_BUFFER,vc,gl.STATIC_DRAW);
pL=gl.getAttribLocation(gl.program,'p');
gl.vertexAttribPointer(pL,2,gl.FLOAT,false,ESIZE*5,0);
gl.enableVertexAttribArray(pL);
cL=gl.getAttribLocation(gl.program,'c');
gl.vertexAttribPointer(cL,3,gl.FLOAT,false,ESIZE*5,ESIZE*2);
gl.enableVertexAttribArray(cL);
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES,0,3);
}
function start(){
var canvas = document.getElementById("myCanvas");
gl = canvas.getContext("webgl");
initShaders(gl,VS_SOURCE,FS_SOURCE);
document.onkeydown = function(ev){keydown(ev);};
mm = new Matrix4();
mm.scale(0.5,0.5,0.5);
mmL = gl.getUniformLocation(gl.program, 'modelMatrix');
gl.uniformMatrix4fv(mmL,false,new Float32Array(mm.entries));
vm = new Matrix4();
vm.lookAt(eyeX,eyeY,eyeZ, 0,0,0, 0,1,0);
vmL = gl.getUniformLocation(gl.program, 'viewMatrix');
gl.uniformMatrix4fv(vmL,false,new Float32Array(vm.entries));
pm = new Matrix4();
pm.orthographic(-0.25,0.25,-0.25,0.25,near,far);
pmL = gl.getUniformLocation(gl.program, 'projectionMatrix');
gl.uniformMatrix4fv(pmL,false,new Float32Array(pm.entries));
var tick = function(){
mm.rotate(1,0,0,1); // rotates one degree every frame
gl.uniformMatrix4fv(mmL,false, new Float32Array(mm.entries));
draw_colored_triangle();
requestAnimationFrame(tick);
};
tick();
}
</script>
</head>
<body onload="start()">
<canvas id="myCanvas" width="500" height="500"></canvas>
</body>
</html>