WebGL JavaScript

Viewing Orthographic Projections

This JavaScript program demonstrates how orthographic projections change the way on object is seen in a WebGL program. The view on the right displays the box that defines the space of the orthographic projection and the view on the left displays how that orthographic projection displays objects.

ViewingOrthographicProjections.html

<!DOCTYPE html>
<html>
  <head>
    <title>XoaX.net's WebGL</title>

    <script  id="idModelVertexShader" type="c">
      attribute vec4 av4Vertex;
      attribute vec4 av4Color;
      varying vec4 vv4Color;
      void main() {
        gl_Position = av4Vertex;
        vv4Color = av4Color;
      }
    </script>

    <script  id="idModelFragmantShader" type="c">
      precision mediump float;
      varying vec4 vv4Color;
      void main() {
        gl_FragColor = vv4Color;
      }
    </script>

    <script  id="idViewVertexShader" type="c">
      attribute vec4 av4Vertex;
      attribute vec4 av4Color;
      varying vec4 vv4Color;
      void main() {
        gl_Position = av4Vertex;
        vv4Color = av4Color;
      }
    </script>

    <script  id="idViewFragmantShader" type="c">
      precision mediump float;
      varying vec4 vv4Color;
      void main() {
        gl_FragColor = vv4Color;
      }
    </script>

    <script type="text/javascript">
    var gqModelWebGL = null;
    var gqViewWebGL = null;
    function CreateProgramAndContext(qInstanceWebGL) {
      // Get the WebGL Context
      var qCanvas = document.querySelector("#"+qInstanceWebGL.msCanvasID);
      qInstanceWebGL.mqGL = qCanvas.getContext("webgl");
      var qGL = qInstanceWebGL.mqGL;

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

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

      // Compile and link the program
      qInstanceWebGL.mqProgram = qGL.createProgram();
      qGL.attachShader(qInstanceWebGL.mqProgram, qVertexShader);
      qGL.attachShader(qInstanceWebGL.mqProgram, qFragmentShader);
      qGL.linkProgram(qInstanceWebGL.mqProgram);
      qGL.useProgram(qInstanceWebGL.mqProgram);
    }
    function CreateBuffers(qInstanceWebGL) {
      var qGL = qInstanceWebGL.mqGL;
      var qVerticesBuffer = qGL.createBuffer();
      qGL.bindBuffer(qGL.ARRAY_BUFFER, qVerticesBuffer);
      qGL.bufferData(qGL.ARRAY_BUFFER, qInstanceWebGL.mfaTransformedVertices, qGL.STATIC_DRAW);

      var qVertexLoc = qGL.getAttribLocation(qInstanceWebGL.mqProgram, 'av4Vertex');
      qGL.vertexAttribPointer(qVertexLoc, 4, qGL.FLOAT, false, 0, 0);
      qGL.enableVertexAttribArray(qVertexLoc);

      var qColorsBuffer = qGL.createBuffer();
      qGL.bindBuffer(qGL.ARRAY_BUFFER, qColorsBuffer);
      qGL.bufferData(qGL.ARRAY_BUFFER, qInstanceWebGL.mfaVertexColors, qGL.STATIC_DRAW);

      var qColors = qGL.getAttribLocation(qInstanceWebGL.mqProgram, 'av4Color');
      qGL.vertexAttribPointer(qColors, 4, qGL.FLOAT, false, 0, 0);
      qGL.enableVertexAttribArray(qColors);
    }
    var gfaVertices = null;
    function Initialization() {
      gfaVertices = new Float32Array([
        // Put the vertices for the diamond first, then the cube vertices
        0.0, 0.85, 0.0, 1.0,  0.85, 0.0, 0.85, 1.0,   -0.85, 0.0, -0.85, 1.0,  0.0, -0.85, 0.0, 1.0,
        // These must be drawn back to front to render it correctly with the alpha blending
        -1.0, 1.0, 1.0, 1.0,  -1.0, 1.0, -1.0, 1.0,   -1.0, -1.0, 1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // x = -1
        1.0, -1.0, 1.0, 1.0,  1.0, -1.0, -1.0, 1.0,   -1.0, -1.0, 1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // y = -1
        1.0, 1.0, -1.0, 1.0,  1.0, -1.0, -1.0, 1.0,   -1.0, 1.0, -1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // z = -1
        1.0, 1.0, 1.0, 1.0,   1.0, -1.0, 1.0, 1.0,   1.0, 1.0, -1.0, 1.0,   1.0, -1.0, -1.0, 1.0,    // x = 1
        1.0, 1.0, 1.0, 1.0,   1.0, 1.0, -1.0, 1.0,   -1.0, 1.0, 1.0, 1.0,    -1.0, 1.0, -1.0, 1.0,  // y = 1
        1.0, 1.0, 1.0, 1.0,   -1.0, 1.0, 1.0, 1.0,   1.0, -1.0, 1.0, 1.0,    -1.0, -1.0, 1.0, 1.0  // z = 1
      ]);
      gqModelWebGL = new CInstanceWebGL("idModelCanvas", "idModelVertexShader", "idModelFragmantShader", 4);
      gqViewWebGL = new CInstanceWebGL("idViewCanvas", "idViewVertexShader", "idViewFragmantShader", 7*4);
      CreateProgramAndContext(gqModelWebGL);
      CreateProgramAndContext(gqViewWebGL);
      CreateBuffers(gqModelWebGL);
      CreateBuffers(gqViewWebGL);
      // Begin the animation loop.
      const kiIntervalId = setInterval(Render, 20);
    }
    function Render() {
        RenderModel(gqModelWebGL);
        RenderModel(gqViewWebGL);
    }

    var gfAngle = 0.0;
    function RenderModel(qInstanceWebGL) {
      // Create the orthographic projection.
      var fLeft = -1.5 + Math.cos(1.2*gfAngle);
      var fRight = 1.5 + Math.cos(1.1*gfAngle);
      var fBottom = -1.5 + Math.cos(1.5*gfAngle);
      var fTop = 1.5 + Math.cos(1.25*gfAngle);
      var fNear = 1.5 + Math.cos(1.3*gfAngle);
      var fFar = -1.5 + Math.cos(1.4*gfAngle);
      var faViewedOrthoMatrix = CreateOrthographicMatrix(fLeft, fRight, fBottom, fTop, fNear, fFar);

      var qGL = qInstanceWebGL.mqGL;
      var faVert = qInstanceWebGL.mfaTransformedVertices;
      var faClr = qInstanceWebGL.mfaVertexColors;
      // Create the rotation matrix
      var faRotationMatrix = CreateARotationAroundYMatrix(gfAngle);
      gfAngle += .005;
      gfAngle = ((gfAngle >= 2000.0*Math.PI) ? (gfAngle - 2000.0*Math.PI) : gfAngle);
      // Transform the first four vertices by the rotation: 4 vertices with 4 coordinates
      // First copy the vertices before the transformation
      for (var i = 0; i < 16; ++i) {
        faVert[i] = gfaVertices[i];
      }
      // Transform each diamond vertex
      for (var i = 0; i < 4; ++i) {
        MultiplyMatrixVertex(faRotationMatrix, faVert, 4*i);
        // Set the colors too
        faClr[4*i]      = .25;
        faClr[4*i + 1]  = .4 + Math.min(Math.max(Math.sin(faVert[4*i + 2]), -.4), .4);
        faClr[4*i + 2]  = .25;
        faClr[4*i + 3]  = 1.0;
      }

      if (qInstanceWebGL !== gqViewWebGL) {
        for (var i = 0; i < 4; ++i) {
          MultiplyMatrixVertex(faViewedOrthoMatrix, faVert, 4*i);
        }
      } else { //if (qInstanceWebGL === gqViewWebGL) {
        var faLookAtMatrix = CreateLookAtMatrix([ 1, 3, 2],[0, 0, 0],[0, 1, 0]);
        // Create the orthographic matrix
        var fOrthoSize = 5.0;
        var faOrthoMatrix =  CreateOrthographicMatrix(-fOrthoSize, fOrthoSize, -fOrthoSize, fOrthoSize, fOrthoSize, -fOrthoSize);
        MultiplyMatrices(faOrthoMatrix, faLookAtMatrix);
        // Add the extra colors for the view cube
        for (var iFace = 0; iFace < 6; ++iFace) {
          var fBrightness = 0.0;
          if (iFace % 3 == 0) {
            fBrightness = 1.0/7.0;
          } else if (iFace % 3 == 2) {
            fBrightness = 2.0/7.0;
          } else {
            fBrightness = 4.0/7.0;
          }
          var iBase = 16 + 16*iFace;
          for (var iVertex = 0; iVertex < 4; ++iVertex) {
            var iOffset = iBase + 4*iVertex;
            faVert[iOffset] = ((gfaVertices[iOffset] == 1.0) ? fRight : fLeft);
            faVert[iOffset + 1] = ((gfaVertices[iOffset + 1] == 1.0) ? fTop : fBottom);
            faVert[iOffset + 2] = ((gfaVertices[iOffset + 2] == 1.0) ? fNear : fFar);
            faVert[iOffset + 3] = gfaVertices[iOffset + 3];
            faClr[iOffset]     = fBrightness;
            faClr[iOffset + 1] = fBrightness;
            faClr[iOffset + 2] = fBrightness;
            faClr[iOffset + 3] = .2;
          }
        }
        for (var i = 0; i < 28; ++i) {
          MultiplyMatrixVertex(faOrthoMatrix, faVert, 4*i);
        }
      }
      // We need to create the buffers afterward
      CreateBuffers(qInstanceWebGL);
      qGL.clearColor(0.0, 0.0, 0.0, 1.0);
      if (qInstanceWebGL !== gqViewWebGL) {
        qGL.clear(qGL.COLOR_BUFFER_BIT);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 0, 4);
      } else {
        qGL.enable(qGL.DEPTH_TEST);
        qGL.clear(qGL.COLOR_BUFFER_BIT | qGL.DEPTH_BUFFER_BIT);
        // Enable alpha blending
        qGL.enable(qGL.BLEND);
        // Set blending function
        qGL.blendFunc(qGL.SRC_ALPHA, qGL.ONE_MINUS_SRC_ALPHA);
        // Draw the diamond first, since it is totally opaque.
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 0, 4);
        // The six sides
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 4, 4);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 8, 4);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 12, 4);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 16, 4);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 20, 4);
        qGL.drawArrays(qGL.TRIANGLE_STRIP, 24, 4);
      }
    }

    function CreateARotationAroundYMatrix(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;
    }
    // Multiply the four coordinate vertex in V at the start index
    function MultiplyMatrixVertex(faM, faV, iStart) { // V = M*V
      var faCopy = [0,0,0,0];
      for (var i = 0; i < 4; ++i) {
        faCopy[i] = faV[iStart + i];
      }
      for (iRow = 0; iRow < 4; ++iRow) {
        faV[iStart + iRow] = faM[iRow]*faCopy[0] + faM[iRow + 4]*faCopy[1] + faM[iRow + 8]*faCopy[2] + faM[iRow + 12]*faCopy[3];
      }
    }
    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 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(faObject, faRight), -Dot(faObject, faStraightUp), -Dot(faObject, faViewDirection), 1.0]);
      return faMatrix;
    }
    function CreateOrthographicMatrix(fLeft, fRight, fBottom, fTop, fNear, fFar) {
      if (fLeft >= fRight || fBottom >=  fTop || fFar >= fNear) {
        throw 'Improper Orthographic Projection Matrix';
      }
      fDx = fRight - fLeft;
      fDy = fTop - fBottom;
      fDz = fNear - fFar;
      var faMatrix = new Float32Array([
        2.0/fDx,                 0.0,                     0.0,                   0.0,
        0.0,                     2.0/fDy,                 0.0,                   0.0,
        0.0,                     0.0,                     2.0/fDz,               0.0,
        -(fLeft + fRight)/fDx,   -(fBottom + fTop)/fDy,   -(fNear + fFar)/fDz,   1.0]);
      return faMatrix;
    }
    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 CInstanceWebGL(sCanvasID, sVertexShaderID, sFragmentShaderID, iVertices) {
	  this.mqGL = null;
	  this.mqProgram = null;
	  this.msCanvasID = sCanvasID;
	  this.msVertexShaderID = sVertexShaderID;
	  this.msFragmentShaderID = sFragmentShaderID;
	  this.mfaTransformedVertices = new Float32Array(4*iVertices);
	  this.mfaVertexColors = new Float32Array(4*iVertices);
	}
    </script>
  </head>
  <body onload="Initialization();">
    <canvas id="idModelCanvas" width="400", height="400" style="border:1px solid lightgray"></canvas>
    <canvas id="idViewCanvas" width="400", height="400" style="border:1px solid lightgray"></canvas>
  </body>
</html>
 

Output

 
 

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