This JavaScript program demonstrates how to draw a rotating solid colored icosahedron with lighting WebGL program. This program shows how to specify a set of vertices and normal vectors with indices to specify the geometry.
<!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, 1.0, 0.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) { const kfPhi = (1 + Math.sqrt(5))/2; // For a unit sphere, remove the sqrt(3) const kfHyp = Math.sqrt(3)*(1/Math.sqrt(1 + kfPhi*kfPhi)); const kfProd = kfPhi*kfHyp; // 20 vertices with 3 coordinates per vertex var faSingleVertices = [ kfProd, kfHyp, 0, -kfProd, kfHyp, 0, kfProd, -kfHyp, 0, -kfProd, -kfHyp, 0, kfHyp, 0, kfProd, kfHyp, 0, -kfProd, -kfHyp, 0, kfProd, -kfHyp, 0, -kfProd, 0, kfProd, kfHyp, 0, -kfProd, kfHyp, 0, kfProd, -kfHyp, 0, -kfProd, -kfHyp ]; // There are 20 sides, 3 vertices per side var iaSideIndices = [ 0, 8, 4, 0, 5, 10, 2, 4, 9, 2, 11, 5, 1, 6, 8, 1, 10, 7, 3, 9, 6, 3, 7, 11, 0, 10, 8, 1, 8, 10, 2, 9, 11, 3, 11, 9, 4, 2, 0, 5, 0, 2, 6, 1, 3, 7, 3, 1, 8, 6, 4, 9, 4, 6, 10, 5, 7, 11, 7, 5 ]; // Construct the actual vertex array fom these indices. // The normals will be constant over each side. // So create 3x20 = 60 vertices and normals. // Calculate the normals on each side using the middle vertices and normalizing. // 20 sides, 3 vertices per side, 3 coordinates per vertex var faVertices = new Float32Array(20*3*3); var faNormalVectors = new Float32Array(20*3*3); // 20 sides, 1 triangle per side, 3 vertices per triangle var ui8aIndices = new Uint8Array(20*3); var faV1 = new Float32Array(3); var faV2 = new Float32Array(3); for (var iFace = 0; iFace < 20; ++iFace) { var iFaceOffset = 9*iFace; for (var iVertex = 0; iVertex < 3; ++iVertex) { // Get the starting index var iVert = 3*iaSideIndices[iVertex + 3*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*1 + iDim] - faVertices[iFaceOffset + iDim]; faV2[iDim] = faVertices[iFaceOffset + 3*2 + iDim] - faVertices[iFaceOffset + iDim]; } var faNormal = Cross(faV1, faV2); Normalize(faNormal); // Set the normal for all three 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]; } // Write the indices for the triangle (0, 1, 2) ui8aIndices[3*iFace] = 3*iFace; ui8aIndices[3*iFace + 1] = 3*iFace + 1; ui8aIndices[3*iFace + 2] = 3*iFace + 2; } 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 lime"></canvas> </body> </html>
© 20072024 XoaX.net LLC. All rights reserved.