WebGL学习(十四)切换着色器

790 阅读5分钟

1. 概述

之前的学习都是在单个着色器上进行的,如果你要绘制不同的图形,必然需要不同的着色器来处理。好在webgl能够随时切换着色器,还记得useProgram这个方法吗,这个就是切换着色器的关键。

2. 实践

现在我们有两个着色器画两个立方体,一个透明的一个不透明的。

本质上画两个和画一个是一样的,只是在画另一个的时候需要切换程序program。所以我会将通用的部分简单封装一下(随便封装的没有参考价值哈)。

// 封装部分操作
import { ReadonlyVec3, glMatrix, mat4 } from "gl-matrix"
import { getModelMat } from "./utils"

class WebGLLib {
  glContext: WebGLRenderingContext
  private programMap: Map<string, WebGLProgram> = new Map()
  // 当前使用的program
  program: WebGLProgram

  private uniformMap: Map<string, WebGLUniformLocation> = new Map()

  private indexBufferLength: number
  constructor(public canvas: HTMLCanvasElement) {
    this.glContext = canvas.getContext('webgl')
  }
  /**
   * 创建program
   */
  createProgram(key: string, vertexCode: string, fragmentCode: string) {
    const gl = this.glContext
    // 1.创建顶点、片元着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER)
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
    // 2.装载着色器代码
    gl.shaderSource(vertexShader, vertexCode)
    gl.shaderSource(fragmentShader, fragmentCode)
    // 3.编译着色器
    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);
    // 4.创建WebGLProgram对象
    const program = gl.createProgram();
    // 5.添加着色器
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)
    // 6.链接程序
    gl.linkProgram(program);
    this.programMap.set(key, program)
  }
  /**
   * 使用program
   */
  use(key: string) {
    const program = this.programMap.get(key)
    if(program) {
      // 7.WebGLProgram对象添加到渲染状态中
      this.glContext.useProgram(program)
      this.program = program
    }
  }
  /**
   * 绑定顶点缓冲
   */

  bindVertexBuffer(
    bufferData: BufferSource, attribName: string,
    size: number, type: number = WebGLRenderingContext.FLOAT, stride: number = 0, offset: number = 0
  ) {

    const gl = this.glContext
    const program = this.program
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, bufferData, gl.STATIC_DRAW)
    const attrib = gl.getAttribLocation(program, attribName)
    gl.vertexAttribPointer(attrib, size, type, false, stride, offset)
    gl.enableVertexAttribArray(attrib)
  }
  /**
   * 绑定索引
   */
  bindIndexBuffer(bufferData: Uint8Array) {
    const gl = this.glContext
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, bufferData, gl.STATIC_DRAW);
    this.indexBufferLength = bufferData.length
  }
  /**
   * 设置uniform值
   */
  setUniformValue(uniformName: string, value: GLfloat[]) {
    const cacheUniform = this.uniformMap.get(uniformName)
    const gl = this.glContext
    const program = this.program
    const uniform = cacheUniform ?? gl.getUniformLocation(program, uniformName)
    this.uniformMap.set(uniformName, uniform)
    const key = `uniform${value.length}fv` as 'uniform1fv'
    gl[key](uniform, value)
  }
  setUniformMatrixValue(uniformName: string, value: mat4) {
    const cacheUniform = this.uniformMap.get(uniformName)
    const gl = this.glContext
    const program = this.program
    const uniform = cacheUniform ?? gl.getUniformLocation(program, uniformName)
    this.uniformMap.set(uniformName, uniform)
    gl.uniformMatrix4fv(uniform, false, value)
  }

  /**
   * 开启功能
   */
  enables(caps: number[]) {
    caps.forEach(c => this.glContext.enable(c))
  }

  /**
   * 获取mvp矩阵
   */
  getMVP({
    perspective = {},
    lookAt = {},
    model = {}
  }: GetMVPParams) {
    const {
      fov = 10,
      aspect = this.canvas.width / this.canvas.height,
      near = 1,
      far = 1000
    } = perspective
    const {
      eye = [10, 10, 30],
      center = [0, 0, 0],
      up = [0, 1, 0],
    } = lookAt
    const {
      translate,
      rotate,
      scale
    } = model
    const perspectiveMat = mat4.perspective(mat4.create(), glMatrix.toRadian(fov), aspect, near, far)
    const viewMat = mat4.lookAt(mat4.create(), eye, center, up)
    const vpMat = mat4.multiply(mat4.create(), perspectiveMat, viewMat)
    const modelMat = getModelMat(translate, rotate, scale)
    const mvpMat = mat4.multiply(mat4.create(), vpMat, modelMat)
    return  {
      perspectiveMat,
      viewMat,
      modelMat,
      mvpMat
    }
  }

  /**
   * 绘制
   */
  draw(clear: boolean = true) {
    const gl = this.glContext
    if(clear) {
      gl.clearColor(0, 0, 0, 1)
      gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT)
    }
    gl.drawElements(gl.TRIANGLES, this.indexBufferLength, gl.UNSIGNED_BYTE, 0);
  }
}
export default WebGLLib

interface GetMVPParams {
  perspective?: {
    fov?: number,
    aspect?: number,
    near?: number,
    far?: number
  },
  lookAt?: {
    eye?: ReadonlyVec3
    center?: ReadonlyVec3
    up?: ReadonlyVec3
  },
  model?: {
    translate?: ReadonlyVec3,
    rotate?: ReadonlyVec3,
    scale?: ReadonlyVec3
  }
}

就是把常用的模板代码放进通用方法里面了。

// 使用
const Lib = new WebGLLib(document.getElementById('webgl') as HTMLCanvasElement)

Lib.createProgram('normalCube', vertexCode, fragmentCode)
Lib.createProgram('transparentCube', vertexCode, fragmentCode)

// 绘制普通立方体
Lib.use('normalCube');
// 绑定顶点缓冲
Lib.bindVertexBuffer()
// 绑定颜色缓冲
Lib.bindVertexBuffer()
// .....
// 绘制透明立方体
Lib.use('normalCube');
// .....

上面简化代码已经揭示了切换着色器的方法,就是切换program进行操作。

msedge_lxRaBnmiMO.gif

3. 完整代码

存一下档

// 顶点着色器

// mvp矩阵
uniform mat4 mvpMat;
// model矩阵
uniform mat4 modelMat;
// 顶点位置
attribute vec4 pos;
// 顶点颜色
attribute vec4 color;
// 初始位置法线向量
attribute vec4 originalNormal;

varying vec4 _color;
varying vec4 _originalNormal;
varying vec4 _vertexPosition;

void main(){
  // 绘制立方体顶点
  gl_Position = mvpMat * pos;
  gl_PointSize = 10.0;
  // 计算顶点世界坐标
  _vertexPosition = modelMat * pos;
  // 法向量是存储在顶点缓冲区的,所以只能间接传递给片元着色器
  _originalNormal = originalNormal;
  _color = color;
}
// 片元着色器
precision mediump float;
// 光源颜色
uniform vec3 lightColor;
// 光源方向
uniform vec3 lightDirection;
// 环境光颜色
uniform vec3 ambientColor;
// 法向量变换矩阵 也就是逆转置矩阵
uniform mat4 normalMat;
varying vec4 _color;
varying vec4 _originalNormal;
varying vec4 _vertexPosition;

void main(){
  // 计算该顶点的光线方向
  vec3 vertexLightDirection = normalize(lightDirection - vec3(_vertexPosition));
  // 计算变换后的法向量
  vec3 normal = normalize(vec3(normalMat * _originalNormal));
  // 计算反射颜色
  float cos = max(dot(vertexLightDirection, normal), 0.0);
  vec3 diffuse = lightColor * _color.rgb * cos;
  // 计算环境反射光
  vec3 ambient = ambientColor * _color.rgb;
  vec4 objectFaceColor =  vec4(diffuse + ambient,  _color.a);
  gl_FragColor = objectFaceColor;
}
import { ReadonlyVec3, glMatrix, mat4 } from "gl-matrix"
import { getModelMat } from "./utils"

class WebGLLib {
  currentKey: string
  glContext: WebGLRenderingContext
  private programMap: Map<string, WebGLProgram> = new Map()
  // 当前使用的program
  program: WebGLProgram

  private uniformMap: Map<string, WebGLUniformLocation> = new Map()

  private indexBufferLength: number
  constructor(public canvas: HTMLCanvasElement) {
    this.glContext = canvas.getContext('webgl')
  }
  /**
   * 创建program
   */
  createProgram(key: string, vertexCode: string, fragmentCode: string) {
    const gl = this.glContext
    // 1.创建顶点、片元着色器
    const vertexShader = gl.createShader(gl.VERTEX_SHADER)
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
    // 2.装载着色器代码
    gl.shaderSource(vertexShader, vertexCode)
    gl.shaderSource(fragmentShader, fragmentCode)
    // 3.编译着色器
    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);
    // 4.创建WebGLProgram对象
    const program = gl.createProgram();
    // 5.添加着色器
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)
    // 6.链接程序
    gl.linkProgram(program);
    this.programMap.set(key, program)
  }
  /**
   * 使用program
   */
  use(key: string) {
    const program = this.programMap.get(key)
    if(program) {
      // 7.WebGLProgram对象添加到渲染状态中
      this.glContext.useProgram(program)
      this.program = program
      this.currentKey = key
    }
  }
  /**
   * 绑定顶点缓冲
   */

  bindVertexBuffer(
    bufferData: BufferSource, attribName: string,
    size: number, type: number = WebGLRenderingContext.FLOAT, stride: number = 0, offset: number = 0
  ) {

    const gl = this.glContext
    const program = this.program
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, bufferData, gl.STATIC_DRAW)
    const attrib = gl.getAttribLocation(program, attribName)
    gl.vertexAttribPointer(attrib, size, type, false, stride, offset)
    gl.enableVertexAttribArray(attrib)
  }
  /**
   * 绑定索引
   */
  bindIndexBuffer(bufferData: Uint8Array) {
    const gl = this.glContext
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, bufferData, gl.STATIC_DRAW);
    this.indexBufferLength = bufferData.length
  }
  /**
   * 设置uniform值
   */
  setUniformValue(uniformName: string, value: GLfloat[]) {
    const cacheUniform = this.uniformMap.get(`${this.currentKey}-${uniformName}`)
    const gl = this.glContext
    const program = this.program
    const uniform = cacheUniform ?? gl.getUniformLocation(program, uniformName)
    this.uniformMap.set(`${this.currentKey}-${uniformName}`, uniform)
    const key = `uniform${value.length}fv` as 'uniform1fv'
    gl[key](uniform, value)
  }
  setUniformMatrixValue(uniformName: string, value: mat4) {
    const cacheUniform = this.uniformMap.get(`${this.currentKey}-${uniformName}`)
    const gl = this.glContext
    const program = this.program
    const uniform = cacheUniform ?? gl.getUniformLocation(program, uniformName)
    this.uniformMap.set(`${this.currentKey}-${uniformName}`, uniform)
    gl.uniformMatrix4fv(uniform, false, value)
  }

  /**
   * 开启功能
   */
  enables(caps: number[]) {
    caps.forEach(c => this.glContext.enable(c))
  }

  /**
   * 获取mvp矩阵
   */
  getMVP({
    perspective = {},
    lookAt = {},
    model = {}
  }: GetMVPParams) {
    const {
      fov = 10,
      aspect = this.canvas.width / this.canvas.height,
      near = 1,
      far = 1000
    } = perspective
    const {
      eye = [10, 10, 30],
      center = [0, 0, 0],
      up = [0, 1, 0],
    } = lookAt
    const {
      translate,
      rotate,
      scale
    } = model
    const perspectiveMat = mat4.perspective(mat4.create(), glMatrix.toRadian(fov), aspect, near, far)
    const viewMat = mat4.lookAt(mat4.create(), eye, center, up)
    const vpMat = mat4.multiply(mat4.create(), perspectiveMat, viewMat)
    const modelMat = getModelMat(translate, rotate, scale)
    const mvpMat = mat4.multiply(mat4.create(), vpMat, modelMat)
    return  {
      perspectiveMat,
      viewMat,
      modelMat,
      mvpMat
    }
  }

  /**
   * 绘制
   */
  draw(clear: boolean = true) {
    const gl = this.glContext
    if(clear) {
      gl.clearColor(0, 0, 0, 1)
      gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT)
    }
    gl.drawElements(gl.TRIANGLES, this.indexBufferLength, gl.UNSIGNED_BYTE, 0);
  }
}
export default WebGLLib

interface GetMVPParams {
  perspective?: {
    fov?: number,
    aspect?: number,
    near?: number,
    far?: number
  },
  lookAt?: {
    eye?: ReadonlyVec3
    center?: ReadonlyVec3
    up?: ReadonlyVec3
  },
  model?: {
    translate?: ReadonlyVec3,
    rotate?: ReadonlyVec3,
    scale?: ReadonlyVec3
  }
}

// utils
import { ReadonlyVec3, mat4, quat } from "gl-matrix"
const axis = ['X', 'Y', 'Z']
/**
 * 获取model矩阵
 * @param translate [x, y, z]平移
 * @param rotate 旋转角度
 */
export const getModelMat = (translate: ReadonlyVec3 = [0, 0, 0], rotate: ReadonlyVec3 = [0, 0, 0], scale: ReadonlyVec3 = [1, 1, 1]) => {
  const routateQuat = quat.create()
  rotate.forEach((r, index) => {
    let rad = r * Math.PI / 180
    // @ts-ignore
    quat[`rotate${axis[index]}`]?.(routateQuat, routateQuat, rad)
  })
  const modelMat = mat4.fromRotationTranslationScale(mat4.create(), routateQuat, translate, scale)
  return modelMat
}
// 主程序
import vertexCode from './vertex.vert'
import fragmentCode from './fragment.frag'
import { mat4, vec3, vec4, glMatrix } from 'gl-matrix'
import { getModelMat } from './utils'
import WebGLLib from './lib'
const output = document.getElementById('output') as HTMLDivElement
const Lib = new WebGLLib(document.getElementById('webgl') as HTMLCanvasElement)

Lib.createProgram('normalCube', vertexCode, fragmentCode)
Lib.createProgram('transparentCube', vertexCode, fragmentCode)


const rotate: vec3 = [0, 0, 0]
// 绘制普通立方体
const drawNormalCube = () => {
  Lib.use('normalCube');
  const {
    modelMat,
    mvpMat
  } = Lib.getMVP({perspective: {fov: 20},model: {rotate, translate: [-3, 0, 0]}})
  Lib.enables([WebGLRenderingContext.DEPTH_TEST])
  // 绑定顶点缓冲
  Lib.bindVertexBuffer(
    //    v6----- v5
    //   /|      /|
    //  v1------v0|
    //  | |     | |
    //  | |v7---|-|v4
    //  |/      |/
    //  v2------v3
    new Float32Array([
      1.0, 1.0, 1.0,1.0,  -1.0, 1.0, 1.0,1.0,  -1.0,-1.0, 1.0,1.0,   1.0,-1.0, 1.0,1.0,    // v0-v1-v2-v3 front
      1.0, 1.0, 1.0,1.0,   1.0,-1.0, 1.0,1.0,   1.0,-1.0,-1.0,1.0,   1.0, 1.0,-1.0,1.0,    // v0-v3-v4-v5 right
      1.0, 1.0, 1.0,1.0,   1.0, 1.0,-1.0,1.0,  -1.0, 1.0,-1.0,1.0,  -1.0, 1.0, 1.0,1.0,    // v0-v5-v6-v1 up
     -1.0, 1.0, 1.0,1.0,  -1.0, 1.0,-1.0,1.0,  -1.0,-1.0,-1.0,1.0,  -1.0,-1.0, 1.0,1.0,    // v1-v6-v7-v2 left
     -1.0,-1.0,-1.0,1.0,   1.0,-1.0,-1.0,1.0,   1.0,-1.0, 1.0,1.0,  -1.0,-1.0, 1.0,1.0,    // v7-v4-v3-v2 down
      1.0,-1.0,-1.0,1.0,  -1.0,-1.0,-1.0,1.0,  -1.0, 1.0,-1.0,1.0,   1.0, 1.0,-1.0,1.0,    // v4-v7-v6-v5 back
    ]),
    'pos',
    4
  )
  // 绑定颜色缓冲
  Lib.bindVertexBuffer(
    new Float32Array([
      0.4, 0.4, 1.0, 1.0,   0.4, 0.4, 1.0, 1.0,   0.4, 0.4, 1.0, 1.0,   0.4, 0.4, 1.0, 1.0,  // v0-v1-v2-v3 前(blue)
      0.4, 1.0, 0.4, 1.0,   0.4, 1.0, 0.4, 1.0,   0.4, 1.0, 0.4, 1.0,   0.4, 1.0, 0.4, 1.0,  // v0-v3-v4-v5 右(green)
      1.0, 0.4, 0.4, 1.0,   1.0, 0.4, 0.4, 1.0,   1.0, 0.4, 0.4, 1.0,   1.0, 0.4, 0.4, 1.0,  // v0-v5-v6-v1 上(red)
      1.0, 1.0, 0.4, 1.0,   1.0, 1.0, 0.4, 1.0,   1.0, 1.0, 0.4, 1.0,   1.0, 1.0, 0.4, 1.0,  // v1-v6-v7-v2 左
      1.0, 1.0, 1.0, 1.0,   1.0, 1.0, 1.0, 1.0,   1.0, 1.0, 1.0, 1.0,   1.0, 1.0, 1.0, 1.0,  // v7-v4-v3-v2 下
      0.4, 1.0, 1.0, 1.0,   0.4, 1.0, 1.0, 1.0,   0.4, 1.0, 1.0, 1.0,   0.4, 1.0, 1.0, 1.0,  // v4-v7-v6-v5 后
    ]),
    'color',
    4
  )
  // 绑定法线向量缓冲
  Lib.bindVertexBuffer(
    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
    ]),
    'originalNormal',
    3
  )
  // 绑定索引
  Lib.bindIndexBuffer(
    new Uint8Array([
      0, 1, 2,      0, 2, 3,   // 前
      4, 5, 6,      4, 6, 7,   // 右
      8, 9, 10,     8, 10, 11,   // 上
      12, 13, 14,   12, 14, 15,   // 左
      16, 17, 18,   16, 18, 19,   // 下
      20, 21, 22,   20, 22, 23,    // 后
    ])
  )
  // 设置光线
  Lib.setUniformValue('lightColor', [1, 1, 1])
  // 光线方向
  Lib.setUniformValue('lightDirection', [ -4, 4, 5])
  // 设置环境光颜色
  Lib.setUniformValue('ambientColor', [0.2, 0.2, 0.2])
  // 设置mvp矩阵
  Lib.setUniformMatrixValue('mvpMat', mvpMat)
  Lib.setUniformMatrixValue('modelMat', modelMat)
  // modelMat的逆转置
  Lib.setUniformMatrixValue('normalMat', mat4.transpose(mat4.create(), mat4.invert(mat4.create(), modelMat)))

  Lib.draw()
}

// 绘制透明立方体
const drawTransparentCube = () => {
  Lib.use('transparentCube');
  const {
    modelMat,
    mvpMat
  } = Lib.getMVP({perspective: {fov: 20},model: {rotate, translate: [4, 0, 0]}})
  // 开启混合 深度测试
  Lib.enables([WebGLRenderingContext.DEPTH_TEST, WebGLRenderingContext.BLEND])
  // 设置混合函数
  Lib.glContext.blendFunc(WebGLRenderingContext.SRC_ALPHA,WebGLRenderingContext.ONE_MINUS_SRC_ALPHA)
  // 绑定顶点缓冲
  Lib.bindVertexBuffer(
    //    v6----- v5
    //   /|      /|
    //  v1------v0|
    //  | |     | |
    //  | |v7---|-|v4
    //  |/      |/
    //  v2------v3
    new Float32Array([
      1.0, 1.0, 1.0,1.0,  -1.0, 1.0, 1.0,1.0,  -1.0,-1.0, 1.0,1.0,   1.0,-1.0, 1.0,1.0,    // v0-v1-v2-v3 front
      1.0, 1.0, 1.0,1.0,   1.0,-1.0, 1.0,1.0,   1.0,-1.0,-1.0,1.0,   1.0, 1.0,-1.0,1.0,    // v0-v3-v4-v5 right
      1.0, 1.0, 1.0,1.0,   1.0, 1.0,-1.0,1.0,  -1.0, 1.0,-1.0,1.0,  -1.0, 1.0, 1.0,1.0,    // v0-v5-v6-v1 up
     -1.0, 1.0, 1.0,1.0,  -1.0, 1.0,-1.0,1.0,  -1.0,-1.0,-1.0,1.0,  -1.0,-1.0, 1.0,1.0,    // v1-v6-v7-v2 left
     -1.0,-1.0,-1.0,1.0,   1.0,-1.0,-1.0,1.0,   1.0,-1.0, 1.0,1.0,  -1.0,-1.0, 1.0,1.0,    // v7-v4-v3-v2 down
      1.0,-1.0,-1.0,1.0,  -1.0,-1.0,-1.0,1.0,  -1.0, 1.0,-1.0,1.0,   1.0, 1.0,-1.0,1.0,    // v4-v7-v6-v5 back
    ]),
    'pos',
    4
  )
  // 绑定颜色缓冲
  Lib.bindVertexBuffer(
    new Float32Array([
      0.4, 0.4, 1.0, 0.5,   0.4, 0.4, 1.0, 0.5,   0.4, 0.4, 1.0, 0.5,   0.4, 0.4, 1.0, 0.5,  // v0-v1-v2-v3 前(blue)
      0.4, 1.0, 0.4, 0.5,   0.4, 1.0, 0.4, 0.5,   0.4, 1.0, 0.4, 0.5,   0.4, 1.0, 0.4, 0.5,  // v0-v3-v4-v5 右(green)
      1.0, 0.4, 0.4, 0.5,   1.0, 0.4, 0.4, 0.5,   1.0, 0.4, 0.4, 0.5,   1.0, 0.4, 0.4, 0.5,  // v0-v5-v6-v1 上(red)
      1.0, 1.0, 0.4, 0.5,   1.0, 1.0, 0.4, 0.5,   1.0, 1.0, 0.4, 0.5,   1.0, 1.0, 0.4, 0.5,  // v1-v6-v7-v2 左
      1.0, 1.0, 1.0, 0.5,   1.0, 1.0, 1.0, 0.5,   1.0, 1.0, 1.0, 0.5,   1.0, 1.0, 1.0, 0.5,  // v7-v4-v3-v2 下
      0.4, 1.0, 1.0, 0.5,   0.4, 1.0, 1.0, 0.5,   0.4, 1.0, 1.0, 0.5,   0.4, 1.0, 1.0, 0.5,  // v4-v7-v6-v5 后
    ]),
    'color',
    4
  )
  // 绑定法线向量缓冲
  Lib.bindVertexBuffer(
    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
    ]),
    'originalNormal',
    3
  )
  // 绑定索引
  Lib.bindIndexBuffer(
    new Uint8Array([
      4, 5, 6,      4, 6, 7,   // 右
      12, 13, 14,   12, 14, 15,   // 左
      16, 17, 18,   16, 18, 19,   // 下
      20, 21, 22,   20, 22, 23,    // 后
      8, 9, 10,     8, 10, 11,   // 上
      0, 1, 2,      0, 2, 3,   // 前
    ])
  )
  // 设置光线
  Lib.setUniformValue('lightColor', [1, 1, 1])
  // 光线方向
  Lib.setUniformValue('lightDirection', [ -4, 4, 5])
  // 设置环境光颜色
  Lib.setUniformValue('ambientColor', [0.2, 0.2, 0.2])
  // 设置mvp矩阵
  Lib.setUniformMatrixValue('mvpMat', mvpMat)
  Lib.setUniformMatrixValue('modelMat', modelMat)
  // modelMat的逆转置
  Lib.setUniformMatrixValue('normalMat', mat4.transpose(mat4.create(), mat4.invert(mat4.create(), modelMat)))
  Lib.glContext.depthMask(false)
  Lib.draw(false)
  Lib.glContext.depthMask(true)
}

drawNormalCube()
drawTransparentCube()


initEventHandlers()

function initEventHandlers() {
  let dragging = false;
  let lastX = -1, lastY = -1;
  const canvas = Lib.canvas
  window.oncontextmenu = (e) => {
    e.preventDefault()
  }
  canvas.addEventListener('mousedown', function(e) {
    const {
      clientX,
      clientY,
      button
    } = e
    // 右键才能拖动
    if(button !== 2) return
    var rect = this.getBoundingClientRect();
    if (rect.left <= clientX && clientX < rect.right && rect.top <= clientY && clientY < rect.bottom) {
      lastX = clientX; lastY = clientY;
      dragging = true;
    }
  })

  canvas.onmouseup = function(ev) { dragging = false;  };
  canvas.addEventListener('mousemove', function(e) {
    const {
      clientX,
      clientY
    } = e
    if (dragging) {
      var factor = 400 / canvas.width; // The rotation ratio
      var dx = factor * (clientX - lastX);
      var dy = factor * (clientY - lastY);
      rotate[0] = rotate[0] + dy
      rotate[1] = rotate[1] + dx;
      drawNormalCube()
      drawTransparentCube()
    }
    lastX = clientX, lastY = clientY;
  })
}

4. 另外

改进下操作,可以单独控制每个立方体。

需要复习一下WebGL学习(九)选中物体

// 处理事件的时候需要改进一下
function initEventHandlers() {
   // ....
  canvas.addEventListener('mousedown', function(e) {
    const {
      clientX,
      clientY,
      button
    } = e
    // 右键才能拖动
    if(button !== 2) return
    var rect = this.getBoundingClientRect();
    if (rect.left <= clientX && clientX < rect.right && rect.top <= clientY && clientY < rect.bottom) {
      lastX = clientX; lastY = clientY;
      dragging = true;

      const {
        width,
        left,
        bottom,
      } = Lib.canvas.getBoundingClientRect()
      const scale = Lib.canvas.width / width
      const res = new Uint8Array(4)
      const canvasX = clientX - left
      const canvasY = bottom - clientY

      // 判断是否点击
      Lib.use('normalCube')
      Lib.setUniformValue('clicked', [1])
      drawNormalCube()
      drawTransparentCube(false)
      Lib.use('normalCube')
      Lib.glContext.readPixels(canvasX * scale, canvasY * scale, 1, 1, WebGLRenderingContext.RGBA, WebGLRenderingContext.UNSIGNED_BYTE, res)
      Lib.setUniformValue('clicked', [0])
      drawNormalCube()
      drawTransparentCube(false)
      Lib.clickedMap.set('normalCube', false)
      if(res.slice(0, 3).every(i => !i)) {
        Lib.clickedMap.set('normalCube', true)
      }


      Lib.use('transparentCube')
      Lib.setUniformValue('clicked', [1])
      drawNormalCube()
      drawTransparentCube(false)
      Lib.glContext.readPixels(canvasX * scale, canvasY * scale, 1, 1, WebGLRenderingContext.RGBA, WebGLRenderingContext.UNSIGNED_BYTE, res)
      Lib.setUniformValue('clicked', [0])
      drawNormalCube()
      drawTransparentCube(false)
      Lib.clickedMap.set('transparentCube', false)
      if(res.slice(0, 3).every(i => !i)) {
        Lib.clickedMap.set('transparentCube', true)
      }
    }
  })

  canvas.onmouseup = function(ev) { dragging = false;  };
  canvas.addEventListener('mousemove', function(e) {
    const {
      clientX,
      clientY
    } = e
    if (dragging) {
      var factor = 400 / canvas.width; // The rotation ratio
      var dx = factor * (clientX - lastX);
      var dy = factor * (clientY - lastY);
       // 分别设置旋转
      if(Lib.clickedMap.get('normalCube')) {
        normalCubeRotate[0] = normalCubeRotate[0] + dy
        normalCubeRotate[1] = normalCubeRotate[1] + dx;
      }
      if(Lib.clickedMap.get('transparentCube')) {
        transparentCubeRotate[0] = transparentCubeRotate[0] + dy
        transparentCubeRotate[1] = transparentCubeRotate[1] + dx;
      }
      drawNormalCube()
      drawTransparentCube(false)
    }
    lastX = clientX, lastY = clientY;
  })
}

msedge_GMjxQReM72.gif