WebGL JavaScript

Draw a Rotating Dodecahedron with Lighting

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

DrawARotatingDodecahedronWithLighting.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">
    var gfAngle = 0.0;
    function Initialization() {
      gfAngle = 0.0;
      // Begin the animation loop with half second intervals.
      const kiIntervalId = setInterval(RotationRender, 20);
    }
    function RotationRender() {
      var faRotationMatrix = CreateARotationAroundZMatrix(gfAngle);
      Render(faRotationMatrix);
      gfAngle += .03;
    }
    function Render(faRotationMatrix) {
      // Get the WebGL Context
      var qCanvas = document.querySelector("#idCanvasWebGL");
      var qGL = qCanvas.getContext("webgl");

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

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

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

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

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

      // Set a directional light source, like the Sun
      var 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);

      var faModelViewProj = CreatePerspectiveMatrix(15, qCanvas.width/qCanvas.height, 1, 10);
      var 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 6 sides with 2 triangles per side and 3 vertices per triangle: 6x2x3 = 36
      var iVertexCount = CreateBuffers(qGL, qProgram);
      qGL.drawElements(qGL.TRIANGLES, iVertexCount, qGL.UNSIGNED_BYTE, 0);
    }

    function Normalize(faV) {
      var 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
      var faRow = [0,0,0,0];
      for (iRow = 0; iRow < 4; ++iRow) {
        // Copy the current row
        for(iCol = 0; iCol < 4; ++iCol) {
          faRow[iCol] = faaM[iRow + 4*iCol];
        }
        for(iCol = 0; iCol < 4; ++iCol) {
          faaM[iRow + 4*iCol] = 0.0;
          for (k = 0; k < 4; ++k) {
            faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k];
          }
        }
      }
    }
    function CreateARotationAroundZMatrix(fRotateRadians) {
      var fSin = Math.sin(fRotateRadians);
      var fCos = Math.cos(fRotateRadians);
      var 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) {
      var faCopyPoint = [0,0,0];
      // Copy the point
      for (i = 0; i < 3; ++i) {
        faCopyPoint[i] = faPoint[i];
      }
      for(iCol = 0; iCol < 3; ++iCol) {
        faPoint[iCol] = 0.0;
        for (iRow = 0; iRow < 3; ++iRow) {
          faPoint[iCol] += faMatrix[iRow + 4*iCol]*faCopyPoint[iRow];
        }
      }
    }
    function CreatePerspectiveMatrix(fFieldOfViewDeg, fAspectRatio, fNearPlane, fFarPlane) {
      var fFieldOfViewRad = Math.PI*fFieldOfViewDeg/180;
      var fSin = Math.sin(fFieldOfViewRad);
      var fCos = Math.cos(fFieldOfViewRad);
      var fCot = fCos/fSin;
      var fDepth = fFarPlane - fNearPlane;
      var 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) {
      var faViewDirection = Difference(faObject, faEye);
      Normalize(faViewDirection);
      var faRight = Cross(faViewDirection, faUp);
      Normalize(faRight);
      var faStraightUp = Cross(faRight, faViewDirection);
      var 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) {
      // The radius of the enclosing sphere is sqrt(3)
      // For a unit radius, use 1/Math.sqrt(3), Math.sqrt((3 - Math.sqrt(5))/6), Math.sqrt((3 + Math.sqrt(5))/6)
      const kfA = 1.0;
      const kfB = 2/(1 + Math.sqrt(5));
      const kfC = (1 + Math.sqrt(5))/2;
      // 20 vertices with 3 coordinates per vertex
      var faSingleVertices = [
        kfA, kfA, kfA,
        kfA, kfA, -kfA,
        kfA, -kfA, kfA,
        kfA, -kfA,-kfA,
        -kfA, kfA, kfA,

        -kfA, kfA, -kfA,
        -kfA, -kfA, kfA,
        -kfA, -kfA, -kfA,
        kfB, kfC, 0,
        -kfB, kfC, 0,

        kfB, -kfC, 0,
        -kfB, -kfC, 0,
        kfC, 0, kfB,
        kfC, 0, -kfB,
        -kfC, 0, kfB,

        -kfC, 0, -kfB,
        0, kfB, kfC,
        0, -kfB, kfC,
        0, kfB, -kfC,
        0, -kfB, -kfC
      ];
      // There are 12 sides, 5 vertices per side, 3 triangles per side, 3 vertices per triangle
      var iaSideIndices = [
        0, 12, 13, 1, 8,
        0, 8, 9, 4, 16,
        0, 16, 17, 2, 12,
        7, 19, 3, 10, 11,
        7, 11, 6, 14, 15,
        7, 15, 5, 18, 19,
        1, 18, 5, 9, 8,
        1, 13, 3, 19, 18,
        2, 10, 3, 13, 12,
        2, 17, 6, 11, 10,
        4, 14, 6, 17, 16,
        4, 9, 5, 15, 14
      ];
      // Construct the actual vertex array fom these indices.
      // The normals will be constant over each side.
      // So create 5x12 = 60 vertices and normals.
      // Calculate the normals on each side using the middle vertices and normalizing.
      // create the triangle indices as 0, 1, 2,    0, 2, 3,    0, 3, 4 and so on.
      // 12 sides, 5 vertices per side, 3 coordinates per vertex
	  var faVertices = new Float32Array(12*5*3);
      var faNormalVectors = new Float32Array(12*5*3);
      // 12 sides, 3 triangles per side, 3 vertices per triangle
      var ui8aIndices = new Uint8Array(12*3*3);
      var faV1 = new Float32Array(3);
      var faV2 = new Float32Array(3);
      for (var iFace = 0; iFace < 12; ++iFace) {
        var iFaceOffset = 15*iFace;
        for (var iVertex = 0; iVertex < 5; ++iVertex) {
          // Get the starting index for the vertex
          var iVert = 3*iaSideIndices[iVertex + 5*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*2 + iDim] - faVertices[iFaceOffset + iDim];
          faV2[iDim] = faVertices[iFaceOffset + 3*3 + iDim] - faVertices[iFaceOffset + iDim];
        }
        var faNormal = Cross(faV1, faV2);
        Normalize(faNormal);
        // Set the normal for all five 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];
          faNormalVectors[iFaceOffset + 9 + iDim] = faNormal[iDim];
          faNormalVectors[iFaceOffset + 12 + iDim] = faNormal[iDim];
        }

        // Write the indices for the three triangles (0, 1, 2), (0, 2, 3), (0, 3, 4)
        ui8aIndices[9*iFace] = 5*iFace;
        ui8aIndices[9*iFace + 1] = 5*iFace + 1;
        ui8aIndices[9*iFace + 2] = 5*iFace + 2;
        ui8aIndices[9*iFace + 3] = 5*iFace;
        ui8aIndices[9*iFace + 4] = 5*iFace + 2;
        ui8aIndices[9*iFace + 5] = 5*iFace + 3;
        ui8aIndices[9*iFace + 6] = 5*iFace;
        ui8aIndices[9*iFace + 7] = 5*iFace + 3;
        ui8aIndices[9*iFace + 8] = 5*iFace + 4;
      }
      var aqBufferData = [
        ['av4Vertex', faVertices],
        ['av4Normal', faNormalVectors]
      ];
      for (var qBufferData of aqBufferData) {
        var qBuffer = qGL.createBuffer();
        qGL.bindBuffer(qGL.ARRAY_BUFFER, qBuffer);
        qGL.bufferData(qGL.ARRAY_BUFFER, qBufferData[1], qGL.STATIC_DRAW);
        var 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
      var qIndexBuffer = qGL.createBuffer();
      qGL.bindBuffer(qGL.ELEMENT_ARRAY_BUFFER, qIndexBuffer);
      qGL.bufferData(qGL.ELEMENT_ARRAY_BUFFER, ui8aIndices, qGL.STATIC_DRAW);
      return ui8aIndices.length;
    }
    </script>
  </head>
  <body onload="Initialization();">
    <canvas id="idCanvasWebGL" width="400", height="400" style="border:1px solid blue"></canvas>
  </body>
</html>
 

Output

 
 

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