WebGL JavaScript

Draw a Rotating Octahedron with Lighting

This JavaScript program demonstrates how to draw a rotating solid colored octahedron with lighting WebGL program. This program shows how to specify a set of vertices and normal vectors with indices to specify the geometry.

DrawARotatingOctahedronWithLighting.html

<!DOCTYPE html>
<html>
	<head>
		<title>XoaX.net's WebGL</title>
		<script  id="idVertexShader" type="c">
			attribute vec4 av4Vertex;
			attribute vec4 av4Normal;
			uniform mat4 um4MvpMatrix;
			uniform vec3 uv3ObjectColor;
			uniform vec3 uv3LightColor;
			uniform vec3 uv3LightDirection;
			varying vec4 vv4Color;

			void main() {
				gl_Position = um4MvpMatrix*av4Vertex;
				vec3 v3Normal = normalize(av4Normal.xyz);
				float fIntensity = max(dot(uv3LightDirection, v3Normal), 0.0);
				vec3 v3Diffuse = fIntensity*uv3LightColor*uv3ObjectColor;
				vv4Color = vec4(v3Diffuse, 1.0);
			}
		</script>
		<script  id="idFragmantShader" type="c">
			precision mediump float;
			varying vec4 vv4Color;

			void main() {
				gl_FragColor = vv4Color;
			}
		</script>
		<script type="text/javascript" src="DrawARotatingOctahedronWithLighting.js"></script>
	</head>
	<body onload="Initialization();">
		<canvas id="idCanvasWebGL" width="400", height="400" style="border:1px solid orange"></canvas>
	</body>
</html>

DrawARotatingOctahedronWithLighting.js

var gfAngle = 0.0;

function Initialization() {
	gfAngle = 0.0;
	// Begin the animation loop.
	const kiIntervalId = setInterval(RotationRender, 20);
}

function RotationRender() {
	let faRotationMatrix = CreateARotationAroundYMatrix(gfAngle);
	Render(faRotationMatrix);
	gfAngle += .03;
}

function CreateProgram(qGL) {
	// Compile the vertex shader
	let sVertexShaderCode = document.querySelector("#idVertexShader").text;
	let qVertexShader = qGL.createShader(qGL.VERTEX_SHADER);
	qGL.shaderSource(qVertexShader, sVertexShaderCode);
	qGL.compileShader(qVertexShader);

	// Compile the fragment shader
	let sFragmentShaderCode = document.querySelector("#idFragmantShader").text;
	let qFragmentShader = qGL.createShader(qGL.FRAGMENT_SHADER);
	qGL.shaderSource(qFragmentShader, sFragmentShaderCode);
	qGL.compileShader(qFragmentShader);

	// Compile and link the program
	let qProgram = qGL.createProgram();
	qGL.attachShader(qProgram, qVertexShader);
	qGL.attachShader(qProgram, qFragmentShader);
	qGL.linkProgram(qProgram);
	qGL.useProgram(qProgram);

	return qProgram;
}

function Render(faRotationMatrix) {
	// Get the WebGL Context
	let qCanvas = document.querySelector("#idCanvasWebGL");
	let qGL = qCanvas.getContext("webgl");

	let qProgram = CreateProgram(qGL);

	// Get the storage locations of uniform variables and so on
	let qMvpMatrix = qGL.getUniformLocation(qProgram, 'um4MvpMatrix');
	let qObjectColor = qGL.getUniformLocation(qProgram, 'uv3ObjectColor');
	let qLightColor = qGL.getUniformLocation(qProgram, 'uv3LightColor');
	let qLightDirection = qGL.getUniformLocation(qProgram, 'uv3LightDirection');

	// Set the color to magenta and the light color to white
	qGL.uniform3f(qObjectColor, 1.0, 0.5, 0.0);
	qGL.uniform3f(qLightColor, 1.0, 1.0, 1.0);

	// Set a directional light source, like the Sun
	let faDirectionOfLight = new Float32Array([.5, 1.0, 2.0]);
	// Rotate the light direction to keep the front lit
	ApplyMatrixToPoint3D(faRotationMatrix, faDirectionOfLight);
	Normalize(faDirectionOfLight);
	qGL.uniform3fv(qLightDirection, faDirectionOfLight);

	let faModelViewProj = CreatePerspectiveMatrix(15, qCanvas.width/qCanvas.height, 1, 10);
	let faLookAt = CreateLookAtMatrix([4, 5, 6],[0, 0, 0],[0, 1, 0]);
	// Proj*Look*Rotate
	MultiplyMatrices(faModelViewProj, faLookAt);
	MultiplyMatrices(faModelViewProj, faRotationMatrix);
	qGL.uniformMatrix4fv(qMvpMatrix, false, faModelViewProj);

	qGL.clearColor(0.9, 0.9, 0.9, 1.0);
	qGL.enable(qGL.DEPTH_TEST);
	qGL.clear(qGL.COLOR_BUFFER_BIT | qGL.DEPTH_BUFFER_BIT);
	// There are 4 sides with 1 triangle per side and 3 vertices per triangle: 4x1x3 = 12
	let iVertexCount = CreateBuffers(qGL, qProgram);
	qGL.drawElements(qGL.TRIANGLES, iVertexCount, qGL.UNSIGNED_BYTE, 0);
}

function Normalize(faV) {
	let fL = Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
	faV[0] /= fL; faV[1] /= fL; faV[2] /= fL;
}

function Dot(faV1, faV2) {
	return (faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2]);
}

function Cross(faV1, faV2) {
	return [faV1[1]*faV2[2]-faV1[2]*faV2[1], faV1[2]*faV2[0]-faV1[0]*faV2[2], faV1[0]*faV2[1]-faV1[1]*faV2[0]];
}

function Difference(faV1, faV2) {
	return [faV1[0]-faV2[0], faV1[1]-faV2[1], faV1[2]-faV2[2]];
}

function MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A
	let 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 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;
}

function ApplyMatrixToPoint3D(faMatrix, faPoint) {
	let faCopyPoint = [0,0,0];
	// Copy the point
	for (let i = 0; i < 3; ++i) {
		faCopyPoint[i] = faPoint[i];
	}
	for(let iCol = 0; iCol < 3; ++iCol) {
		faPoint[iCol] = 0.0;
		for (let iRow = 0; iRow < 3; ++iRow) {
			faPoint[iCol] += faMatrix[iRow + 4*iCol]*faCopyPoint[iRow];
 		}
	}
}

function 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*fFarPlane*fNearPlane)/fDepth, 0.0]);
	return faMatrix;
}

function CreateLookAtMatrix(faEye, faObject, faUp) {
	let faViewDirection = Difference(faObject, faEye);
	Normalize(faViewDirection);
	let faRight = Cross(faViewDirection, faUp);
	Normalize(faRight);
	let faStraightUp = Cross(faRight, faViewDirection);
	let faMatrix = new Float32Array([
	faRight[0], faStraightUp[0], -faViewDirection[0], 0.0,
	faRight[1], faStraightUp[1], -faViewDirection[1], 0.0,
	faRight[2], faStraightUp[2], -faViewDirection[2], 0.0,
		-Dot(faEye, faRight), -Dot(faEye, faStraightUp), Dot(faEye, faViewDirection), 1.0]);
	return faMatrix;
}

function CreateBuffers(qGL, qProgram) {
	const kfSqrt3 = Math.sqrt(3);
	// 8 vertices with 3 coordinates per vertex
	var faSingleVertices = [
		kfSqrt3, 0.0, 0.0,
		-kfSqrt3, 0.0, 0.0,
		0.0, kfSqrt3, 0.0,
		0.0, -kfSqrt3, 0.0,
		0.0, 0.0, kfSqrt3,
		0.0, 0.0, -kfSqrt3
	];
	// There are 4 sides, 3 vertices per side
	var iaSideIndices = [
		4, 0, 2,
		4, 2, 1,
		4, 1, 3,
		4, 3, 0,
		5, 2, 0,
		5, 1, 2,
		5, 3, 1,
		5, 0, 3
	];
	// Construct the actual vertex array fom these indices.
	// The normals will be constant over each side.
	// So create 3x8 = 24 vertices and normals.
	// Calculate the normals on each side using the middle vertices and normalizing.
	// 8 sides, 3 vertices per side, 3 coordinates per vertex
	var faVertices = new Float32Array(8*3*3);
	var faNormalVectors = new Float32Array(8*3*3);
	// 8 sides, 1 triangle per side, 3 vertices per triangle
	var ui8aIndices = new Uint8Array(8*3);
	var faV1 = new Float32Array(3);
	var faV2 = new Float32Array(3);
	for (var iFace = 0; iFace < 8; ++iFace) {
		var iFaceOffset = 9*iFace;
		for (var iVertex = 0; iVertex < 3; ++iVertex) {
			// Get the starting index
			var iVert = 3*iaSideIndices[iVertex + 3*iFace];
			for (var iDim = 0; iDim < 3; ++iDim) {
				faVertices[iFaceOffset + 3*iVertex + iDim] = faSingleVertices[iVert + iDim];
			}
		}
		// Get the normal for the current face
		for (var iDim = 0; iDim < 3; ++iDim) {
			faV1[iDim] = faVertices[iFaceOffset + 3*1 + iDim] - faVertices[iFaceOffset + iDim];
			faV2[iDim] = faVertices[iFaceOffset + 3*2 + iDim] - faVertices[iFaceOffset + iDim];
		}
		var faNormal = Cross(faV1, faV2);
		Normalize(faNormal);
		// Set the normal for all three vertices on this side
		for (var iDim = 0; iDim < 3; ++iDim) {
			faNormalVectors[iFaceOffset + iDim] = faNormal[iDim];
			faNormalVectors[iFaceOffset + 3 + iDim] = faNormal[iDim];
			faNormalVectors[iFaceOffset + 6 + iDim] = faNormal[iDim];
		}
		// Write the indices for the triangle (0, 1, 2)
		ui8aIndices[3*iFace] = 3*iFace;
		ui8aIndices[3*iFace + 1] = 3*iFace + 1;
		ui8aIndices[3*iFace + 2] = 3*iFace + 2;
	}
	var aqBufferData = [
		['av4Vertex', faVertices],
		['av4Normal', faNormalVectors]
	];
	for (let qBufferData of aqBufferData) {
		let qBuffer = qGL.createBuffer();
		qGL.bindBuffer(qGL.ARRAY_BUFFER, qBuffer);
		qGL.bufferData(qGL.ARRAY_BUFFER, qBufferData[1], qGL.STATIC_DRAW);
		let qAttribute = qGL.getAttribLocation(qProgram, qBufferData[0]);
		// There are 3 coordinates per point and 3 vertices per triangle
		qGL.vertexAttribPointer(qAttribute, 3, qGL.FLOAT, false, 0, 0);
		qGL.enableVertexAttribArray(qAttribute);
		qGL.bindBuffer(qGL.ARRAY_BUFFER, null);
	}
	// Store the indices in the element buffer
	let qIndexBuffer = qGL.createBuffer();
	qGL.bindBuffer(qGL.ELEMENT_ARRAY_BUFFER, qIndexBuffer);
	qGL.bufferData(qGL.ELEMENT_ARRAY_BUFFER, ui8aIndices, qGL.STATIC_DRAW);
	return ui8aIndices.length;
}
 

Output

 
 

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