WebGL JavaScript

Viewing Persepctive Projections

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

ViewingPerspectiveProjections.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
        // This will be used to create the viewing frustum
        -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) {
      // These are set now for perspecive
      var fNear = -1.5 + Math.cos(1.3*gfAngle);
      var fFar = -4.5 + Math.cos(1.4*gfAngle);
      var fDegFOV = 45.0 + 15.0*Math.cos(1.6*gfAngle);
      var fAspectRatio = 1.0;
      // Calculate the width, height, and the aspect ratio
      var faViewedOrthoMatrix =CreatePerspectiveMatrix(fDegFOV, fAspectRatio, 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) {
        // Center the object at (0,0,-3)
        for (var i = 0; i < 4; ++i) {
          faVert[4*i + 2] -= 3.0;
          MultiplyMatrixVertex(faViewedOrthoMatrix, faVert, 4*i);
        }
      } else { //if (qInstanceWebGL === gqViewWebGL) {
        var faLookAtMatrix = CreateLookAtMatrix([.1, .2, -2.9],[0, 0, -3.0],[0, 1, 0]);
        // Create the orthographic matrix for viewing the frustum
        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;
            // Find the z first, since it will be used to calculate the x and y coordinates
            // z is the length of a leg of the right triangle
            // One half the field of view angle corresponds to the y height
            // We can use the tangent of half of the field of view angle to relate the near z and y
            var fCurrZ = ((gfaVertices[iOffset + 2] == 1.0) ? fNear: fFar);
            // Divide by 2 first because the angle in the triangle is half of the field of view. Then convert to radians.
            var fAngleRad = (fDegFOV/2.0)*Math.PI/180.0;
            var fPosXY = -fCurrZ*Math.tan(fAngleRad);
            faVert[iOffset] = fPosXY*gfaVertices[iOffset];
            faVert[iOffset + 1] = fPosXY*gfaVertices[iOffset + 1];
            //faVert[iOffset] = ((gfaVertices[iOffset] == 1.0) ? fRight : fLeft);
            //faVert[iOffset + 1] = ((gfaVertices[iOffset + 1] == 1.0) ? fTop : fBottom);
            faVert[iOffset + 2] = fCurrZ;
            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) {
          // Center the object at (0,0,-3)
          if (i < 4) {
            faVert[4*i + 2] -= 3.0;
          }
          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);
        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 CreatePerspectiveMatrix(fFieldOfViewDeg, fAspectRatio, fNearPlane, fFarPlane) {
      var fFieldOfViewRad = Math.PI*fFieldOfViewDeg/360;
      var fSin = Math.sin(fFieldOfViewRad);
      var fCos = Math.cos(fFieldOfViewRad);
      var fCot = fCos/fSin;
      var fDepth = fNearPlane - fFarPlane;
      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 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.