WebGL 矩阵和向量运算源码(空间变换和灯光效果)

57 阅读1分钟

代码是 VUE3 ,里面包含了空间变换(矩阵运算)和灯光效果(向量运算)的示例代码,可以帮助理解前面三节文章的知识点。

<template>
  <div>
    使用矩阵进行三维空间的位移、旋转、缩放变换,灯光效果。原理与二维类似。
    聚光灯实例中会出现黑色,可能是计算出现undefined的原因,要专门处理之后才可以消除。
  </div>
  <canvas id="canvas"></canvas>
  <a-tabs style="width: 300px; float: right">
    <a-tab-pane key="1" tab="模型参数">
      X 方向平移:{{ transform.tx }}
      <a-slider class="slider" v-model:value="transform.tx" :step="1" :min="-300" :max="300" />
      Y 方向平移:{{ transform.ty }}
      <a-slider class="slider" v-model:value="transform.ty" :step="1" :min="-300" :max="300" />
      Z 方向平移:{{ transform.tz }}
      <a-slider class="slider" v-model:value="transform.tz" :step="1" :min="-300" :max="300" />
      绕 X 轴旋转:{{ transform.rx }}
      <a-slider class="slider" v-model:value="transform.rx" :step="1" :min="0" :max="360" />
      绕 Y 轴旋转:{{ transform.ry }}
      <a-slider class="slider" v-model:value="transform.ry" :step="1" :min="0" :max="360" />
      绕 Z 轴旋转:{{ transform.rz }}
      <a-slider class="slider" v-model:value="transform.rz" :step="1" :min="0" :max="360" />
      X 方向缩放:{{ transform.sx }}
      <a-slider class="slider" v-model:value="transform.sx" :step="1" :min="1" :max="10" />
      Y 方向缩放:{{ transform.sy }}
      <a-slider class="slider" v-model:value="transform.sy" :step="1" :min="1" :max="10" />
      Z 方向缩放:{{ transform.sz }}
      <a-slider class="slider" v-model:value="transform.sz" :step="1" :min="1" :max="10" />
    </a-tab-pane>
    <a-tab-pane key="3" tab="相机参数">
      相机 X 坐标:{{ transform.cameraX }}
      <a-slider class="slider" v-model:value="transform.cameraX" :step="1" :min="-300" :max="300" />
      相机 Y 坐标:{{ transform.cameraY }}
      <a-slider class="slider" v-model:value="transform.cameraY" :step="1" :min="-300" :max="300" />
      相机 Z 坐标:{{ transform.cameraZ }}
      <a-slider class="slider" v-model:value="transform.cameraZ" :step="1" :min="-300" :max="300" />
      目标点 X 坐标:{{ transform.targetX }}
      <a-slider class="slider" v-model:value="transform.targetX" :step="1" :min="-300" :max="300" />
      目标点 Y 坐标:{{ transform.targetY }}
      <a-slider class="slider" v-model:value="transform.targetY" :step="1" :min="-300" :max="300" />
      目标点 Z 坐标:{{ transform.targetZ }}
      <a-slider class="slider" v-model:value="transform.targetZ" :step="1" :min="-300" :max="300" />
    </a-tab-pane>
    <a-tab-pane key="2" tab="灯光参数">
      灯光 X 坐标:{{ transform.lightX }}
      <a-slider class="slider" v-model:value="transform.lightX" :step="1" :min="-300" :max="300" />
      灯光 Y 坐标:{{ transform.lightY }}
      <a-slider class="slider" v-model:value="transform.lightY" :step="1" :min="-300" :max="300" />
      灯光 Z 坐标:{{ transform.lightZ }}
      <a-slider class="slider" v-model:value="transform.lightZ" :step="1" :min="-300" :max="300" />
      聚光灯内径:{{ transform.spotLightR1 }}
      <a-slider class="slider" v-model:value="transform.spotLightR1" :step="1" :min="0" :max="360" />
      聚光灯外径:{{ transform.spotLightR2 }}
      <a-slider class="slider" v-model:value="transform.spotLightR2" :step="1" :min="0" :max="360" />
    </a-tab-pane>
  </a-tabs>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { M4 } from '../../utils/m4';
// 变换参数
let transform = ref({
  tx: 0,
  ty: 0,
  tz: -10,
  rx: 0,
  ry: 10,
  rz: 0,
  sx: 1.0,
  sy: 1.0,
  sz: 1.0,
  cameraX: 100,
  cameraY: 150,
  cameraZ: 200,
  targetX: 0,
  targetY: 35,
  targetZ: 0,
  lightX: 20,
  lightY: 30,
  lightZ: 60,
  spotLightR1: 10,
  spotLightR2: 20,
});
let gl: WebGLRenderingContext;
let program: WebGLProgram;
onMounted(async () => {
  // 准备环境
  gl = createWebGL();
  // 创建着色器
  program = createProgram(gl)!;
  render();
});

/** 从 canvas 获取 gl */
function createWebGL(): WebGLRenderingContext {
  let canvas = document.getElementById('canvas') as HTMLCanvasElement;
  let gl = canvas.getContext('webgl2') as WebGLRenderingContext;
  canvas.width = canvas.clientWidth;
  canvas.height = canvas.clientHeight;
  gl.viewport(0, 0, canvas.width, canvas.height);
  return gl;
}

/** 创建着色器 */
function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined {
  let shader: WebGLShader = gl.createShader(type)!; // 创建着色器对象
  gl.shaderSource(shader, source); // 提供数据源
  gl.compileShader(shader); // 编译 -> 生成着色器
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

/** 创建着色器程序 */
function createProgram(gl: WebGLRenderingContext): WebGLProgram | undefined {
  let vertexShaderSource = `
      attribute vec4 a_position; // 模型局部坐标系下的顶点(这里的顶点数据已经是世界坐标系了)
      attribute vec3 a_color; // 模型各个面的颜色
      attribute vec3 a_normal; // 模型局部坐标系下的法向量(这里的法向量数据已经是世界坐标系了)
      
      uniform mat4 u_mvpMatrix;
      uniform mat4 u_mMatrix;
      uniform mat4 u_wMatrix;
      uniform mat4 u_nMatrix;
      uniform vec3 u_pointLightPosition;
      uniform vec3 u_cameraPosition;

      varying vec3 v_normal; // 世界坐标下法向量,用于和平行光相乘
      varying vec3 v_surfaceToLight; // 世界坐标下点光源方向,用于计算光照
      varying vec3 v_surfaceToCamera; // 世界坐标下点视线方向,用于计算光照
      varying vec3 v_color;

      void main() {
        mat4 matrix = u_mvpMatrix * u_wMatrix;
        gl_Position = matrix * a_position;

        v_normal = mat3(u_nMatrix) * a_normal; // 重新计算世界坐标的下法向量
        vec3 surfacePosition = (u_wMatrix * a_position).xyz; // 变换之后的顶点的世界坐标
        v_surfaceToLight = u_pointLightPosition - surfacePosition; // 计算世界坐标下每个顶点对应的点光源方向
        v_surfaceToCamera = u_cameraPosition - surfacePosition; // 计算世界坐标下每个顶点对应的视线方向

        v_color = a_color;
      }
	`;

  let fragmentShaderSource = `
      precision mediump float;
      varying vec3 v_color;
      varying vec3 v_normal; // 片元法向量
      varying vec3 v_surfaceToCamera; // 视线方向
      varying vec3 v_surfaceToLight; // 点光源光线方向

      uniform vec3 u_parallelLightDirection; // 平行光的方向,传进来的就是归一化向量
      uniform float u_spotLightAngleLimitIn; // 点光的范围(聚光大小)
      uniform float u_spotLightAngleLimitOut; // 点光的范围(聚光大小)
      uniform vec3 u_spotLightDirection; // 聚光方向 
      uniform vec3 v_ambientLightColor; // 环境光颜色
      uniform vec3 u_pointLightColor; // 点光颜色
      uniform vec3 u_specularColor; // 高光颜色
      uniform float u_shininess; // 高光颜强度

      void main() {
        vec3 normal = normalize(v_normal);

        // // 平行光
        // float parallelLight = dot(normal, u_parallelLightDirection); // 计算平行光照
        // vec3 parallel = parallelLight * v_color; // 将颜色rgb部分和平行光相乘
        // vec3 ambientColor = v_ambientLightColor * v_color; // 环境光
        // gl_FragColor = vec4(parallel + ambientColor, 1);

        // // 点光
        // vec3 surfaceToLightDirection = normalize(v_surfaceToLight); // 点光方向
        // vec3 surfaceToCameraDirection = normalize(v_surfaceToCamera); // 视线方向
        // vec3 halfVector = normalize(surfaceToLightDirection + surfaceToCameraDirection); // 视线和点光方向夹角的一半
        // float pointLight = dot(normal, surfaceToLightDirection); // 计算点光照
        // float specular = 0.0;
        // if (pointLight > 0.0) {
        //   specular = pow(dot(normal, halfVector), u_shininess); // 高光强度(值越大亮斑越小)
        // }
        // // 颜色合成
        // vec3 spotColor = pointLight * u_pointLightColor * v_color; // 将颜色rgb部分和点光、点光颜色相乘
        // vec3 specularColor = specular * u_specularColor; // 加上高光强度和颜色
        // vec3 ambientColor = v_ambientLightColor * v_color; // 环境光
        // gl_FragColor = vec4(spotColor + specularColor + ambientColor, 1);

        // 聚光
        vec3 surfaceToLightDirection = normalize(v_surfaceToLight); // 点光方向
        vec3 surfaceToCameraDirection = normalize(v_surfaceToCamera); // 视线方向
        vec3 halfVector = normalize(surfaceToLightDirection + surfaceToCameraDirection); // 视线和点光方向夹角的一半
        float dotFromDirection = dot(surfaceToLightDirection, -u_spotLightDirection);
        float inLight = smoothstep(u_spotLightAngleLimitOut, u_spotLightAngleLimitIn, dotFromDirection);
        float spotLight = inLight * dot(normal, surfaceToLightDirection);
        float specular = inLight * pow(dot(normal, halfVector), u_shininess);    
        // 颜色合成    
        vec3 spotColor = spotLight * v_color; // 将颜色rgb部分和聚光相乘
        vec3 specularColor = specular * u_specularColor; // 加上高光强度和颜色
        vec3 ambientColor = v_ambientLightColor * v_color; // 环境光
        gl_FragColor = vec4(spotColor + specularColor + ambientColor, 1);
      }
  `;

  // 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
  let vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource)!;
  let fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource)!;
  let program: WebGLProgram = gl.createProgram()!;
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);

  let success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    gl.useProgram(program);
    return program;
  } else {
    console.log(gl.getProgramInfoLog(program));
    gl.deleteProgram(program);
  }
}

/** 渲染 */
let zDeg = 0;
function render() {
  zDeg += 0.5;

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the canvas AND the depth buffer.
  gl.enable(gl.CULL_FACE); // 开启背面剔除
  gl.enable(gl.DEPTH_TEST); // 开启深度检测

  let positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
  let colorAttributeLocation = gl.getAttribLocation(program, 'a_color');
  let normalAttributeLocation = gl.getAttribLocation(program, 'a_normal');
  let ambientLightColorLocation = gl.getUniformLocation(program, 'v_ambientLightColor');
  let parallelLightDirectionLocation = gl.getUniformLocation(program, 'u_parallelLightDirection');
  let pointLightPositionLocation = gl.getUniformLocation(program, 'u_pointLightPosition');
  let pointLightColorLocation = gl.getUniformLocation(program, 'u_pointLightColor');
  let spotLightAngleLimitInLocation = gl.getUniformLocation(program, 'u_spotLightAngleLimitIn');
  let spotLightAngleLimitOutLocation = gl.getUniformLocation(program, 'u_spotLightAngleLimitOut');
  let spotLightDirectionLocation = gl.getUniformLocation(program, 'u_spotLightDirection');
  let shininessLocation = gl.getUniformLocation(program, 'u_shininess');
  let specularColorLocation = gl.getUniformLocation(program, 'u_specularColor');
  let cameraPositionLocation = gl.getUniformLocation(program, 'u_cameraPosition');
  let mMatrixLocation = gl.getUniformLocation(program, 'u_mMatrix');
  let mvpMatrixLocation = gl.getUniformLocation(program, 'u_mvpMatrix');
  let wMatrixLocation = gl.getUniformLocation(program, 'u_wMatrix');
  let nMatrixLocation = gl.getUniformLocation(program, 'u_nMatrix');

  // 设置模型数据
  setPosition(gl); // 顶点
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
  setColor(gl); // 颜色
  gl.enableVertexAttribArray(colorAttributeLocation);
  gl.vertexAttribPointer(colorAttributeLocation, 3, gl.UNSIGNED_BYTE, true, 0, 0);
  setNormal(gl); // 法线,是固定的,如果要变,也要和矩阵相乘
  gl.enableVertexAttribArray(normalAttributeLocation);
  gl.vertexAttribPointer(normalAttributeLocation, 3, gl.FLOAT, true, 0, 0);

  //#region 设置光照数据
  // 设置环境光颜色
  gl.uniform3fv(ambientLightColorLocation, new Float32Array([0.2, 0.2, 0.2]));
  // 设置平行光线,只有一个方向
  gl.uniform3fv(parallelLightDirectionLocation, M4.normalize([0.5, 0.7, 1]));
  // 设置点光(只需要设置点光源的位置,然后在着色器中计算每个顶点上光的方向)
  gl.uniform3fv(pointLightColorLocation, [1, 0.6, 0.6]);
  gl.uniform3fv(pointLightPositionLocation, [transform.value.lightX, transform.value.lightY, transform.value.lightZ]);
  gl.uniform1f(shininessLocation, 35);
  gl.uniform1f(spotLightAngleLimitInLocation, Math.cos((transform.value.spotLightR1 * Math.PI) / 180));
  gl.uniform1f(spotLightAngleLimitOutLocation, Math.cos((transform.value.spotLightR2 * Math.PI) / 180));
  gl.uniform3fv(specularColorLocation, [1, 0.6, 0.6]);
  //#endregion

  // M 模型矩阵,这里的坐标已经是世界坐标,不需要变换了
  let mMatrix = M4.identity();
  gl.uniformMatrix4fv(mMatrixLocation, false, mMatrix);

  // V 视图矩阵(相机在100,150,200),目标在(0,35,0)
  let vMatrix = M4.identity();
  let cameraPosition = [transform.value.cameraX, transform.value.cameraY, transform.value.cameraZ];
  let targetPosition = [transform.value.targetX, transform.value.targetY, transform.value.targetZ];
  vMatrix = M4.inverse(M4.lookAt(cameraPosition, targetPosition, [0, 1, 0]));
  // 设置相机的位置
  gl.uniform3fv(cameraPositionLocation, cameraPosition);
  gl.uniform3fv(spotLightDirectionLocation, [-vMatrix[8], -vMatrix[9], -vMatrix[10]]);

  // P 投影矩阵
  // 正交投影,长宽是容器大小,深度是6000(大点,不容易被裁减)
  // let pMatrix = M4.orthographic(-gl.canvas.width / 2, gl.canvas.width / 2, -gl.canvas.height / 2, gl.canvas.height / 2, -3000, 3000);
  // 透视投影,90度视场角,深度是6000(大点,不容易被裁减)
  let pMatrix = M4.perspective((60 * Math.PI) / 180, gl.canvas.width / gl.canvas.width, 1, 6000);

  // 计算最终矩阵,注意是左乘PMV顺序,满足乘法结合律,不满足交换律
  let mvpMatrix = M4.multiply(pMatrix, M4.multiply(vMatrix, mMatrix));
  gl.uniformMatrix4fv(mvpMatrixLocation, false, mvpMatrix);
  //#endregion

  //#region W 世界矩阵:
  let wMatrix = M4.identity();
  wMatrix = M4.translate(wMatrix, transform.value.tx, transform.value.ty, transform.value.tz); // 平移矩阵
  // wMatrix = M4.xRotate(wMatrix, (zDeg * Math.PI) / 180); // 旋转矩阵(自动旋转动画)
  // wMatrix = M4.yRotate(wMatrix, (zDeg * Math.PI) / 180); // 旋转矩阵(自动旋转动画)
  // wMatrix = M4.zRotate(wMatrix, (zDeg * Math.PI) / 180); // 旋转矩阵(自动旋转动画)
  wMatrix = M4.xRotate(wMatrix, (transform.value.rx * Math.PI) / 180); // 旋转矩阵
  wMatrix = M4.yRotate(wMatrix, (transform.value.ry * Math.PI) / 180); // 旋转矩阵
  wMatrix = M4.zRotate(wMatrix, (transform.value.rz * Math.PI) / 180); // 旋转矩阵
  wMatrix = M4.scale(wMatrix, transform.value.sx, transform.value.sy, transform.value.sz); // 缩放矩阵
  gl.uniformMatrix4fv(wMatrixLocation, false, wMatrix);
  //#endregion

  // 设置法向矩阵
  let nMatrix = M4.transpose(M4.inverse(M4.multiply(mMatrix, wMatrix)));
  gl.uniformMatrix4fv(nMatrixLocation, false, nMatrix);

  // 16 个矩形(每个矩形 2 个三角形,每个三角形 3 个点)
  gl.drawArrays(gl.TRIANGLES, 0, 16 * 2 * 3);

  requestAnimationFrame(render);
}

const setPosition = (gl: WebGLRenderingContext) => {
  // 除了Z方向的面逆时针,其他方向(+-X、+-Y、-Z)都要顺时针
  // prettier-ignore
  let positions = [
    -0.8,  0.8, 0.1, // F前面-1(是逆时针)
    -0.8, -0.8, 0.1,
    -0.5, -0.8, 0.1,
    -0.8,  0.8, 0.1, // F前面-2(是逆时针)
    -0.5, -0.8, 0.1,
    -0.5,  0.8, 0.1,
    -0.5,  0.8, 0.1, // F前面-3(是逆时针)
    -0.5,  0.6, 0.1,
     0.0,  0.6, 0.1,
    -0.5,  0.8, 0.1, // F前面-4(是逆时针)
     0.0,  0.6, 0.1,
     0.0,  0.8, 0.1,
    -0.5,  0.1, 0.1, // F前面-5(是逆时针)
    -0.5, -0.1, 0.1,
    -0.1, -0.1, 0.1,
    -0.1, -0.1, 0.1, // F前面-6(是逆时针)
    -0.1,  0.1, 0.1,
    -0.5,  0.1, 0.1,
    //#region 侧面
    -0.8,  0.8,  0.1, // F顶面-1(是顺时针)
     0.0,  0.8, -0.1,
    -0.8,  0.8, -0.1,
    -0.8,  0.8,  0.1, // F顶面-2(是顺时针)
     0.0,  0.8,  0.1,
     0.0,  0.8, -0.1,
    -0.8,  0.8, -0.1, // F左面-1(是顺时针)
    -0.8, -0.8, -0.1,
    -0.8,  0.8,  0.1,
    -0.8, -0.8, -0.1, // F左面-2(是顺时针)
    -0.8, -0.8,  0.1,
    -0.8,  0.8,  0.1,
    -0.8, -0.8,  0.1, // F面-1(是顺时针)
    -0.5, -0.8, -0.1,
    -0.5, -0.8,  0.1,
    -0.8, -0.8,  0.1, // F面-2(是顺时针)
    -0.8, -0.8, -0.1,
    -0.5, -0.8, -0.1,
    //#endregion
    -0.8,  0.8, -0.1, // F后面-1(是顺时针)
    -0.5, -0.8, -0.1,
    -0.8, -0.8, -0.1,
    -0.8,  0.8, -0.1, // F后面-2(是顺时针)
    -0.5,  0.8, -0.1,
    -0.5, -0.8, -0.1,
    -0.5,  0.8, -0.1, // F后面-3(是顺时针)
     0.0,  0.6, -0.1,
    -0.5,  0.6, -0.1,
    -0.5,  0.8, -0.1, // F后面-4(是顺时针)
     0.0,  0.8, -0.1,
     0.0,  0.6, -0.1,
    -0.5,  0.1, -0.1, // F后面-5(是顺时针)
    -0.1, -0.1, -0.1,
    -0.5, -0.1, -0.1,
    -0.5,  0.1, -0.1, // F后面-6(是顺时针)
    -0.1,  0.1, -0.1,
    -0.1, -0.1, -0.1,
    // 右侧还有几个面
  ];
  // 使用教程里的数据,倒立的 F,Z 值都大于 0,后面会“扶正”
  positions = [
    // left column front
    0, 0, 0, 0, 150, 0, 30, 0, 0, 0, 150, 0, 30, 150, 0, 30, 0, 0,
    // top rung front
    30, 0, 0, 30, 30, 0, 100, 0, 0, 30, 30, 0, 100, 30, 0, 100, 0, 0,
    // middle rung front
    30, 60, 0, 30, 90, 0, 67, 60, 0, 30, 90, 0, 67, 90, 0, 67, 60, 0,
    // left column back
    0, 0, 30, 30, 0, 30, 0, 150, 30, 0, 150, 30, 30, 0, 30, 30, 150, 30,
    // top rung back
    30, 0, 30, 100, 0, 30, 30, 30, 30, 30, 30, 30, 100, 0, 30, 100, 30, 30,
    // middle rung back
    30, 60, 30, 67, 60, 30, 30, 90, 30, 30, 90, 30, 67, 60, 30, 67, 90, 30,
    // top
    0, 0, 0, 100, 0, 0, 100, 0, 30, 0, 0, 0, 100, 0, 30, 0, 0, 30,
    // top rung right
    100, 0, 0, 100, 30, 0, 100, 30, 30, 100, 0, 0, 100, 30, 30, 100, 0, 30,
    // under top rung
    30, 30, 0, 30, 30, 30, 100, 30, 30, 30, 30, 0, 100, 30, 30, 100, 30, 0,
    // between top rung and middle
    30, 30, 0, 30, 60, 30, 30, 30, 30, 30, 30, 0, 30, 60, 0, 30, 60, 30,
    // top of middle rung
    30, 60, 0, 67, 60, 30, 30, 60, 30, 30, 60, 0, 67, 60, 0, 67, 60, 30,
    // right of middle rung
    67, 60, 0, 67, 90, 30, 67, 60, 30, 67, 60, 0, 67, 90, 0, 67, 90, 30,
    // bottom of middle rung.
    30, 90, 0, 30, 90, 30, 67, 90, 30, 30, 90, 0, 67, 90, 30, 67, 90, 0,
    // right of bottom
    30, 90, 0, 30, 150, 30, 30, 90, 30, 30, 90, 0, 30, 150, 0, 30, 150, 30,
    // bottom
    0, 150, 0, 0, 150, 30, 30, 150, 30, 0, 150, 0, 30, 150, 30, 30, 150, 0,
    // left side
    0, 0, 0, 0, 0, 30, 0, 150, 30, 0, 0, 0, 0, 150, 30, 0, 150, 0,
  ];

  // 把 F 扶正,中心放到 0 0 0 位置
  let matrix = M4.xRotation(Math.PI); // 反转倒立的 F
  matrix = M4.translate(matrix, -50, -75, -15); // 移动到中心位置
  for (let i = 0; i < positions.length; i += 3) {
    // 3个一步长,依次转换每一个点的坐标
    let vector = M4.transformPoint(matrix, [positions[i + 0], positions[i + 1], positions[i + 2], 1]);
    positions[i + 0] = vector[0];
    positions[i + 1] = vector[1];
    positions[i + 2] = vector[2];
  }

  let positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
};

const setColor = (gl: WebGLRenderingContext) => {
  // prettier-ignore
  let colors = [
    100,  0, 0, // F前面-1
    100,  0, 0,
    100,  0, 0,
     0, 100, 0, // F前面-2
     0, 100, 0,
     0, 100, 0,
    100, 20, 0, // F前面-3
    100, 20, 0,
    100, 20, 0,
    100, 60, 0, // F前面-4
    100, 60, 0,
    100, 60, 0,
    100, 80, 0, // F前面-5
    100, 80, 0,
    100, 80, 0,
    100, 0,  0, // F前面-6
    100, 0,  0,
    100, 0,  0,
    //#region 侧面
     0, 100, 0, // 侧顶面-1
     0, 100, 0,
     0, 100, 0,
    130, 10, 0, // 侧顶面-2
    130, 10, 0,
    130, 10, 0,
     0, 100, 0, // 侧左面-1
     0, 100, 0,
     0, 100, 0,
    60, 100, 0, // 侧左面-2
    60, 100, 0,
    60, 100, 0,
    0,100, 100, // 侧下面-1
    0,100, 100,
    0,100, 100,
    60, 100, 0, // 侧下面-2
    60, 100, 0,
    60, 100, 0,
    //#endregion
     0, 0, 100,  // F后面-1
     0, 0, 100,
     0, 0, 100,
    80, 0, 100,  // F后面-2
    80, 0, 100,
    80, 0, 100,
    20, 0, 100,  // F后面-3
    20, 0, 100,
    20, 0, 100,
    40, 0, 100,  // F后面-4
    40, 0, 100,
    40, 0, 100,
     0, 0, 100,  // F后面-5
     0, 0, 100,
     0, 0, 100,
    60, 0, 100,  // F后面-6
    60, 0, 100,
    60, 0, 100,
  ];
  // 使用 教程里的数据
  colors = [
    // left column front
    200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120,
    // top rung front
    200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120,
    // middle rung front
    200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120, 200, 70, 120,
    // left column back
    80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200,
    // top rung back
    80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200,
    // middle rung back
    80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200, 80, 70, 200,
    // top
    70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210, 70, 200, 210,
    // top rung right
    200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70, 200, 200, 70,
    // under top rung
    210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70,
    // between top rung and middle
    210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70, 210, 160, 70,
    // top of middle rung
    70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210, 70, 180, 210,
    // right of middle rung
    100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210, 100, 70, 210,
    // bottom of middle rung.
    76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100, 76, 210, 100,
    // right of bottom
    140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80, 140, 210, 80,
    // bottom
    90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110, 90, 130, 110,
    // left side
    160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220, 160, 160, 220,
  ];

  let colorsBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array(colors), gl.STATIC_DRAW);
};

const setNormal = (gl: WebGLRenderingContext) => {
  let normals = [
    // 正面左竖
    0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
    // 正面上横
    0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
    // 正面中横
    0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
    // 背面左竖
    0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
    // 背面上横
    0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
    // 背面中横
    0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
    // 顶部
    0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
    // 上横右面
    1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
    // 上横下面
    0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
    // 上横和中横之间
    1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
    // 中横上面
    0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
    // 中横右面
    1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
    // 中横底面
    0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
    // 底部右侧
    1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
    // 底面
    0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0,
    // 左面
    -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
  ];

  let normalBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
};
</script>
<style>
#canvas {
  width: 600px;
  height: 600px;
  border: 1px solid orange;
}
</style>

这是WebGL 系列的入门文章,免费订阅,如有帮助请点赞收藏,纰漏之处欢迎指正!

qrcode_for_gh_3695c3ae18f4_258.jpg

也欢迎关注公众号交流知识哇😄