WebGL JavaScript

Draw a Two-Sided Triangle

This JavaScript program demonstrates how to draw a two-sided triangle in WebGL. To do this, the triangle is drawn twice in two different colors and the back faces are culled.

RotateTwoSidedTriangle.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>XoaX.net's WebGL</title>
		<style>
			#idCanvas {
				border: 1px solid black;
				width: 800px;
				height: 600px
			}
		</style>
		<script  id="idVertexShader" type="c">
			attribute vec4 av4VertexPosition;
			uniform mat4 um4Model;
			uniform mat4 um4View;
			uniform mat4 um4Projection;
			void main() {
				gl_Position = um4Projection*um4View*um4Model*av4VertexPosition;
			}
		</script>
		<script  id="idFragmantShader" type="c">
			precision mediump float;
			uniform vec4 uv4Color;
			void main() {
				gl_FragColor = uv4Color;//vec4(0.5, 0.1, 0.1, 1.0);
			}
		</script>
		<script type="text/javascript" src="RotateTwoSidedTriangle.js"></script>
  </head>
  <body onload="Render()">
    <canvas id="idCanvas""></canvas>
  </body>
</html>

RotateTwoSidedTriangle.js

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;
}

function Render() {

	const qCanvas = document.getElementById("idCanvas");
	qCanvas.width = qCanvas.clientWidth;
	qCanvas.height = qCanvas.clientHeight;
	const kqGL = qCanvas.getContext("webgl");

	let qProgram = CreateProgram(kqGL);

	// Tell WebGL how to convert from clip space to pixels
	kqGL.viewport(0, 0, kqGL.canvas.width, kqGL.canvas.height);

	// Clear the canvas
	kqGL.clearColor(0.85, 0.85, 0.85, 1.0);
	kqGL.clear(kqGL.COLOR_BUFFER_BIT);

	kqGL.useProgram(qProgram);

	// Create the matrices here and pass them in
	// Millseconds relative to timeOrigin
	let dTime = performance.now()/6000.0;
	// Kepp the fractional part
	dTime -= Math.floor(dTime);
	// Create the model matrix - a rotation around the y-axis
	let faModel = CreateRotationAroundYMatrix(2*Math.PI*dTime);
	// Create the perspective matrix
	const kdFOV = Math.PI*(45.0/180.0);
	const kdAspect = qCanvas.clientWidth/qCanvas.clientHeight;
	const kdNearPlane = 0.1;
	const kdFarPlane = 1000.0;
	let faProjection = CreatePerspective(kdFOV, kdAspect, kdNearPlane, kdFarPlane);
	// Create the view matrix
	let daEye = [-3.0, 2.0, 0.0];
	let daCenter = [0.0, 0.0, 0.0];
	let daUp = [0.0, 1.0, 0.0];
	let faView = CreateLookAt(daEye, daCenter, daUp);
	// Create the matrices here and pass them in
	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(qProgram, "um4Model"), false, faModel);
	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(qProgram, "um4View"), false, faView);
	kqGL.uniformMatrix4fv(kqGL.getUniformLocation(qProgram, "um4Projection"), false, faProjection);

	// Color the front side red
	let faVertexColor = new Float32Array([0.5, 0.3, 0.3, 1.0]);
	const kiColor = kqGL.getUniformLocation(qProgram, 'uv4Color');
	kqGL.uniform4fv(kiColor, faVertexColor);

	let dAngle1 = Math.PI/2.0;
	let dAngle2 = 2.0*Math.PI/3.0 + Math.PI/2.0;
	let dAngle3 = 4.0*Math.PI/3.0 + Math.PI/2.0;
	// An Equilateral Triangle
	let faPositions = new Float32Array([Math.cos(dAngle1), Math.sin(dAngle1), 0.0, 1.0,
		Math.cos(dAngle2), Math.sin(dAngle2), 0.0, 1.0,
		Math.cos(dAngle3), Math.sin(dAngle3), 0.0, 1.0]);
	let qPositionBuffer = kqGL.createBuffer();
	kqGL.bindBuffer(kqGL.ARRAY_BUFFER, qPositionBuffer);
	kqGL.bufferData(kqGL.ARRAY_BUFFER, faPositions, kqGL.STATIC_DRAW);
	let qAttrLoc = kqGL.getAttribLocation(qProgram, "av4VertexPosition");
	kqGL.vertexAttribPointer(qAttrLoc, 4, kqGL.FLOAT, false, 0, 0);
	kqGL.enableVertexAttribArray(qAttrLoc);
	kqGL.drawArrays(kqGL.TRIANGLES, 0, 3);

	// Change the color to green before we draw the back side
	let faBacksideColor = new Float32Array([0.3, 0.5, 0.3, 1.0]);
	kqGL.uniform4fv(kiColor, faBacksideColor);

	// Draw a triangle in the oppositie direction (back side face)
	let iaSideIndices = new Uint8Array([0, 2, 1]);
	let qIndexBuffer = kqGL.createBuffer();
	kqGL.bindBuffer(kqGL.ELEMENT_ARRAY_BUFFER, qIndexBuffer);
	kqGL.bufferData(kqGL.ELEMENT_ARRAY_BUFFER, iaSideIndices, kqGL.STATIC_DRAW);
	kqGL.drawElements(kqGL.TRIANGLES, 3, kqGL.UNSIGNED_BYTE, 0);

	// Specify that the back of the polygons should be culled (discarded)
	// So, only one side of each traingle is rendered
	kqGL.enable(kqGL.CULL_FACE);
	kqGL.cullFace(kqGL.BACK); 
	requestAnimationFrame(Render);
}

function CreateIdentity() {
	let faI = new Float32Array(16);
	faI[0]  = 1.3; faI[1]  = 0.0; faI[2]  = 0.0; faI[3]  = 0.0;
	faI[4]  = 0.0; faI[5]  = 1.3; faI[6]  = 0.0; faI[7]  = 0.0;
	faI[8]  = 0.0; faI[9]  = 0.0; faI[10] = 1.3; faI[11] = 0.0;
	faI[12] = 0.0; faI[13] = 0.0; faI[14] = 0.0; faI[15] = 1.0;
	return faI;
}

function CreateRotationAroundYMatrix(fAngleRadians) {
	let faM = CreateIdentity();
	let dSin = Math.sin(fAngleRadians);
	let dCos = Math.cos(fAngleRadians);
	// Temp storage
	let faV = new Float32Array(8);
	// Perform the rotation to get the resulting x and z rows
	for (let i = 0; i < 4; ++i) {
		faV[i] = dCos*faM[i] + dSin*faM[i+8];
		faV[i + 4] = -dSin*faM[i] + dCos*faM[i+8];
	}
	// Copy the rotated rows back to the matrix
	for (let i = 0; i < 4; ++i) {
		faM[i] = faV[i];
		faM[i + 8] = faV[i+4];
	}
	return faM;
}

function CreatePerspective(dFOV, dAspect, dNear, dFar) {
	let faM = new Float32Array(16);
	let dF = 1.0/Math.tan(dFOV/2.0);
	let dNF = 1/(dNear-dFar);
	faM[0] = dF/dAspect; faM[1] = 0.0;  faM[2] = 0.0;                 faM[3] = 0.0;
	faM[4] = 0.0;        faM[5] = dF;   faM[6] = 0.0;                 faM[7] = 0.0;
	faM[8] = 0.0;        faM[9] = 0.0;  faM[10] = (dNear+dFar)*dNF;   faM[11] = -1.0;
	faM[12] = 0.0;       faM[13] = 0.0; faM[14] = 2.0*dNear*dFar*dNF; faM[15] = 0.0;
	return faM;
}

function CreateLookAt(faEye, faCenter, faUp) {
	let faM = new Float32Array(16);
	let faView = Difference(faEye, faCenter);
	Normalize(faView);
	let faX = Cross(faUp, faView);
	Normalize(faX);
	let faY = Cross(faView, faX);
	Normalize(faY);
	faM[0] = faX[0]; faM[1] = faY[0]; faM[2] = faView[0];  faM[3] = 0.0;
	faM[4] = faX[1]; faM[5] = faY[1]; faM[6] = faView[1];  faM[7] = 0.0;
	faM[8] = faX[2]; faM[9] = faY[2]; faM[10] = faView[2]; faM[11] = 0.0;
	faM[12] = -Dot(faX, faEye)
	faM[13] = -Dot(faY, faEye)
	faM[14] = -Dot(faView, faEye)
	faM[15] = 1.0;
	return faM;
}

function Dot(faV1, faV2) {
	let dDot = 0.0;
	for (let i = 0; i < 3; ++i) {
		dDot += faV1[i]*faV2[i];
	}
	return dDot;
}

function Difference(faV1, faV2) {
	let faDiff = new Float32Array(3);
	for (let i = 0; i < 3; ++i) {
		faDiff[i] = faV1[i] - faV2[i];
	}
	return faDiff;
}

function Cross(faV1, faV2) {
	let faCross = new Float32Array(3);
	for (let i = 0; i < 3; ++i) {
		faCross[i] = faV1[(i+1)%3]*faV2[(i+2)%3] - faV1[(i+2)%3]*faV2[(i+1)%3];
	}
	return faCross;
}

function Normalize(faV) {
	let dLength = 1.0/Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
	for (let i = 0; i < 3; ++i) {
		faV[i] *= dLength;
	}
}
 

Output

 
 

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