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