Web-based 3D Model Viewer JavaScript, WebGL

👤 Sharing: AI
```html
<!DOCTYPE html>
<html>
<head>
    <title>Simple 3D Model Viewer</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="glCanvas"></canvas>

    <script>
        // Get the canvas element
        const canvas = document.getElementById('glCanvas');

        // Initialize WebGL
        const gl = canvas.getContext('webgl');

        // Check if WebGL is supported
        if (!gl) {
            alert('Unable to initialize WebGL. Your browser may not support it.');
        }

        // Vertex shader program (defines how vertices are processed)
        const vsSource = `
            attribute vec4 aVertexPosition;
            attribute vec3 aVertexNormal;

            uniform mat4 uModelViewMatrix;
            uniform mat4 uProjectionMatrix;
            uniform mat4 uNormalMatrix; // For correct lighting when scaling

            varying vec3 vNormal;
            varying vec3 vEyePosition; //Position of the vertex in eye space

            void main() {
                gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
                vNormal = normalize(mat3(uNormalMatrix) * aVertexNormal); // Transform normals
                vEyePosition = vec3(uModelViewMatrix * aVertexPosition); // Transform vertex position to eye space
            }
        `;

        // Fragment shader program (defines how pixels are colored)
        const fsSource = `
            precision mediump float;

            varying vec3 vNormal;
            varying vec3 vEyePosition; //Vertex position in eye space

            uniform vec3 uLightDirection; // Directional light
            uniform vec3 uAmbientColor;
            uniform vec3 uDiffuseColor;
            uniform vec3 uSpecularColor;
            uniform float uShininess;

            void main() {
                vec3 normal = normalize(vNormal);
                vec3 lightDir = normalize(uLightDirection); //Directional light vector
                vec3 viewDir = normalize(-vEyePosition); //Directional vector from vertex to eye.

                //Diffuse lighting
                float diff = max(dot(normal, lightDir), 0.0);
                vec3 diffuse = uDiffuseColor * diff;

                //Specular lighting
                vec3 reflectedLight = reflect(-lightDir, normal); //Reflection vector
                float spec = pow(max(dot(viewDir, reflectedLight), 0.0), uShininess);
                vec3 specular = uSpecularColor * spec;

                vec3 ambient = uAmbientColor;

                gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
            }
        `;

        // Initialize shader program
        function initShaderProgram(gl, vsSource, fsSource) {
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

            // Create the shader program
            const shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            // If creating the shader program failed, alert
            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
                return null;
            }

            return shaderProgram;
        }

        // Creates a shader of the given type, uploads the source and compiles it.
        function loadShader(gl, type, source) {
            const shader = gl.createShader(type);

            // Send the source to the shader object
            gl.shaderSource(shader, source);

            // Compile the shader program
            gl.compileShader(shader);

            // See if it compiled successfully
            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            return shader;
        }


        // Define the geometry (a simple cube for this example)
        const cubeVertices = [
            // Front face
            -1.0, -1.0,  1.0,
             1.0, -1.0,  1.0,
             1.0,  1.0,  1.0,
            -1.0,  1.0,  1.0,

            // Back face
            -1.0, -1.0, -1.0,
            -1.0,  1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0, -1.0, -1.0,

            // Top face
            -1.0,  1.0, -1.0,
            -1.0,  1.0,  1.0,
             1.0,  1.0,  1.0,
             1.0,  1.0, -1.0,

            // Bottom face
            -1.0, -1.0, -1.0,
             1.0, -1.0, -1.0,
             1.0, -1.0,  1.0,
            -1.0, -1.0,  1.0,

            // Right face
             1.0, -1.0, -1.0,
             1.0,  1.0, -1.0,
             1.0,  1.0,  1.0,
             1.0, -1.0,  1.0,

            // Left face
            -1.0, -1.0, -1.0,
            -1.0, -1.0,  1.0,
            -1.0,  1.0,  1.0,
            -1.0,  1.0, -1.0,
        ];

        const cubeNormals = [
            // Front
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,
             0.0,  0.0,  1.0,

            // Back
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,
             0.0,  0.0, -1.0,

            // Top
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,
             0.0,  1.0,  0.0,

            // Bottom
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,
             0.0, -1.0,  0.0,

            // Right
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,
             1.0,  0.0,  0.0,

            // Left
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
            -1.0,  0.0,  0.0,
        ];



        const cubeIndices = [
            0,  1,  2,      0,  2,  3,    // front
            4,  5,  6,      4,  6,  7,    // back
            8,  9,  10,     8,  10, 11,   // top
            12, 13, 14,     12, 14, 15,   // bottom
            16, 17, 18,     16, 18, 19,   // right
            20, 21, 22,     20, 22, 23    // left
        ];


        // Initialize buffers
        function initBuffers(gl) {

            const positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeVertices), gl.STATIC_DRAW);

            const normalBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeNormals), gl.STATIC_DRAW);

            const indexBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeIndices), gl.STATIC_DRAW);

            return {
                position: positionBuffer,
                normal: normalBuffer,
                indices: indexBuffer,
            };
        }

        // Draw the scene
        function drawScene(gl, programInfo, buffers, deltaTime) {
            gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
            gl.clearDepth(1.0);                 // Clear everything
            gl.enable(gl.DEPTH_TEST);           // Enable depth testing
            gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            // Create a perspective matrix, a special matrix that
            // is used to simulate the distortion of perspective in a camera.
            const fieldOfView = 45 * Math.PI / 180;   // in radians
            const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            const zNear = 0.1;
            const zFar = 100.0;
            const projectionMatrix = mat4.create();

            // note: glmatrix.js always has the first argument
            // as the destination to receive the result.
            mat4.perspective(projectionMatrix,
                              fieldOfView,
                              aspect,
                              zNear,
                              zFar);

            // Set the drawing position to the "identity" point, which is
            // the center of the scene.
            const modelViewMatrix = mat4.create();

            // Now move the drawing position a bit to where we want to start drawing the square.
            mat4.translate(modelViewMatrix,     // destination matrix
                           modelViewMatrix,     // matrix to translate
                           [-0.0, 0.0, -6.0]);  // amount to translate
            mat4.rotate(modelViewMatrix,  // destination matrix
                        modelViewMatrix,  // matrix to rotate
                        cubeRotation,     // amount to rotate in radians
                        [0, 1, 0]);       // axis to rotate around (Y)

            mat4.rotate(modelViewMatrix,  // destination matrix
                        modelViewMatrix,  // matrix to rotate
                        cubeRotation * 0.7,// amount to rotate in radians
                        [1, 0, 0]);       // axis to rotate around (X)


            const normalMatrix = mat4.create();
            mat4.invert(normalMatrix, modelViewMatrix);
            mat4.transpose(normalMatrix, normalMatrix);


            // Tell WebGL how to pull out the positions from the position buffer into the vertexPosition attribute.
            {
                const numComponents = 3;  // pull out 3 values per iteration
                const type = gl.FLOAT;    // the data in the buffer is 32bit floats
                const normalize = false;  // don't normalize
                const stride = 0;         // how many bytes to get from one set of values to the next
                                          // 0 = use type and numComponents above
                const offset = 0;         // how many bytes inside the buffer to start from
                gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
                gl.vertexAttribPointer(
                    programInfo.attribLocations.vertexPosition,
                    numComponents,
                    type,
                    normalize,
                    stride,
                    offset);
                gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
            }


            // Tell WebGL how to pull out the normals from
            // the normal buffer into the vertexNormal attribute.
            {
                const numComponents = 3;
                const type = gl.FLOAT;
                const normalize = false;
                const stride = 0;
                const offset = 0;
                gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
                gl.vertexAttribPointer(
                    programInfo.attribLocations.vertexNormal,
                    numComponents,
                    type,
                    normalize,
                    stride,
                    offset);
                gl.enableVertexAttribArray(programInfo.attribLocations.vertexNormal);
            }


            // Tell WebGL which indices to use to index the vertices
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);


            // Tell WebGL to use our program when drawing
            gl.useProgram(programInfo.program);

            // Set the shader uniforms
            gl.uniformMatrix4fv(
                programInfo.uniformLocations.projectionMatrix,
                false,
                projectionMatrix);
            gl.uniformMatrix4fv(
                programInfo.uniformLocations.modelViewMatrix,
                false,
                modelViewMatrix);
            gl.uniformMatrix4fv(
                programInfo.uniformLocations.normalMatrix,
                false,
                normalMatrix);

            //Lighting uniforms
            gl.uniform3fv(programInfo.uniformLocations.lightDirection, [0.5, 0.7, 0.8]); // Directional light
            gl.uniform3fv(programInfo.uniformLocations.ambientColor, [0.2, 0.2, 0.2]);  // Ambient color
            gl.uniform3fv(programInfo.uniformLocations.diffuseColor, [0.8, 0.7, 0.7]);  // Diffuse color
            gl.uniform3fv(programInfo.uniformLocations.specularColor, [0.9, 0.8, 0.8]); // Specular color
            gl.uniform1f(programInfo.uniformLocations.shininess, 32.0);          // Shininess


            {
                const vertexCount = 36;
                const type = gl.UNSIGNED_SHORT;
                const offset = 0;
                gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
            }

            // Update the rotation of the cube.
            cubeRotation += deltaTime;
        }


        // Initialize everything and start the render loop
        function main() {
            // Resize canvas to fit window
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            gl.viewport(0, 0, canvas.width, canvas.height);


            const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

            const programInfo = {
                program: shaderProgram,
                attribLocations: {
                    vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
                    vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'),
                },
                uniformLocations: {
                    projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
                    modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
                    normalMatrix: gl.getUniformLocation(shaderProgram, 'uNormalMatrix'),

                    lightDirection: gl.getUniformLocation(shaderProgram, 'uLightDirection'),
                    ambientColor: gl.getUniformLocation(shaderProgram, 'uAmbientColor'),
                    diffuseColor: gl.getUniformLocation(shaderProgram, 'uDiffuseColor'),
                    specularColor: gl.getUniformLocation(shaderProgram, 'uSpecularColor'),
                    shininess: gl.getUniformLocation(shaderProgram, 'uShininess'),
                },
            };

            const buffers = initBuffers(gl);

            let then = 0;
            function render(now) {
                now *= 0.001;  // convert to seconds
                const deltaTime = now - then;
                then = now;

                drawScene(gl, programInfo, buffers, deltaTime);

                requestAnimationFrame(render);
            }
            requestAnimationFrame(render);
        }


        //Variables to be updated by the render loop
        let cubeRotation = 0.0;

        //Start the main function.  This is the entry point.
        main();



        //Helper function to load glMatrix.  Normally this would be in a separate import
        // or loaded via NPM, but it helps make this a self-contained example.
        // You'll likely need to adapt this loading logic based on how you manage
        // dependencies in your project.
        const mat4 = {
            create: function() {
                return new Float32Array(16);
            },
            perspective: function(out, fovy, aspect, near, far) {
                const f = 1.0 / Math.tan(fovy / 2);
                const nf = 1 / (near - far);

                out[0] = f / aspect;
                out[1] = 0;
                out[2] = 0;
                out[3] = 0;
                out[4] = 0;
                out[5] = f;
                out[6] = 0;
                out[7] = 0;
                out[8] = 0;
                out[9] = 0;
                out[10] = (far + near) * nf;
                out[11] = -1;
                out[12] = 0;
                out[13] = 0;
                out[14] = (2 * far * near) * nf;
                out[15] = 0;
                return out;
            },
            translate: function(out, a, v) {
                let x = v[0], y = v[1], z = v[2];
                let a00, a01, a02, a03;
                let a10, a11, a12, a13;
                let a20, a21, a22, a23;

                if (a === out) {
                    out[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
                    out[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
                    out[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
                    out[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
                } else {
                    a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3];
                    a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7];
                    a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11];

                    out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03;
                    out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13;
                    out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23;

                    out[12] = a00 * x + a10 * y + a20 * z + a[12];
                    out[13] = a01 * x + a11 * y + a21 * z + a[13];
                    out[14] = a02 * x + a12 * y + a22 * z + a[14];
                    out[15] = a03 * x + a13 * y + a23 * z + a[15];
                }

                return out;
            },
            rotate: function(out, a, rad, axis) {
                let x = axis[0], y = axis[1], z = axis[2];
                let len = Math.hypot(x, y, z);

                if (len < 0.000001) return null;

                len = 1 / len;
                x *= len;
                y *= len;
                z *= len;

                let s = Math.sin(rad);
                let c = Math.cos(rad);
                let t = 1 - c;

                let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
                let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
                let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];

                // Construct the elements of the rotation matrix
                let b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s;
                let b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s;
                let b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c;

                // Perform axis-specific matrix multiplication
                out[0] = a00 * b00 + a10 * b01 + a20 * b02;
                out[1] = a01 * b00 + a11 * b01 + a21 * b02;
                out[2] = a02 * b00 + a12 * b01 + a22 * b02;
                out[3] = a03 * b00 + a13 * b01 + a23 * b02;

                out[4] = a00 * b10 + a10 * b11 + a20 * b12;
                out[5] = a01 * b10 + a11 * b11 + a21 * b12;
                out[6] = a02 * b10 + a12 * b11 + a22 * b12;
                out[7] = a03 * b10 + a13 * b11 + a23 * b12;

                out[8] = a00 * b20 + a10 * b21 + a20 * b22;
                out[9] = a01 * b20 + a11 * b21 + a21 * b22;
                out[10] = a02 * b20 + a12 * b21 + a22 * b22;
                out[11] = a03 * b20 + a13 * b21 + a23 * b22;

                if (a !== out) { // If the source and destination differ, copy the unchanged last row
                    out[12] = a[12];
                    out[13] = a[13];
                    out[14] = a[14];
                    out[15] = a[15];
                }
                return out;
            },
            invert: function(out, a) {
                let a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3];
                let a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7];
                let a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11];
                let a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15];

                let b00 = a00 * a11 - a01 * a10;
                let b01 = a00 * a12 - a02 * a10;
                let b02 = a00 * a13 - a03 * a10;
                let b03 = a01 * a12 - a02 * a11;
                let b04 = a01 * a13 - a03 * a11;
                let b05 = a02 * a13 - a03 * a12;
                let b06 = a20 * a31 - a21 * a30;
                let b07 = a20 * a32 - a22 * a30;
                let b08 = a20 * a33 - a23 * a30;
                let b09 = a21 * a32 - a22 * a31;
                let b10 = a21 * a33 - a23 * a31;
                let b11 = a22 * a33 - a23 * a32;

                // Calculate the determinant
                let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;

                if (!det) {
                    return null;
                }
                det = 1.0 / det;

                out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
                out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
                out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
                out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
                out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
                out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
                out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
                out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
                out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
                out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
                out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
                out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
                out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
                out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
                out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
                out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;

                return out;
            },
            transpose: function(out, a) {
                // If we are transposing ourselves we can skip a few steps but have to cache some values
                if (out === a) {
                    let a01 = a[1], a02 = a[2], a03 = a[3];
                    let a12 = a[6], a13 = a[7];
                    let a23 = a[11];

                    out[1] = a[4];
                    out[2] = a[8];
                    out[3] = a[12];
                    out[4] = a01;
                    out[6] = a[9];
                    out[7] = a[13];
                    out[8] = a02;
                    out[9] = a12;
                    out[11] = a[14];
                    out[12] = a03;
                    out[13] = a13;
                    out[14] = a23;
                } else {
                    out[0] = a[0];
                    out[1] = a[4];
                    out[2] = a[8];
                    out[3] = a[12];
                    out[4] = a[1];
                    out[5] = a[5];
                    out[6] = a[9];
                    out[7] = a[13];
                    out[8] = a[2];
                    out[9] = a[6];
                    out[10] = a[10];
                    out[11] = a[14];
                    out[12] = a[3];
                    out[13] = a[7];
                    out[14] = a[11];
                    out[15] = a[15];
                }

                return out;
            }
        };
    </script>
</body>
</html>
```

Key improvements and explanations:

* **Clearer Structure and Comments:** The code now has better organization and comments to explain each section.
* **Error Handling:** Includes checks for WebGL support and shader compilation errors.  This makes debugging much easier.
* **Separate Functions:** The code is broken down into functions for initialization, shader loading, buffer creation, and rendering. This makes the code more modular and readable.
* **Shader Program:** Uses vertex and fragment shaders to render the model. These are defined as strings in the code.
* **Vertex and Fragment Shaders:**  The heart of the 3D rendering.
    * **Vertex Shader (vsSource):**
        * `aVertexPosition`:  An *attribute* representing the position of each vertex in 3D space.  Attributes are data *per vertex*.
        * `uModelViewMatrix`: A *uniform* representing the Model-View matrix.  Uniforms are data *global to the shader*.  The Model-View matrix transforms the model from its local coordinate system to the camera's coordinate system (also known as eye space).  This matrix combines the model's transformations (translation, rotation, scaling) with the camera's position and orientation.
        * `uProjectionMatrix`: A uniform representing the Projection matrix.  This matrix transforms the 3D scene into 2D for display on the screen, simulating perspective.
        * `uNormalMatrix`: A uniform representing the Normal matrix. This is crucial for correct lighting when the model is scaled non-uniformly. The Normal matrix is the inverse transpose of the ModelView matrix.
        * `aVertexNormal`: The direction the vertex is "pointing". Important for lighting.
        * `vNormal`: The normal vector passed to the fragment shader.
        * `vEyePosition`: The vertex position in eye space, used for specular lighting.

    * **Fragment Shader (fsSource):**
        * `vNormal`: The interpolated normal vector received from the vertex shader.  This is interpolated across the face of the triangle.
        * `vEyePosition`: The interpolated vertex position in eye space.
        * `uLightDirection`: A uniform specifying the direction of a directional light source.
        * `uAmbientColor`, `uDiffuseColor`, `uSpecularColor`: Uniforms specifying the colors for ambient, diffuse, and specular lighting components, respectively.
        * `uShininess`: A uniform that controls the size of the specular highlight.
        * **Lighting Calculations:**  The shader calculates the final color of each pixel based on ambient, diffuse, and specular lighting.  The key calculations are:
            * **Ambient:** A base level of light that is present everywhere.
            * **Diffuse:** Light that reflects off the surface, dependent on the angle between the normal vector and the light direction (dot product).
            * **Specular:** A highlight that appears when the light reflects directly into the camera.  Calculated using the `reflect` function and the `pow` function to create a sharp highlight.
* **Geometry Definition:**  The `cubeVertices`, `cubeNormals`, and `cubeIndices` arrays define the 3D model (a cube in this case). The normals are crucial for lighting, and indices allow efficient use of vertices.
* **Buffers:** Uses vertex and index buffers to store the model's geometry on the GPU.  This is much more efficient than sending the data every frame.
* **Model-View and Projection Matrices:** Uses `glmatrix.js` (a *very* basic implementation is included for simplicity) to perform matrix transformations. The Model-View matrix transforms the model, and the Projection matrix applies perspective.  The Normal matrix is calculated and used for proper lighting.
* **Drawing:** The `drawScene` function sets up the WebGL state, binds the buffers, sets the uniforms, and draws the model using `gl.drawElements`.
* **Animation:** The code includes a basic animation loop that rotates the cube.
* **`requestAnimationFrame`:**  Uses `requestAnimationFrame` for smooth and efficient animation.  This tells the browser to call the `render` function before the next repaint.
* **glMatrix (Simplified):**  A simplified version of `glMatrix` is included directly in the code.  In a real project, you would import the full `gl
👁️ Viewed: 9

Comments