WebGL 手把手入门指南(四)图形基本变换

avatar

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

单调的图形总是枯燥无味接下来讲讲如何让图形动起来,想要图形动起来就先要了解怎么实现图形的变换,变换一般有三种,平移、旋转和缩放

平移

先从简单的平移开始:只需要每个顶点加上偏移量就可以实现图形的偏移,所以只需要修改顶掉着色器,每个顶点都是相同的位移所以可以使用uniform变量。具体位移公式如下

  • x' = x + T.x;
  • y' = y + T.y;
  • z' = z + T.z;

const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  uniform vec4 u_Translate;
  void main() {
  	gl_Position = u_Translate + a_Position;
  }
 `;

const u_Translate = gl.getUniformLocation(gl.program, 'u_Translate')
gl.uniform4f(u_Translate, 0.5, 0.5, 0.0, 0);

其中u_Translate + a_Position为每一个分量相加得到一个新的vec4值。

旋转

对于旋转可能会复杂一些,假设我们旋转角度为B,绕z轴旋转,计算过程就省略了,通过计算后会得到如下公式:

  • x' = x cosB - y sinB
  • y' = x sinB + y cosB
  • z' = z
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  uniform float u_CosB;
  uniform float u_SinB;
  void main() {
  	gl_Position.x = a_Position.x * u_CosB - a_Position.y * u_SinB;
  	gl_Position.y = a_Position.x * u_SinB + a_Position.y * u_CosB;
    gl_Position.z = a_Position.z;
    gl_Position.w = 1.0;
  }
 `;

但从着色器上就可以看到非常的复杂了,如果再去实现组合变换就非常麻烦,所以我们需要使用到一个数学工具变换矩阵

变换矩阵

为了得到平移的矩阵我们先来看看平移的矩阵计算:

目标位置 = 平移矩阵 X 原始位置

通过矩阵计算得到

  • x' = ax + by + cz + d
  • y' = ex + fy + gz + h
  • z' = ix + jy + kz + l
  • 1 = mx + ny + oz + p

因为平移只需要在之前的基础上加上新的位移所以得出平移矩阵为:

同理可以得到旋转矩阵为:

缩放矩阵为:

有了这些矩阵后可以直接传递矩阵给着色器,在着色器中做矩阵的叉乘就可以得到变换后的图形了。在WebGL中我们会把这类变换矩阵称之为模型矩阵(model matrix) code

// 顶点着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  // 4x4矩阵
  uniform mat4 u_xformMatrix;
  void main() {
    // 矩阵乘法计算
    gl_Position = u_xformMatrix * a_Position;
  }
`
 
// 弧度制
const radian = Math.PI / 4
const cosB = Math.cos(radian)
const sinB = Math.sin(radian)        
// 旋转矩阵
const xformMatrix = new Float32Array([
  cosB, sinB, 0.0, 0.0,
  -sinB, cosB, 0.0, 0.0,
  0.0, 0.0, 1.0, 0.0,
  0.0, 0.0, 0.0, 1.0
])
        
// 旋转矩阵传输给着色器
const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix')
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix)

在顶点着色器中mat4类型是glsl的4x4矩阵,同时使用*运算符就可以进行矩阵与矢量的叉乘运算。

通过计算出来的正弦余弦值可以补充旋转矩阵,仔细的你会发现旋转矩阵的值的位置好像有猫腻,矩阵有两种储存方式,一种是按行主序一种是按列主序,在WebGL和OpenGL中规定矩阵都是按列主序的。所以传递的数据和我们想象的矩阵长得不太一样。

最后通过uniformMatrix4fv传递数据到着色器。其实还可以再偷懒一点,使用现成的js库来传入位移和旋转角度生成各种矩阵,因为是单纯api使用这里就不赘述了,后续有用到会注释说明。

动画

图形能够变化了,就可以讲讲怎么动起来了。code

// 通过库函数创建矩阵对象
const modelMatrix = new Matrix4()
// 设置初始化旋转角度的矩阵
modelMatrix.setRotate(1, 1, 1, 1)

const u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix')

const tick = () => {
  // 乘以旋转角度
  modelMatrix.rotate(1, 1, 1, 1)
  // 传递新的旋转矩阵
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements)
  // 绘制前需要清空颜色缓冲区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制方式; 开始顶点; 总顶点数;
  gl.drawArrays(gl.TRIANGLE_FAN, 0, 3)
  requestAnimationFrame(tick)
}
tick()

去除复杂的矩阵计算后,可以看到动画就跟我们的Canvas动画的步骤基本上一致