WebGL JavaScript

Drawing with a Basic OOD Setup

This JavaScript program demonstrates how create a setup for rendering with a few simple classes in WebGL.

BasicSetup.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>XoaX.net's WebGL</title>
  </head>
  <style>
    #idCanvas {
    	border: 1px solid black;
    	width: 800px;
    	height: 600px
    }
  </style>
		<script  id="idVertexShader" type="c">
			attribute vec4 av4Position;
			uniform mat4 um4MvpMatrix;
			uniform vec3 uv3ObjectColor;
			uniform vec3 uv3Normal;
			uniform vec3 uv3LightDirection;
			varying vec4 vv4Color;

			void main() {
				vec3 v3NormalLightDir = normalize(uv3LightDirection);
				gl_Position = um4MvpMatrix*av4Position;
				float fIntensity = max(dot(v3NormalLightDir, uv3Normal), 0.0);
				vec3 v3FinalColor = (.5*fIntensity + .5)*uv3ObjectColor;
				vv4Color = vec4(v3FinalColor, 0.5);
			}
		</script>
		<script  id="idFragmantShader" type="c">
			precision mediump float;

			//uniform vec4 uv4Color;
			varying vec4 vv4Color;

			void main() {
				//gl_FragColor = vec4(.5, .75, 1, 1);//uv4Color;
				gl_FragColor = vv4Color;
			}
		</script>
  <script type="text/javascript" src="BasicSetup.js"></script>
  <body onload="render()">
    <canvas id="idCanvas"></canvas>
  </body>
</html>

BasicSetup.js

class CLinAlg {
	// The rows are actually columns. So, the top row is the first column.
	static CreatePerspectiveMatrix(fFieldOfViewDeg, fAspectRatio, fNearPlane, fFarPlane) {
		let fFieldOfViewRad = Math.PI*fFieldOfViewDeg/180;
		let fSin = Math.sin(fFieldOfViewRad);
		let fCos = Math.cos(fFieldOfViewRad);
		let fCot = fCos/fSin;
		let fDepth = fFarPlane - fNearPlane;
		let faMatrix = new Float32Array([
			fCot/fAspectRatio, 0.0, 0.0, 0.0,
			0.0, fCot, 0.0, 0.0,
			0.0, 0.0, -(fFarPlane + fNearPlane)/fDepth, -1.0,
			0.0, 0.0, -(2.0*fFarPlane*fNearPlane)/fDepth, 0.0]);
		return faMatrix;
	}
	static CreateLookAtMatrix(faEye, faObject, faUp) {
		// Vector from the eye to the object
		let faViewDirection = CLinAlg.DifferenceVector(faObject, faEye);
		CLinAlg.NormalizeVector(faViewDirection);
		let faLeft = CLinAlg.Cross(faUp, faViewDirection);
		CLinAlg.NormalizeVector(faLeft);
		let faStraightUp = CLinAlg.Cross(faViewDirection, faLeft);
		let faReverseView = CLinAlg.Negate(faViewDirection);
		let faMatrix = new Float32Array([
			faLeft[0], faStraightUp[0], faReverseView[0], 0.0,
			faLeft[1], faStraightUp[1], faReverseView[1], 0.0,
			faLeft[2], faStraightUp[2], faReverseView[2], 0.0,
			-CLinAlg.Dot(faEye, faLeft), -CLinAlg.Dot(faEye, faStraightUp), -CLinAlg.Dot(faEye, faReverseView), 1.0]);
		return faMatrix;
	}
	static CreateARotationAroundZMatrix(fRotateRadians) {
		let fSin = Math.sin(fRotateRadians);
		let fCos = Math.cos(fRotateRadians);
		let faMatrix = new Float32Array([
			fCos,	-fSin,	0.0,	0.0,
			fSin,	fCos,		0.0,	0.0,
			0.0,	0.0,		1.0,	0.0,
			0.0,	0.0,		0.0,	1.0]);
		return faMatrix;
	}
	static CreateARotationAroundYMatrix(fRotateRadians) {
		let fSin = Math.sin(fRotateRadians);
		let fCos = Math.cos(fRotateRadians);
		let faMatrix = new Float32Array([
			fCos,	0.0, -fSin,	0.0,
			0.0,	1.0, 0.0,		0.0,
			fSin,	0.0, fCos,	0.0,
			0.0,	0.0, 0.0,		1.0]);
		return faMatrix;
	}
	static CreateIdentityMatrix() {
		let faMatrix = new Float32Array([
			1.0, 0.0, 0.0, 0.0,
			0.0, 1.0, 0.0, 0.0,
			0.0, 0.0, 1.0, 0.0,
			0.0, 0.0, 0.0, 1.0]);
		return faMatrix;
	}
	static Negate(faV) {
		let faReversed = new Float32Array(3);
		faReversed[0] = -faV[0];
		faReversed[1] = -faV[1];
		faReversed[2] = -faV[2];
		return faReversed;
	}
	static NormalizeVector(faV) {
		let dLength = 1.0/Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
		faV[0] *= dLength;
		faV[1] *= dLength;
		faV[2] *= dLength;
	}
	static Cross(faV1, faV2) {
		let faCross = new Float32Array(3);
		faCross[0] = faV1[1]*faV2[2] - faV1[2]*faV2[1];
		faCross[1] = faV1[2]*faV2[0] - faV1[0]*faV2[2];
		faCross[2] = faV1[0]*faV2[1] - faV1[1]*faV2[0];
		return faCross;
	}
	static DifferenceVector(faV1, faV2) {
		let faDiff = new Float32Array(3);
		faDiff[0] = faV1[0] - faV2[0];
		faDiff[1] = faV1[1] - faV2[1];
		faDiff[2] = faV1[2] - faV2[2];
		return faDiff;
	}
	static Dot(faV1, faV2) {
		return faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2];
	}
	static TransposeMatrix(faM) {
		let fSwap = faM[1];		faM[1] = faM[4];		faM[4] = fSwap;
				fSwap = faM[2];		faM[2] = faM[8];		faM[8] = fSwap;
				fSwap = faM[3];		faM[3] = faM[12];		faM[12] = fSwap;
				fSwap = faM[6];		faM[6] = faM[9];		faM[9] = fSwap;
				fSwap = faM[7];		faM[7] = faM[13];		faM[13] = fSwap;
				fSwap = faM[11];	faM[11] = faM[14];	faM[14] = fSwap;
	}
	static MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A
		var faRow = [0,0,0,0];
		for (let iRow = 0; iRow < 4; ++iRow) {
			// Copy the current row
			for(let iCol = 0; iCol < 4; ++iCol) {
				faRow[iCol] = faaM[iRow + 4*iCol];
			}
			for(let iCol = 0; iCol < 4; ++iCol) {
				faaM[iRow + 4*iCol] = 0.0;
				for (let k = 0; k < 4; ++k) {
					faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k];
				}
			}
		}
	}
}

function CreateProgram(kqGL) {

	// Compile the vertex shader
	const ksVertexShaderCode = document.getElementById("idVertexShader").innerHTML;
	let qVertexShader = kqGL.createShader(kqGL.VERTEX_SHADER);
	kqGL.shaderSource(qVertexShader, ksVertexShaderCode);
	kqGL.compileShader(qVertexShader);
	if (!kqGL.getShaderParameter(qVertexShader, kqGL.COMPILE_STATUS)) {
		alert("The vertex shader failed to compile!");
		kqGL.deleteShader(qVertexShader);
		return null;
	}

	// Compile the fragment shader
	const ksFragmentShaderCode = document.getElementById("idFragmantShader").innerHTML;
	let qFragmentShader = kqGL.createShader(kqGL.FRAGMENT_SHADER);
	kqGL.shaderSource(qFragmentShader, ksFragmentShaderCode);
	kqGL.compileShader(qFragmentShader);
	if (!kqGL.getShaderParameter(qFragmentShader, kqGL.COMPILE_STATUS)) {
		alert("The fragment shader failed to compile!");
		kqGL.deleteShader(qFragmentShader);
		return null;
	}

	// Compile and link the program
	let qProgram = kqGL.createProgram();
	kqGL.attachShader(qProgram, qVertexShader);
	kqGL.attachShader(qProgram, qFragmentShader);
	kqGL.linkProgram(qProgram);
	if (!kqGL.getProgramParameter(qProgram, kqGL.LINK_STATUS)) {
		alert("The program failed to initialize", kqGL.getProgramInfoLog(qProgram));
		return null;
	}

	return qProgram;
}

class CWebGL {
	#mqGL;
	#mqProgram;
	#mdaMatrix;
	#mdaRotation;
	constructor() {
		const qCanvas = document.getElementById("idCanvas");
	
		qCanvas.width = qCanvas.clientWidth;
		qCanvas.height = qCanvas.clientHeight;
	
		this.#mqGL = qCanvas.getContext("webgl");
		this.#mqProgram = CreateProgram(this.#mqGL);
		if (this.#mqProgram == null) {
			alert("Closing the program!");
		}
		this.#mqGL.useProgram(this.#mqProgram);
	}
	Context() {
		return this.#mqGL;
	}
	Program() {
		return this.#mqGL;
	}
	// All values in [0, 1]
	Clear(dR, dG, dB, dA) {
		this.#mqGL.clearColor(dR, dG, dB, dA);
		this.#mqGL.clear(this.#mqGL.COLOR_BUFFER_BIT);
		// Do not use the depth test with alpha blending
		// Enable alpha blending
		this.#mqGL.enable(this.#mqGL.BLEND);
		// Set blending function
		this.#mqGL.blendFunc(this.#mqGL.SRC_ALPHA, this.#mqGL.ONE_MINUS_SRC_ALPHA);
	}
	ClearDepthBuffer() {
		this.#mqGL.clear(this.#mqGL.DEPTH_BUFFER_BIT);
	}
	EnableScissorTest() {
		this.#mqGL.enable(this.#mqGL.SCISSOR_TEST);
	}
	ScissorRectangle(x, y, w, h) {
		this.#mqGL.scissor(x, y, w, h);
	}
	EnableDepthTest() {
		this.#mqGL.enable(this.#mqGL.DEPTH_TEST);
	}
	UseProgram() {
		this.#mqGL.useProgram(this.#mqProgram);
	}
	SetViewMatrix(dRadians) {
		const kdFOV = 30;
		const kdAspect = this.#mqGL.canvas.clientWidth/this.#mqGL.canvas.clientHeight;
		const kdNearPlane = 0.1;
		const kdFarPlane = 1000.0;
		// Create the matrices and multiply them.
		let faPerspective = CLinAlg.CreatePerspectiveMatrix(kdFOV, kdAspect, kdNearPlane, kdFarPlane);
		let faLookAt = CLinAlg.CreateLookAtMatrix([1, 1, 1],[0, 0, 0],[0, 1, 0]);
		let faRotationMatrix = CLinAlg.CreateARotationAroundYMatrix(dRadians);
		let faRotationZMatrix = CLinAlg.CreateARotationAroundZMatrix(dRadians/5)
		this.#mdaMatrix = faPerspective;
		CLinAlg.MultiplyMatrices(this.#mdaMatrix, faLookAt);
		CLinAlg.MultiplyMatrices(this.#mdaMatrix, faRotationMatrix);
		CLinAlg.MultiplyMatrices(this.#mdaMatrix, faRotationZMatrix);

		let qMvpMatrix = this.#mqGL.getUniformLocation(this.#mqProgram, 'um4MvpMatrix');
		this.#mqGL.uniformMatrix4fv(qMvpMatrix, false, this.#mdaMatrix);
		this.#mdaRotation = faRotationMatrix;
	}


	DrawBox(dX, dY, dZ, dL, dW, dH) {
		// Create 8 vertices
		let fa3V = new Float32Array(3*8);
		// [dX, dY, dZ,					dX + dL, dY, dZ,				dX, dY + dW, dZ,				dX + dL, dY + dW, dZ,
		//  dX, dY, dZ + dH,		dX + dL, dY, dZ + dH,		dX, dY + dW, dZ + dH,		dX + dL, dY + dW, dZ + dH]
		for (let i = 0; i < 8; ++i) {
			fa3V[3*i] = ((i & 1) ? dX  + dL : dX);
			fa3V[3*i+1] = ((i & 2) ? dY + dW : dY);
			fa3V[3*i+2] = ((i & 4) ? dZ + dH : dZ);
		}

		const kqGL = this.#mqGL;
		
		// Create the vertex buffer
		let qVertexBuffer = kqGL.createBuffer();
		kqGL.bindBuffer(kqGL.ARRAY_BUFFER, qVertexBuffer);
		kqGL.bufferData(kqGL.ARRAY_BUFFER, fa3V, kqGL.STATIC_DRAW);
		let qAttributeLocation = kqGL.getAttribLocation(this.#mqProgram, "av4Position");
		// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
		var iCoordinates = 3;					// Use three coordinates per point
		var iDataType = kqGL.FLOAT;		// Coordinates are 32 bit floats
		var bNormalize = false;				// Turn off normalization
		var iStride = 0;							// The number of entries to jump over for each iteration
		var iOffset = 0;							// The first entry to start reading coordinates from
		kqGL.vertexAttribPointer(qAttributeLocation, iCoordinates, iDataType, bNormalize, iStride, iOffset);
		kqGL.enableVertexAttribArray(qAttributeLocation);
		
		kqGL.bindBuffer(kqGL.ARRAY_BUFFER, null);

		// 6 sides with 4 vertex strips
		// Sides [0,2,1,3] [4,5,6,7] - [0,1,4,5] [2,6,3,7] - [0,4,2,6] [1,3,5,7]
		let ui8aIndices = new Uint8Array([0,2,1,3, 4,5,6,7, 0,1,4,5, 2,6,3,7, 0,4,2,6, 1,3,5,7]);
		const qIndiciesBuffer = kqGL.createBuffer();
		kqGL.bindBuffer(kqGL.ELEMENT_ARRAY_BUFFER, qIndiciesBuffer);
		kqGL.bufferData(kqGL.ELEMENT_ARRAY_BUFFER, ui8aIndices, kqGL.STATIC_DRAW,);
		let faaColors = [[1.0, 0.0, 0.0],[0.0, 1.0, 0.0],[0.0, 0.0, 1.0],
			[1.0, 1.0, 0.0],[1.0, 0.0, 1.0],[0.0, 1.0, 1.0]];
		let faaNormals = [[0.0, 0.0, -1.0],[0.0, 0.0, 1.0],[0.0, -1.0, 0.0],
			[0.0, 1.0, 0.0],[-1.0, 0.0, 0.0],[1.0, 0.0, 0.0]];

    // Perform the actual draw
    const kiVertexCount = 24;
    let qObjectColor = kqGL.getUniformLocation(this.#mqProgram, 'uv3ObjectColor');
    let qLightDirection = kqGL.getUniformLocation(this.#mqProgram, 'uv3LightDirection');
    // Light direction (-1,-1,-1) and rotate it
    kqGL.uniform3f(qLightDirection,
    	(this.#mdaRotation[0] + this.#mdaRotation[1] + this.#mdaRotation[2]), 
    	(this.#mdaRotation[4] + this.#mdaRotation[5] + this.#mdaRotation[6]),
    	(this.#mdaRotation[8] + this.#mdaRotation[9] + this.#mdaRotation[10]));
    let qNormal = kqGL.getUniformLocation(this.#mqProgram, 'uv3Normal');
    // Draw the sides
		for (let i = 0; i < 6; ++i) {
			kqGL.uniform3f(qObjectColor, faaColors[i][0], faaColors[i][1], faaColors[i][2]);
			kqGL.uniform3f(qNormal, faaNormals[i][0], faaNormals[i][1], faaNormals[i][2]);
			kqGL.drawElements(kqGL.TRIANGLE_STRIP, 4, kqGL.UNSIGNED_BYTE, 4*i);
		}
    // Draw the sides
		for (let i = 0; i < 6; ++i) {
			kqGL.uniform3f(qObjectColor, 1, 1, 1);
			kqGL.drawElements(kqGL.LINE_LOOP, 4, kqGL.UNSIGNED_BYTE, 4*i);
		}
	}
}

function render() {
	let qWebGL = new CWebGL();

	let dPreviousMS = document.timeline.currentTime;
	let dTotal = 0;
	
	// dTimeStamp is equal to document.timeline.currentTime and it is in milliseconds
	function RenderNextFrame(dTimeStamp) {
		let dElapsedMS = dTimeStamp - dPreviousMS;
		dTotal += dElapsedMS;
		dPreviousMS = dTimeStamp;
		
		qWebGL.SetViewMatrix(dTotal/1000);
		qWebGL.Clear(.25, .25, 0.25, 1.0);
		// THe depth buffer can cause transparency to fail
		//qWebGL.ClearDepthBuffer();
		//qWebGL.EnableDepthTest();

		qWebGL.DrawBox(-.5, -.75, -.25, 1, 1.5, .5);
		
		requestAnimationFrame(RenderNextFrame);
	}
	requestAnimationFrame(RenderNextFrame);
}
 

Output

 
 

© 2007–2026 XoaX.net LLC. All rights reserved.