WebGL点光源立方体

本例程选自《WebGL 编程指南》(WebGL Programing Guide)第8章,简单展示了点光源处理物体表面的方法。

效果


源码参考

main.vert:

/**
 * main.vert
 * Based on 'WebGL Programming Guide. Kouichi Matsuda. Rodger Lea' - ch08
 *
 * @project PointLightedCubeAnimation
 * @last_modified 2018-03-08
 */

attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec4 a_Normal;

uniform mat4 u_MvpMatrix;
uniform mat4 u_ModelMatrix;    // Model matrix
uniform mat4 u_NormalMatrix;   // Coordinate transformation matrix of the normal
uniform vec3 u_LightColor;     // Light color
uniform vec3 u_LightPosition;  // Position of the light source
uniform vec3 u_AmbientLight;   // Ambient light color
varying vec4 v_Color;

void main() {
  gl_Position = u_MvpMatrix * a_Position;

  // Recalculate the normal based on the model matrix and make its length 1.
  vec3 normal = normalize(vec3(u_NormalMatrix * a_Normal));

  // Calculate world coordinate of vertex
  vec4 vertexPosition = u_ModelMatrix * a_Position;

  // Calculate the light direction and make it 1.0 in length
  vec3 lightDirection = normalize(u_LightPosition - vec3(vertexPosition));

  // Calculate the dot product of the normal and light direction
  float nDotL = max(dot(normal, lightDirection), 0.0);

  // Calculate the color due to diffuse reflection
  vec3 diffuse = u_LightColor * a_Color.rgb * nDotL;

  // Calculate the color due to ambient reflection
  vec3 ambient = u_AmbientLight * a_Color.rgb;

  // Add the surface colors due to diffuse reflection and ambient reflection
  v_Color = vec4(diffuse + ambient, a_Color.a);
}

main.frag:

/**
 * main.frag
 * Based on 'WebGL Programming Guide. Kouichi Matsuda. Rodger Lea' - ch08
 * 
 * @project PointLightedCubeAnimation
 * @last_modified 2018-03-08
 */

#ifdef GL_ES
  precision mediump float;
#endif

varying vec4 v_Color;

void main() {
  gl_FragColor = v_Color;
}

main.js:

/**
 * main.js
 * Based on 'WebGL Programming Guide. Kouichi Matsuda. Rodger Lea' - ch08
 *
 * @project PointLightedCubeAnimation
 * @last_modified 2018-03-08
 */

const main = (function (window, document) {
  // Global

  // Vertex shader program
  let VSHADER_SOURCE = null;
  
  // Fragment shader program
  let FSHADER_SOURCE = null;
  
  let canvas = null;
  let gl = null;

  // Rotation angle (degrees/second)
  const ANGLE_STEP = 30.0;
  
  // Last time that this function was called
  let g_last = Date.now();
  
  const animate = function (angle) {
    // Calculate the elapsed time
    const now = Date.now();
    const elapsed = now - g_last;
    g_last = now;
 
    // Update the current rotation angle (adjusted by the elapsed time)
    let newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
    return newAngle %= 360;
  }

  // Run program
  const start = function () {
    // Initialize shaders
    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
      console.error('Failed to intialize shaders.');
      return;
    }

    // Set the vertex coordinates, the color and the normal
    const n = initVertexBuffers();
    if (n < 0) {
      console.error('Failed to set the vertex information');
      return;
    }

    // Set the clear color and enable the depth test
    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.enable(gl.DEPTH_TEST);

    // Get the storage locations of uniform variables and so on
    const u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
    const u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
    const u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
    const u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
    const u_LightPosition = gl.getUniformLocation(gl.program, 'u_LightPosition');
    const u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
    if (!u_MvpMatrix || !u_NormalMatrix || !u_LightColor || !u_LightPosition || !u_AmbientLight) { 
      console.error('Failed to get the storage location');
      return;
    }

    const vpMatrix = new Matrix4();   // View projection matrix
    vpMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100);
    vpMatrix.lookAt(6, 6, 14, 0, 0, 0, 0, 1, 0);

    // Set the light color (white)
    gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);
    // Set the light direction (in the world coordinate)
    gl.uniform3f(u_LightPosition, 2.3, 4.0, 3.5);
    // Set the ambient light
    gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

    let currentAngle = 0.0;  // Current rotation angle
    const modelMatrix = new Matrix4();  // Model matrix
    const mvpMatrix = new Matrix4();    // Model view projection matrix
    const normalMatrix = new Matrix4(); // Transformation matrix for normals

    const tick = function () {
      currentAngle = animate(currentAngle);  // Update the rotation angle

      // Calculate the model matrix
      modelMatrix.setRotate(currentAngle, 0, 1, 0); // Rotate around the y-axis
      // Pass the model matrix to u_ModelMatrix
      gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);

      // Pass the model view projection matrix to u_MvpMatrix
      mvpMatrix.set(vpMatrix).multiply(modelMatrix);
      gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

      // Pass the matrix to transform the normal based on the model matrix to u_NormalMatrix
      normalMatrix.setInverseOf(modelMatrix);
      normalMatrix.transpose();
      gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

      // Clear color and depth buffer
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

      // Draw the cube
      gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
      // gl.drawElements(gl.LINE_LOOP, n, gl.UNSIGNED_BYTE, 0);
      // gl.drawElements(gl.LINE_STRIP, n, gl.UNSIGNED_BYTE, 0);
      // gl.drawElements(gl.LINES, n, gl.UNSIGNED_BYTE, 0);

      requestAnimationFrame(tick, canvas); // Request that the browser ?calls tick
    };
    tick();
  };

  const initVertexBuffers = function () {
    // Create a cube
    //    v6----- v5
    //   /|      /|
    //  v1------v0|
    //  | |     | |
    //  | |v7---|-|v4
    //  |/      |/
    //  v2------v3
    // Coordinates
    const vertices = new Float32Array([
       2.0, 2.0, 2.0,  -2.0, 2.0, 2.0,  -2.0,-2.0, 2.0,   2.0,-2.0, 2.0, // v0-v1-v2-v3 front
       2.0, 2.0, 2.0,   2.0,-2.0, 2.0,   2.0,-2.0,-2.0,   2.0, 2.0,-2.0, // v0-v3-v4-v5 right
       2.0, 2.0, 2.0,   2.0, 2.0,-2.0,  -2.0, 2.0,-2.0,  -2.0, 2.0, 2.0, // v0-v5-v6-v1 up
      -2.0, 2.0, 2.0,  -2.0, 2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0,-2.0, 2.0, // v1-v6-v7-v2 left
      -2.0,-2.0,-2.0,   2.0,-2.0,-2.0,   2.0,-2.0, 2.0,  -2.0,-2.0, 2.0, // v7-v4-v3-v2 down
       2.0,-2.0,-2.0,  -2.0,-2.0,-2.0,  -2.0, 2.0,-2.0,   2.0, 2.0,-2.0  // v4-v7-v6-v5 back
    ]);
  
    // Colors
    const colors = new Float32Array([
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1,     // v0-v1-v2-v3 front
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1,     // v0-v3-v4-v5 right
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1,     // v0-v5-v6-v1 up
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1,     // v1-v6-v7-v2 left
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1,     // v7-v4-v3-v2 down
      1, 1, 1,   1, 1, 1,   1, 1, 1,  1, 1, 1      // v4-v7-v6-v5 back
   ]);
  
    // Normal
    const normals = new Float32Array([
      0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,  // v0-v1-v2-v3 front
      1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,  // v0-v3-v4-v5 right
      0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,  // v0-v5-v6-v1 up
     -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  -1.0, 0.0, 0.0,  // v1-v6-v7-v2 left
      0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,   0.0,-1.0, 0.0,  // v7-v4-v3-v2 down
      0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0,   0.0, 0.0,-1.0   // v4-v7-v6-v5 back
    ]);
  
    // Indices of the vertices
    // A cube with 6 planes
    const indices = new Uint8Array([
      0,  1, 2,   0, 2, 3,    // front
      4,  5, 6,   4, 6, 7,    // right
      8,  9,10,   8,10,11,    // up
      12,13,14,  12,14,15,    // left
      16,17,18,  16,18,19,    // down
      20,21,22,  20,22,23     // back
    ]);
  
    // A cube with edges only
    // const indices = new Uint8Array([
    //   0, 1,   1, 2,    2, 3,    // front
    //   4, 5,   5, 6,    6, 7,    // right
    //   8, 9,   9, 10,  10,11,    // up
    //  12,13,   13,14,  14,15,    // left
    //  16,17,   17,18,  18,19,    // down
    //  20,21,   21,22,  22,23     // back
    // ]);
  
    // Write the vertex property to buffers (coordinates, colors and normals)
    if (!initArrayBuffer('a_Position', vertices, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer('a_Color', colors, 3, gl.FLOAT)) return -1;
    if (!initArrayBuffer('a_Normal', normals, 3, gl.FLOAT)) return -1;
  
    // Unbind the buffer object
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
  
    // Write the indices to the buffer object
    const indexBuffer = gl.createBuffer();
    if (!indexBuffer) {
      console.error('Failed to create the buffer object');
      return false;
    }
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  
    return indices.length;
  }
  
  const initArrayBuffer = function (attribute, data, num, type) {
    // Create a buffer object
    const buffer = gl.createBuffer();
    if (!buffer) {
      console.error('Failed to create the buffer object');
      return false;
    }

    // Write date into the buffer object
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

    // Assign the buffer object to the attribute variable
    const a_attribute = gl.getAttribLocation(gl.program, attribute);
    if (a_attribute < 0) {
      console.error(`Failed to get the storage location of ${attribute}`);
      return false;
    }
    gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
    // Enable the assignment of the buffer object to the attribute variable
    gl.enableVertexAttribArray(a_attribute);
  
    return true;
  }

  // Load sharders by ajax (.frag, .vert)
  const LoadShaderFile = function (fileName, shader) {
    const request = new XMLHttpRequest;
    const onLoadShader = function (fileString, type) {
      VSHADER_SOURCE = (type == gl.VERTEX_SHADER) ? fileString : VSHADER_SOURCE;
      FSHADER_SOURCE = (type == gl.FRAGMENT_SHADER) ? fileString : FSHADER_SOURCE;
      
      // Start rendering when file loaded
      VSHADER_SOURCE && FSHADER_SOURCE && start();
    }
    
    request.onreadystatechange = () => (
      request.readyState === 4 && request.status !== 404 &&
          onLoadShader(request.responseText, shader));
    
    request.open('GET', fileName, true);
    request.send();
  };

  // Main

  const main = function () {
    // Retrieve <canvas> element
    canvas = document.getElementById('webgl');
  
    // Get the rendering context for WebGL
    gl = getWebGLContext(canvas);
    if (!gl) {
      throw Error('Failed to get the rendering context for WebGL');
      return;
    }
  
    // Load GLSL shaders from files
    LoadShaderFile('./glsl/main.vert', gl.VERTEX_SHADER);
    LoadShaderFile('./glsl/main.frag', gl.FRAGMENT_SHADER);
  };
  
  return main;
})(window, document)

参考:

  1. WebGL 编程指南. Kouichi Matsuda. Rodger Lea
  2. webglfundamentals.org

作者: YanWen

Web 开发者

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google photo

You are commenting using your Google account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s