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.
<!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>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;
}
}© 20072026 XoaX.net LLC. All rights reserved.