Wednesday, October 5, 2011

Retro 3D Pipeline

Nowadays we have OpenGL to handle all our 3D needs. But I like kickin it old school and taking things apart. When I was learning computer graphics in the 80s we were coding everything from scratch or from text books. 3D libraries weren't readily available and the WWW just wasn't born yet.

In this article I am going to blend javascript with the HTML 5 canvas and do some old school 3-D'in! I am not going to bore you with a lot of details I just want to provide for you a simple demonstration of 3D rotation in a text book purists sense. That mean's easy to follow code for you to cut and paste into your own coding experiments. Easy to follow means non-optimized or anything clever. Just straight up 3D!

While we're kickin it old school I am going to create a sin and cos lookup table. After all we're using a scripting language and not any flavor of a compiled language like, C, C++, C#, or Objective-C. This is so 1990's but has it's applications here, the trig lookup tables.

 
var sin_table = new Array();
var cos_table = new Array();

// function: initialize_trig_tables
//
// description: initialize the trig
// sin and cos lookup tables
//
// arguments: none
// returns: none
function initialize_trig_tables ()
{

for(angle=0; angle<361; angle++)
{
sin_table[angle] = Math.sin(angle);
cos_table[angle] = Math.cos(angle);
}
}


We're not done with our initializations yet. We will need functions to create a null matrix and an identity matrix.
 

// function: makeIdentityMatrix
//
// description: create a 4x4 identity matrix
//
// 1 0 0 0
// I = 0 1 0 0
// 0 0 1 0
// 0 0 0 1
//
// arguments: none
// returns: 4x4 identity matrix
function makeIdentityMatrix ()
{

return [
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1],
];
}

// function: makeNullMatrix
//
// description: create a 4x4 identity matrix
//
// 0 0 0 0
// N = 0 0 0 0
// 0 0 0 0
// 0 0 0 0
//
// arguments: none
// returns: 4x4 NULL matrix
function makeNullMatrix ()
{
return [
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
];

}


Now we need some code to perform the rotations Rx, Ry, and Rz. These are the respective rotations about the axis of x,y, and z. I store the rotation and projection code in a javascript library. Just a heads up for the driver code where it's implied you have this in a library somewhere.



For rotation Rx,

 
// function: createRotationXMatrix
//
// Description: creates a rotation matrix
// about the x-axis
//
// 1 0 0 0
// Rx(a) = 0 cos a -sin a 0
// 0 sin a cos a 0
// 0 0 0 1
//
// arguments: angle - angle of rotation
// returns: 4x4 Rotation matrix Rx
function createRotationXMatrix (angle)
{
// create and initalize rx to identity matrix
var rx = makeIdentityMatrix ();

rx[1][1] = cos_table[angle];
rx[1][2] = sin_table[angle];
rx[2][1] = -1 * sin_table[angle];
rx[2][2] = cos_table[angle];

return rx;
}


For rotation Ry,

 
// function: createRotationYMatrix
//
// Description: creates a rotation matrix
// about the y-axis
//
// cos t 0 sin t 0
// Ry(t) = 0 1 0 0
// -sin t 0 cos t 0
// 0 0 0 1
//
// arguments: angle - angle of rotation
// returns: 4x4 Rotation matrix Ry
function createRotationYMatrix (angle)
{
// create and initalize rx to identity matrix
var ry = makeIdentityMatrix ();

ry[0][0] = cos_table[angle];
ry[0][2] = -1 * sin_table[angle];
ry[2][0] = sin_table[angle];
ry[2][2] = cos_table[angle];

return ry;
}



For rotation about the Z axis, Rz,

 
// function: createRotationZMatrix
//
// Description: creates a rotation matrix
// about the z-axis
//
// cos p -sin p 0 0
// Rz(p) = sin p cos p 0 0
// 0 0 1 0
// 0 0 0 1
//
// arguments: angle - angle of rotation
// returns: 4x4 Rotation matrix Rz
function createRotationZMatrix (angle)
{
// create and initalize rz to identity matrix
var rz = makeIdentityMatrix ();

rz[0][0] = cos_table[angle];
rz[0][1] = -1 * sin_table[angle];
rz[1][0] = sin_table[angle];
rz[1][1] = cos_table[angle];

return rz;
}


Now we need a function to multiply matrices. Simple and straight forward -no optimization. This looks like,

 
// function: matrixMultiply
//
// Description: multiplies 2
// 4x4 matrices and returns the result
// arguments: A - matrix, B - matrix
// returns: 4x4 Matrix C as result of A * B
function matrixMultiply ( A, B ) {

// initialize an empty 4x4 matrix
var C = makeNullMatrix();

// Cij = E Aik Bkj
for (i=0; i<4;i++)
{
for (j=0; j<4; j++)
{
for (k=0; k<4; k++)
C[i][j] = C[i][j] + A[i][k]*B[k][j]
}
}
return C;
}


Using function matrixMultiply, we crunch some numbers brute force and create the Rotation desired,

 
// function: createRotationRMatrix
//
function createRotationRMatrix (anglex, angley, anglez)
{

var Rx = createRotationXMatrix (anglex);
var Ry = createRotationYMatrix (angley);
var Rz = createRotationXMatrix (anglez);

return ( matrixMultiply ( matrixMultiply ( Rx, Ry ), Rz ) );
}


The function returns a matrix with our desired results.

What is required now is means to project the rotation onto the display. We now need a perspective projection.

 
// Perspective Projection
//
// | 1 0 0 0 |
// | 0 1 0 0 |
// [x y z 1] | 0 0 0 1/d|
// | 0 0 0 1 |
//
function gc_projection ( x,y,z,d,P)
{
var xy_Array = new Array();

var gc_width = 800; // game console context width
var gc_height = 600; // game console context height

var xp = P[0][0]*x+P[1][0]*y+P[2][0]*z;
var yp = P[0][1]*x+P[1][1]*y+P[2][1]*z;
var zp = P[0][2]*x+P[1][2]*y+P[2][2]*z + 10;
var aspect_ratio = gc_width/gc_height;

xp = (gc_width/2) + (xp * d / zp);
yp = (gc_height/2) + (aspect_ratio * yp * d /zp);

xy_Array[0] = xp;
xy_Array[1] = yp;

return xy_Array;
}


Here's what a simple driver code looks like in javascript.

 


:
snip
:

var c=document.getElementById("myCanvas");
var cxt = c.getContext("2d");

var vertex = [
[0.57735026919, 0.57735026919, 0.57735026919],
[0.57735026919, -0.57735026919, -0.57735026919],
[-0.57735026919, -0.57735026919, 0.57735026919],
];

var P = createRotationRMatrix (30,30,30);
var vertex_list = new Array();

for (i=0; i<3; i++) { vertex_list[i] = gc_projection ( vertex[i][0],vertex[i][1],vertex[i][2],2000,P) } // render it cxt.moveTo(vertex_list[0][0],vertex_list[0][1]); cxt.lineTo(vertex_list[1][0],vertex_list[1][1]); cxt.lineTo(vertex_list[2][0],vertex_list[2][1]); cxt.lineTo(vertex_list[0][0],vertex_list[0][1]);


That's that!

No comments:

Post a Comment