Interactive WebGL Model Viewer JavaScript, WebGL

👤 Sharing: AI
```javascript
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Interactive WebGL Model Viewer</title>
  <style>
    body { margin: 0; }
    canvas { display: block; }
  </style>
</head>
<body>
  <canvas id="glCanvas"></canvas>

  <script>
    // 1. Get the canvas element
    const canvas = document.getElementById('glCanvas');
    const gl = canvas.getContext('webgl'); // Or 'webgl2' if available


    // Check if WebGL is supported
    if (!gl) {
      alert("Your browser doesn't support WebGL.");
    }


    // 2.  Define vertex and fragment shaders (GLSL)

    // Vertex shader:  Responsible for transforming vertex positions and passing data to the fragment shader.
    const vsSource = `
      attribute vec4 aVertexPosition; // Vertex position
      attribute vec3 aVertexNormal;   // Vertex normal

      uniform mat4 uModelViewMatrix;  // Transformation for model
      uniform mat4 uProjectionMatrix; // Camera projection

      varying vec3 vNormal;          // Normal vector (passed to fragment shader)
      varying vec3 vPosition;         // Vertex position in world space

      void main() {
        gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; // Final vertex position after transformations
        vNormal = mat3(uModelViewMatrix) * aVertexNormal; // Transform normal to world space
        vPosition = (uModelViewMatrix * aVertexPosition).xyz; // Transform position to world space
      }
    `;

    // Fragment shader:  Responsible for calculating the color of each fragment (pixel).  Implements lighting.
    const fsSource = `
      precision mediump float; // Set floating point precision

      varying vec3 vNormal;   // Interpolated normal vector from vertex shader
      varying vec3 vPosition; // Interpolated vertex position from vertex shader

      uniform vec3 uLightDirection; // Direction of the light source
      uniform vec4 uColor;        // Color of the object

      void main() {
        vec3 normal = normalize(vNormal);
        vec3 lightDir = normalize(uLightDirection);

        float diffuse = max(dot(normal, lightDir), 0.0); // Diffuse lighting calculation

        vec3 ambient = vec3(0.1, 0.1, 0.1); // Ambient lighting

        vec3 finalColor = uColor.rgb * (ambient + diffuse);

        gl_FragColor = vec4(finalColor, uColor.a); // Set the final fragment color
      }
    `;


    // 3. Shader Program Creation and Linking
    function initShaderProgram(gl, vsSource, fsSource) {
      const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
      const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);

      const shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, vertexShader);
      gl.attachShader(shaderProgram, fragmentShader);
      gl.linkProgram(shaderProgram);

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

      return shaderProgram;
    }

    // Function to create a shader
    function createShader(gl, type, source) {
      const shader = gl.createShader(type);
      gl.shaderSource(shader, source);
      gl.compileShader(shader);

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

      return shader;
    }


    // 4.  Buffer Data (Example: Simple Cube)
    function initBuffers(gl) {
      const positions = [
        // 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 normals = [
        // 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 indices = [
        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
      ];



      // Create position buffer
      const positionBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

      // Create normal buffer
      const normalBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);

      // Create index buffer
      const indexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
      gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);



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


    // 5. Draw the Scene
    function drawScene(gl, programInfo, buffers, deltaTime) {
      gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black
      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); // Clear the canvas before drawing


      // Create projection matrix (camera perspective)
      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();  //from gl-matrix.js

      mat4.perspective(projectionMatrix,
                       fieldOfView,
                       aspect,
                       zNear,
                       zFar);


      // Create model view matrix (where the object is placed in the scene)
      const modelViewMatrix = mat4.create();

      // Rotate the cube (updates based on deltaTime)
      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)

      mat4.translate(modelViewMatrix,     // destination matrix
                     modelViewMatrix,     // matrix to translate
                     [-0.0, 0.0, -6.0]);  // amount to translate


      // 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);


      // Set the shader uniforms

      gl.useProgram(programInfo.program);

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

      // Set the light direction uniform
      gl.uniform3fv(programInfo.uniformLocations.lightDirection, lightDirection);

      // Set the color uniform
      gl.uniform4fv(programInfo.uniformLocations.color, objectColor);

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



    // 6. Initialize and Run
    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'),
        lightDirection: gl.getUniformLocation(shaderProgram, 'uLightDirection'),
        color: gl.getUniformLocation(shaderProgram, 'uColor'),
      },
    };


    const buffers = initBuffers(gl);

    //Rotation parameters
    let cubeRotation = 0.0;
    let deltaTime = 0;
    let lastTime = 0;

    //Light parameters
    const lightDirection = [0.5, 0.7, 0.3];

    //Object color
    const objectColor = [0.8, 0.5, 0.2, 1.0];


    //Resize the canvas
    function resizeCanvas() {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      gl.viewport(0, 0, canvas.width, canvas.height);
    }


    function render(now) {
      now *= 0.001;  // convert to seconds
      deltaTime = now - lastTime;
      lastTime = now;

      cubeRotation += deltaTime;

      drawScene(gl, programInfo, buffers, deltaTime);

      requestAnimationFrame(render);
    }


    function main(){
      resizeCanvas(); //Initial resize

      //Call resize function on window size change
      window.addEventListener('resize', resizeCanvas);


      requestAnimationFrame(render);
    }


    //Load gl-matrix
    var script = document.createElement('script');
    script.src = "https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js";
    script.onload = function () {
      main();  // Call main after gl-matrix is loaded
    };
    document.head.appendChild(script);


  </script>
</body>
</html>
```

Key improvements and explanations:

* **Clearer Structure and Comments:**  The code is now organized with numbered sections (1-6) and detailed comments explaining each step. This makes it much easier to understand the purpose of each code block.
* **Error Handling:** Includes checks for WebGL support and shader compilation errors. This is crucial for robust applications.
* **Shader Program Creation:** The `initShaderProgram` function is used to create and link the vertex and fragment shaders.  It also includes error checking for shader compilation.
* **Buffer Initialization (Cube Example):** The `initBuffers` function sets up the vertex positions, normals, and indices for a simple cube. Normals are now correctly defined. Importantly, it *returns* the buffer information so that the `drawScene` function can use it.
* **gl-matrix Library:**  Uses the `gl-matrix` library for matrix operations (projection, model-view transformations).  Crucially, it now loads `gl-matrix` asynchronously and calls `main()` only after it's loaded. This avoids errors where `gl-matrix` functions are called before the library is ready.  Added a `script` element to the `head` to load the gl-matrix library.
* **Transformation Matrices:**  The `drawScene` function constructs the projection and model-view matrices.  The model-view matrix now includes both rotation *and* translation.  Rotation is dependent on `deltaTime` to make the animation framerate-independent.
* **Uniforms and Attributes:** Correctly binds vertex positions, normals, and indices to the shader program attributes. It also sets the uniform variables (projection matrix, model-view matrix, light direction, and color).
* **Lighting:** Implements basic diffuse and ambient lighting using normals and a light direction. This significantly improves the visual quality.
* **Rendering Loop:** The `render` function handles the animation loop. It calls `drawScene` repeatedly, updates the cube's rotation, and requests the next animation frame.
* **`deltaTime` for Smooth Animation:** Calculates `deltaTime` (the time elapsed since the last frame) and uses it to update the rotation. This makes the animation speed consistent regardless of the frame rate.
* **Color:** Adds a uniform for setting the object color.
* **Light Direction:** Adds a uniform for setting the light direction.
* **Resize Canvas:** Added resizeCanvas() to properly resize the canvas when the window is resized.
* **Clear Errors:** Fixed various small errors in the code, particularly related to buffer binding and attribute access.
* **Index Buffer:**  Uses an index buffer for the cube, which is the standard and more efficient way to define 3D models in WebGL.
* **Clear Separation of Concerns:** The code is broken down into logical functions, making it more modular and easier to maintain.

How to Run:

1. **Save as HTML:** Save the code as an HTML file (e.g., `webgl_viewer.html`).
2. **Open in Browser:** Open the HTML file in a web browser that supports WebGL.  Most modern browsers do.
3. **Check Console:** If you see a blank screen or encounter errors, open your browser's developer console (usually by pressing F12) and look for error messages.

This revised example provides a solid foundation for building more complex interactive WebGL model viewers.  You can extend it by:

* **Loading Models:** Integrate a model loading library (e.g., `OBJLoader.js` from Three.js or `GLTFLoader.js` for glTF models).
* **User Interaction:** Add controls for rotating, zooming, and panning the camera.
* **More Advanced Shading:** Implement more sophisticated lighting models (e.g., specular highlights, shadows, environment mapping).
* **Textures:** Add support for textures to add more detail to the models.
* **Animations:**  Load and play animations associated with the models.
👁️ Viewed: 8

Comments