Summary:

Tutorial 4 - 3D

This tutorial will cover the final step in using WebRender to create a 3D object. Some experience with 3D rendering, such as with OpenGL or DirectX is required if you want to understand everything discussed in this tutorial. Like before, we are building on the previous tutorial, so sections that have not been changed will not be mentioned again.

Tutorial Files (14.5 KB)

Result

The end result of this tutorial should be this:

This the HTML for this example:


<html>
	<head>	
		<title>WebRender Tutorial 4</title>
		<script src="matrix.js"></script>
		<script src="webrender.js"></script>
		<script src="example.js"></script>
	</head>
	<body onLoad="main()">	
		<canvas id="screen" width="600" height="600"></canvas>
	</body>
</html>

This the JavaScript for this example:


var canvas;
var render;
var vbo;
var verticesLength;
var tbo;

var timeCounter = 0;

function main() {
	canvas = document.getElementById("screen");
	
	render = new WebRender(canvas, callback, 0, "WebRender.js");
	render.initialise(canvas.width, canvas.height);
	render.clearColor(0, 0, 0, 255);

	var shaderID = render.getSBOId();
	render.assignSBOVertexShader(shaderID, "index.js", "vertexShader");
	render.assignSBOPixelShader(shaderID, "index.js", "fragmentShader");
	render.addSBOVertexAttribute(shaderID, "VERTEX", "POSITION", 3);
	render.addSBOVertexAttribute(shaderID, "VERTEX", "TEXCOORD", 2);
	render.addSBOVertexAttribute(shaderID, "PIXEL", "POSITION", 4);
	render.addSBOVertexAttribute(shaderID, "PIXEL", "TEXCOORD", 2);
	render.loadSBO(shaderID);
	render.bindSBO(shaderID);

	vbo = render.getVBOId();
	loadModel(vbo);

	tbo = render.getTBOId();
	loadTexture("texture.png", tbo);
	
	var persMatrix = createPrespectiveMatrix(85, canvas.width/canvas.height, 0.1, 100);
	render.setShaderVariable("VERTEX_perspectiveMatrix", persMatrix);
	draw();
	
	return;
}

//----------------------------------------------------------------
// Draw Loop
//----------------------------------------------------------------
function draw() {
	timeCounter += 0.01;

	var objectMatrix = new Matrix();
	objectMatrix.makeTranslationMatrix(0, 0, -4);
	objectMatrix.makeXRotationMatrix(0.5);
	objectMatrix.makeYRotationMatrix(timeCounter);
	render.setShaderVariable("VERTEX_objectMatrix", objectMatrix);
	
	render.bindTBO(tbo, 0);
	
	render.clearBuffer(0);
	render.draw(vbo, 0, verticesLength);
	render.getBuffer();
}

function callback() {
	setTimeout(draw, 0);
}

//----------------------------------------------------------------
// Shaders
//----------------------------------------------------------------
var VERTEX_objectMatrix;
var VERTEX_perspectiveMatrix;

function vertexShader(render, inputVertex, outputVertex) {
	outputVertex[0] = inputVertex[0];
	outputVertex[1] = inputVertex[1];
	outputVertex[2] = inputVertex[2];
	outputVertex[3] = 1;
	outputVertex[4] = inputVertex[3];
	outputVertex[5] = inputVertex[4];
	
	render.vecByMatrixCol(outputVertex, VERTEX_objectMatrix.data, 0);
	render.vecByMatrixCol(outputVertex, VERTEX_perspectiveMatrix.data, 0);
}

function fragmentShader(render, x, y, z, w, u, v) {
	return render.getTexturePixelColor(u, v, render.TEXTURE_0);
}

//----------------------------------------------------------------
// Texture Loading Code
//----------------------------------------------------------------
function loadTexture(texturePath, tbo) {	
	var texture = new Image();
	texture.id = tbo + ':Texture' + texturePath;
	texture.src = texturePath;
	texture.onload = this.imageLoaded;
	return;
}

function imageLoaded(e) {
	
	//Get the ID
	var source = e.target;
	if (source == undefined) {	//for IE
		source = e.srcElement;
	}
	var id = (source.id).split(":")[0];
	
	//drawing the texture onto our canvas.
	var texture = source;
	
	//resize canvas to size of texture
	var originalWidth = canvas.width;
	var originalHeight = canvas.height;
	canvas.width = texture.width;
	canvas.height = texture.height;
	
	//draw the texture and then grab the pixel array
	var loaderContext = canvas.getContext("2d");
	loaderContext.clearRect(0, 0, texture.width, texture.height);
	loaderContext.drawImage(texture, 0, 0);
	var textureData = loaderContext.getImageData(0, 0, texture.width, texture.height);
	
	canvas.width = originalWidth;
	canvas.height = originalHeight;
	
	//load the texture into WebRender
	render.loadTBO(id, textureData.data, textureData.width, textureData.height);
	
	return;
}

//----------------------------------------------------------------
// Model Creating Code
//----------------------------------------------------------------
function loadModel() {
	//Place any model loading code here.
	
	//This example uses a cube which data is in the following array.
	var vertices=[-1,-1,1,0,1,1,-1,1,1,1,1,1,1,1,0,-1,-1,1,0,1,1,1,1,1,0,-1,1,1,
	0,0,-1,-1,-1,1,1,1,1,-1,0,0,1,-1,-1,0,1,-1,-1,-1,1,1,-1,1,-1,1,0,1,1,-1,0,0,
	-1,1,-1,0,0,-1,1,1,0,1,1,1,1,1,1,-1,1,-1,0,0,1,1,1,1,1,1,1,-1,1,0,-1,-1,-1,
	0,1,1,-1,1,1,0,-1,-1,1,0,0,-1,-1,-1,0,1,1,-1,-1,1,1,1,-1,1,1,0,1,-1,-1,1,1,
	1,1,-1,1,0,1,1,1,0,0,1,-1,-1,1,1,1,1,1,0,0,1,-1,1,0,1,-1,-1,-1,0,1,-1,1,1,1,
	0,-1,1,-1,0,0,-1,-1,-1,0,1,-1,-1,1,1,1,-1,1,1,1,0];
	verticesLength = 36;
	render.loadVBO(vbo, vertices);
}

//----------------------------------------------------------------
// Perspective Matrix
//----------------------------------------------------------------
function createPrespectiveMatrix(fieldOfView, aspectRatio, near, far) {	
	var top = near*Math.tan(fieldOfView * 3.14159265/360);
	var bottom = -top;	
	var right = top*aspectRatio;
	var left = -right;
	
	var result = new Matrix();
	result.set(0, 0, (2*near) / (right-left));
	result.set(1, 1, (2*near) / (top-bottom));
	result.set(2, 2, (near+far) / (far-near));
	result.set(3, 2, -1);
	result.set(2, 3, (2*near*far)/(far-near));
	result.set(3, 3, 0);
	
	return result;
}

Explanation - HTML

There is no change in the HTML for this tutorial.

Explanation - JavaScript

Due to the amount of similarities between this code and the previous tutorial the follow functions will not be discussed as they have not been modified: callback(), vertexShader(), fragmentShader() and loadTexture(). This is because the previous tutorial was already working in 3D, just with a 2D triangle.

The two new functions added in this are loadModel() and createPrespectiveMatrix(), which will be explained below.


var canvas;
var render;
var vbo;
var verticesLength;
var tbo;

var timeCounter = 0;

We have added a new global variable that tracks the number of vertices in our model. In a more advanced program this would be wrapped within an object with the vbo, however for this simple program just having it as global is fine.


function main() {
	canvas = document.getElementById("screen");
	
	render = new WebRender(canvas, callback, 0, "WebRender.js");
	render.initialise(canvas.width, canvas.height);
	render.clearColor(0, 0, 0, 255);

	var shaderID = render.getSBOId();
	render.assignSBOVertexShader(shaderID, "index.js", "vertexShader");
	render.assignSBOPixelShader(shaderID, "index.js", "fragmentShader");
	render.addSBOVertexAttribute(shaderID, "vertex", "position", 3);
	render.addSBOVertexAttribute(shaderID, "vertex", "texcoord", 2);
	render.addSBOVertexAttribute(shaderID, "pixel", "position", 4);
	render.addSBOVertexAttribute(shaderID, "pixel", "texcoord", 2);
	render.loadSBO(shaderID);
	render.bindSBO(shaderID);

	vbo = render.getVBOId();
	loadModel(vbo);

	tbo = render.getTBOId();
	loadTexture("texture.png", tbo);
	
	var persMatrix = createPrespectiveMatrix(85, canvas.width/canvas.height, 0.1, 100);
	render.setShaderVariable("VERTEX_perspectiveMatrix", persMatrix);
	
	draw();
	
	return;
}

The main method is similar to that of the previous tutorial, the setup and shaders are created the same. However we have a new line loadModel(vbo); in order to load the cube model we are using. We also create a perspective matrix that is loaded into our WebRender instance with setShaderVariable(). Since we do not need to update the perspective matrix, it is created, loaded and not stored by our program.


function draw() {
	timeCounter += 0.01;

	var objectMatrix = new Matrix();
	objectMatrix.makeTranslationMatrix(0, 0, -4);
	objectMatrix.makeXRotationMatrix(0.5);
	objectMatrix.makeYRotationMatrix(timeCounter);
	render.setShaderVariable("VERTEX_objectMatrix", objectMatrix);
	
	render.bindTBO(0, tbo);
	
	render.clearBuffer(0);
	render.draw(vbo, 0, verticesLength);
	render.getBuffer();
}

There is only a minor change to our draw loop. The cube is given a different objectMatrix in order to now rotate it around the Y axis and to offset its position.


function loadModel() {
	//Place any model loading code here.
	
	//This example uses a cube which data is in the following array.
	var vertices=[-1,-1,1,0,1,1,-1,1,1,1,1,1,1,1,0,-1,-1,1,0,1,1,1,1,1,0,-1,1,1,
	0,0,-1,-1,-1,1,1,1,1,-1,0,0,1,-1,-1,0,1,-1,-1,-1,1,1,-1,1,-1,1,0,1,1,-1,0,0,
	-1,1,-1,0,0,-1,1,1,0,1,1,1,1,1,1,-1,1,-1,0,0,1,1,1,1,1,1,1,-1,1,0,-1,-1,-1,
	0,1,1,-1,1,1,0,-1,-1,1,0,0,-1,-1,-1,0,1,1,-1,-1,1,1,1,-1,1,1,0,1,-1,-1,1,1,
	1,1,-1,1,0,1,1,1,0,0,1,-1,-1,1,1,1,1,1,0,0,1,-1,1,0,1,-1,-1,-1,0,1,-1,1,1,1,
	0,-1,1,-1,0,0,-1,-1,-1,0,1,-1,-1,1,1,1,-1,1,1,1,0];
	verticesLength = 36;
	render.loadVBO(vbo, vertices);
}

This new function is where any model loading code would go. In our case this just provides an array containing our cube data in the format X, Y, Z, U, V, which is the same format as the previous tutorial. In a more advanced program this could have a JSON request to fetch the model data from a file.

The important part of this is that the model data is now seperated out of the main and loaded in a similar way to the textures.


function createPrespectiveMatrix(fieldOfView, aspectRatio, near, far) {	
	var top = near*Math.tan(fieldOfView * 3.14159265/360);
	var bottom = -top;	
	var right = top*aspectRatio;
	var left = -right;
	
	var result = new Matrix();
	result.set(0, 0, (2*near) / (right-left));
	result.set(1, 1, (2*near) / (top-bottom));
	result.set(2, 2, (near+far) / (far-near));
	result.set(3, 2, -1);
	result.set(2, 3, (2*near*far)/(far-near));
	result.set(3, 3, 0);
	
	return result;
}

This is just a utility function that creates a perspective projection matrix and should be familiar to anyone who has used OpenGL or DirectX. This function is used by the program to crate the matrix in main().

Conclusion

You should now know all the basics for using WebRender. Look at the API for further information about specific functions and at the examples for more complex effects and programs using WebRender.