翼度科技»论坛 编程开发 JavaScript 查看内容

webgl 系列 —— 变换矩阵和动画

3

主题

3

帖子

9

积分

新手上路

Rank: 1

积分
9
其他章节请看:
webgl 系列
变换矩阵和动画

动画就是不停地将某个东西变换(transform)。例如将三角形不停地旋转就是一个动画
CSS transform 类似,变换有三种形式:平移、缩放和旋转。
简单的变换用普通表达式容易实现,如果事情复杂,比如旋转后平移,这时就可以使用变换矩阵。
普通表达式

平移

比如要平移一个三角形,只需要将三个顶点移动相同的距离即可(这是一个逐顶点操作)

用二维向量表示,就像这样:[x1, y1] + [tx1, ty2] = [x2, y2]
比如要实现如下效果:

前面我们已经讲过三角形了,这里不再冗余,改动的核心代码如下:
  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. +uniform vec4 u_Translation;
  4. void main() {
  5. -  gl_Position = a_Position;
  6. +  gl_Position = a_Position + u_Translation;
  7.    gl_PointSize = 10.0;
  8. }
  9. `
  10. function main() {
  11.      gl.clearColor(0, 0, 0, 1);
  12.      gl.clear(gl.COLOR_BUFFER_BIT);
  13. +    const u_Translation = gl.getUniformLocation(gl.program, 'u_Translation');
  14. +    if (!u_Translation) {
  15. +        return;
  16. +    }
  17. +    gl.uniform4f(u_Translation, 0.5, 0.5, 0, 0.0);
  18. +
复制代码
a_Position 和 u_Translation 都是 vec4 类型,使用 + 号,两个矢量(也称向量)对应的分量会被同时相加(矢量相加是着色器语言的特性之一)。就像这样:

缩放

以一个点为例,比如要将 A 点缩放到 B 点,乘以一个系数就好,系数小于1表示缩小,系数大于1表示放大:

用二维向量表示,就像这样:k[x1, y1] = [x2, y2]
旋转

比如要将 p 点旋转 β,推导出来的公式如下:

变换矩阵

概念

变换矩阵(非常适合操作计算机图形)是数学线性代数中的一个概念。请看下图:

将点从 S 旋转到 T,新坐标(m, n)可以用普通表达式表示,同样可以用变换矩阵来表示(旧点 * 变换矩阵 = 新点)
变换矩阵和向量相乘有一个规则,并会得到一个新的向量。
Tip:webgl 中的一个点,在坐标系中就相当于一个向量
在 webgl 中变换矩阵和向量相乘的规则如下:

:牢记公式:变换矩阵 * 向量 会生成一个新的向量;顺序不同结果也不同,例如:向量 * 变换矩阵
旋转

将旋转的普通表达式转为变换矩阵:

四维矩阵

为什么要用四维矩阵?
因为三维矩阵矩阵不够用,比如将 (0,0,0) 移动到 (1, 0, 0),用三维矩阵是表示不出来的,而四维却可以。请看下图:

平移

将平移的普通表达式转为变换矩阵:

缩放

将缩放的普通表达式转为变换矩阵:

手动矩阵

为了更好的理解矩阵。我们先不使用矩阵库,直接通过 js 来使用矩阵实现变换。
矩阵颠倒

js 中没有矩阵数据类型,这里用数组表示。
比如要表示如下一个平移矩阵:
  1. 1, 0, 0, Tx
  2. 0, 1, 0, Ty
  3. 0, 0, 1, Tz
  4. 0, 0, 0, 1
复制代码
数组就是这样:
  1. const matrix = [
  2.   1, 0, 0, Tx,
  3.   0, 1, 0, Ty,
  4.   0, 0, 1, Tz,
  5.   0, 0, 0, 1,
  6. ]
复制代码
而要表示如上这个变换矩阵,在 webgl 中就得将数组颠倒:行变成列。
所以最后就得这么写:
  1. const matrix = [
  2.   1, 0, 0, 0,
  3.   0, 1, 0, 0,
  4.   0, 0, 1, 0,
  5.   Tx, Ty, Tz, 1,
  6. ]
复制代码
Tip: 对于缩放,颠倒后和颠倒前是相同的。
平移

需求:将三角形向右上角偏移。
效果:

前面我们已经学会画三角形,笔者在此基础上改动如下代码:
  1. const VSHADER_SOURCE = `
  2. +// mat4 是一种4维矩阵
  3. +uniform mat4 u_xformMatrix;
  4. void main() {
  5. -  gl_Position = a_Position ;
  6. +  // 注:必须是 "变换矩阵 * 向量",不可是 "向量 * 变换矩阵"
  7. +  gl_Position = u_xformMatrix * a_Position ;
  8.    gl_PointSize = 10.0;
  9. }
  10. `
  11. function main() {
  12.      initVertexBuffers(gl, vertices)
  13. +    变换(gl)
  14.     gl.drawArrays(gl.TRIANGLES, 0, vertices.vertexNumber);
  15. }
  16. +function 变换(gl){
  17. +    const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
  18. +    if (!u_xformMatrix) {
  19. +        console.log('Failed to get the storage location of u_xformMatrix');
  20. +        return;
  21. +    }
  22. +    // 四维矩阵
  23. +    const [Tx, Ty, Tz] = [0.5, 0.5, 0];
  24. +    // webgl 中矩阵的行和列是要颠倒的,比如要传一个 A 矩阵,给 webgl 的A矩阵就得颠倒,也就是将 A 的第一行变为第一列,第二行变成第二列
  25. +    const matrix = new Float32Array([
  26. +        1, 0, 0, 0,
  27. +        0, 1, 0, 0,
  28. +        0, 0, 1, 0,
  29. +        Tx, Ty, Tz, 1,
  30. +    ])
  31. +    // 将矩阵分配给 u_xformMatrix
  32. +    gl.uniformMatrix4fv(u_xformMatrix, false, matrix);
  33. +}
复制代码
代码解析:

  • 前面已经说过,变换是一个逐顶点的操作,每个顶点都相同,所以不用 attribute 而用 uniform
  • mat4 表示4*4的矩阵
  • 向量(新点) = 变换矩阵 * 向量(旧点)
  • gl.uniformMatrix4fv(location, transpose, value) 为 uniform variables 指定矩阵值。webgl 中 transpose 必须为 false.
注:如果改变变换矩阵 * 向量的顺序,平移效果就不对了:

矩阵库

自己手写矩阵数组非常麻烦。
openGL 提供了一系列有用的函数帮助我们创建变换矩阵。例如通过 glTranslate 传入在 x、y、z 上平移的距离,就可以创建一个平移矩阵。
既然 webgl 中未提供创建变换矩阵的函数,我们就使用库来做这部分工作。
gl-matrix

笔者使用一个较流行的矩阵库 gl-matrix —— 用于高性能WebGL应用程序的Javascript矩阵和矢量(又称为向量)库。
下载后,在 dist 目录下看到 esm 文件夹和两个 js 文件:
  1. toji-gl-matrix-4480752/dist (master)
  2. $ ll
  3. drwxr-xr-x 1 Administrator 197121      0 Mar  6 15:26 esm/
  4. -rw-r--r-- 1 Administrator 197121  52466 Jan 10 05:24 gl-matrix-min.js
  5. -rw-r--r-- 1 Administrator 197121 206643 Jan 10 05:24 gl-matrix.js
复制代码
其实也就是提供两种使用的方法:

  • esm 通过  这种方式使用
  • 最常见的
笔者选用第二种:在 html 中引入:
这时在控制台就有一个 glMatrix 全局变量:
  1. glMatrix
  2. {glMatrix: {…}, mat2: {…}, mat2d: {…}, mat3: {…}, mat4: {…}, …}
  3.     glMatrix: {EPSILON: 0.000001, ANGLE_ORDER: "zyx", RANDOM: ƒ, setMatrixArrayType: ƒ, …}
  4.     mat2: {create: ƒ, clone: ƒ, copy: ƒ, identity: ƒ, fromValues: ƒ, …}
  5.     mat2d: {create: ƒ, clone: ƒ, copy: ƒ, identity: ƒ, fromValues: ƒ, …}
  6.     mat3: {create: ƒ, fromMat4: ƒ, clone: ƒ, copy: ƒ, fromValues: ƒ, …}
  7.     mat4: {create: ƒ, clone: ƒ, copy: ƒ, fromValues: ƒ, set: ƒ, …}
  8.     quat: {create: ƒ, identity: ƒ, setAxisAngle: ƒ, getAxisAngle: ƒ, getAngle: ƒ, …}
  9.     quat2: {create: ƒ, clone: ƒ, fromValues: ƒ, fromRotationTranslationValues: ƒ, fromRotationTranslation: ƒ, …}
  10.     vec2: {create: ƒ, clone: ƒ, fromValues: ƒ, copy: ƒ, set: ƒ, …}
  11.     vec3: {create: ƒ, clone: ƒ, length: ƒ, fromValues: ƒ, copy: ƒ, …}
  12.     vec4: {create: ƒ, clone: ƒ, fromValues: ƒ, copy: ƒ, set: ƒ, …}
复制代码
官方文档也是从这几个模块来介绍的:mat2, mat2d, mat3, mat4, quat, quat2, vec2, vec3, vec4。

  • mat[234]就是2维3维4维矩阵
  • vec[234]就是2维3维4维向量
四维矩阵

首先取得 mat4 模块,然后调用 create() 就会创建一个四维矩阵:
  1. // 四维矩阵模块
  2. const { mat4 } = glMatrix
  3. // 创建一个4维单位矩阵
  4. const matrix = mat4.create()
  5. /*
  6. Float32Array(16) [
  7.     1, 0, 0, 0,
  8.     0, 1, 0, 0,
  9.     0, 0, 1, 0,
  10.     0, 0, 0, 1]
  11. */
  12. console.log(matrix)
复制代码
Tip: create() 创建的是一个单位矩阵,如同数的乘法中的1
平移矩阵

fromTranslation - 平移矩阵
语法如下:
  1. (static) fromTranslation(out, v) → {mat4}
  2. Creates a matrix from a vector translation This is equivalent to (but much faster than): mat4.identity(dest); mat4.translate(dest, dest, vec);
  3. Parameters:
  4. Name        Type                 Description
  5. out            mat4                 mat4 receiving operation result
  6. v            ReadonlyVec3         Translation vector
  7. Returns:
  8. out
复制代码
请看示例:
  1. mat4.fromTranslation(matrix, [0.5, 0.5, 0])
  2. /*
  3.     Float32Array(16) [
  4.         1,   0,   0,   0,
  5.         0,   1,   0,   0,
  6.         0,   0,   1,   0,
  7.         0.5, 0.5, 0,   1
  8.     ]
  9. */
  10. console.log(matrix)
复制代码
matrix 是一个单位矩阵,通过该方法,即可得到一个向 x 和 y 各平移 0.5 的变换矩阵。
与之对应不修改原矩阵的方法是:translate(out, a, v)。语法如下:
  1. (static) translate(out, a, v) → {mat4}
  2. Translate a mat4 by the given vector
  3. Parameters:
  4. Name        Type                Description
  5. out          mat4                the receiving matrix
  6. a            ReadonlyMat4        the matrix to translate
  7. v            ReadonlyVec3        vector to translate by
  8. Returns:
  9. out
复制代码
请看示例:
  1. const matrix2 = mat4.create()
  2. mat4.translate(matrix2, matrix, [0.5, 0.5, 0])
  3. // Float32Array(16) [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
  4. /*
  5.     Float32Array(16) [
  6.         1, 0, 0, 0,
  7.         0, 1, 0, 0,
  8.         0, 0, 1, 0,
  9.         0, 0, 0, 1
  10.     ]
  11. */
  12. console.log(matrix)
  13. /*
  14.     Float32Array(16) [
  15.         1,   0,   0,   0,
  16.         0,   1,   0,   0,
  17.         0,   0,   1,   0,
  18.         0.5, 0.5, 0,   1
  19.     ]
  20. */
  21. console.log(matrix2)
复制代码
matrix 没有改变,最终变换矩阵输出到 matrix2。
旋转矩阵

fromRotation - 旋转矩阵
创建一个旋转矩阵。请看示例:
  1. // fromRotation(out, rad, axis) - out 是要修改的矩阵、rad 旋转角度、axis 围绕哪个轴旋转 [x, y, z]
  2. const angle = 90
  3. // 角度转弧度
  4. const rad = angle * Math.PI / 180;
  5. const axis = [0, 0, 1];
  6. // 等于 fromXRotation、fromYRotation、fromZRotation
  7. mat4.fromRotation(matrix, rad, axis)
  8. /*
  9. Float32Array(16) [
  10.     6.123234262925839e-17, 1, 0, 0,
  11.     -1, 6.123234262925839e-17, 0, 0,
  12.     0, 0, 1, 0,
  13.     0, 0, 0, 1
  14. ]
  15. */
  16. console.log(matrix)
复制代码
与之对应不修改原矩阵的方法是:rotate(out, a, rad, axis)。用法与平移中的类似。
toRadian

旋转矩阵需要使用弧度,通过 toRadian() 可以将角度转为弧度。用法如下:
  1. glMatrix.glMatrix.toRadian(180) => 3.141592653589793
复制代码
缩放矩阵

fromScaling - 缩放矩阵
创建一个缩放矩阵。请看示例:
  1. mat4.fromScaling(matrix, [2, 2, 0])
  2. /*
  3. Float32Array(16) [
  4.     2, 0, 0, 0,
  5.     0, 2, 0, 0,
  6.     0, 0, 0, 0,
  7.     0, 0, 0, 1
  8. ]
  9. */
  10. console.log(matrix)
复制代码
与之对应不修改原矩阵的方法是:scale(out, a, v)。用法与平移中的类似。
平移

现在使用这个库来实现平移,只需要将手动矩阵替换如下即可:
  1. -    const matrix = new Float32Array([
  2. -        1, 0, 0, 0,
  3. -        0, 1, 0, 0,
  4. -        0, 0, 1, 0,
  5. -        Tx, Ty, Tz, 1,
  6. -    ])
  7. +
  8. +    const { mat4 } = glMatrix
  9. +    const matrix = mat4.create()
  10. +    mat4.fromTranslation(matrix, [Tx, Ty, 0])
复制代码
旋转、缩放也类似,不再展开。
组合变换矩阵

变换矩阵可以组合,比如希望将三角形旋转和平移,这里需要注意:顺序不同导致结果不同。请看下图

核心代码:
  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. // 移动矩阵
  4. uniform mat4 u_tformMatrix;
  5. // 旋转矩阵
  6. uniform mat4 u_rformMatrix;
  7. void main() {
  8.   // 先旋转后移动
  9.   // gl_Position = u_tformMatrix * u_rformMatrix * a_Position;
  10.   // 先移动后旋转
  11.   gl_Position = u_rformMatrix * u_tformMatrix * a_Position;
  12.   gl_PointSize = 10.0;               
  13. }
  14. `
  15. const u_rformMatrix = gl.getUniformLocation(gl.program, 'u_rformMatrix');
  16. const u_tformMatrix = gl.getUniformLocation(gl.program, 'u_tformMatrix');
  17. const { mat4 } = glMatrix
  18. const tMatrix = mat4.create()
  19. const rMatrix = mat4.create()
  20. mat4.fromTranslation(tMatrix, [0.5, 0, 0])
  21. // 设置移动矩阵
  22. gl.uniformMatrix4fv(u_tformMatrix, false, tMatrix);
  23. const rad = glMatrix.glMatrix.toRadian(90)
  24. const axis = [0, 0, 1];
  25. mat4.fromRotation(rMatrix, rad, axis)
  26. // 设置旋转矩阵
  27. gl.uniformMatrix4fv(u_rformMatrix, false, rMatrix);
复制代码
组合变换矩阵的顺序和 css 类似,从右往左。比如:

  • u_rformMatrix * u_tformMatrix * a_Position 先移动后旋转
  • u_tformMatrix * u_rformMatrix * a_Position 先旋转后移动
Tip: 这里的组合变换矩阵其实就是计算机图形学中模型变换(M)。还有视图变换(V)、投影变换(P),统称为 MVP。
动画

需求

需求:绘制一个旋转动画
效果如下:

实现

思路:

  • 首先绘制三角形
  • 通过变换矩阵进行旋转
  • 不停的绘制(改变旋转角度)。使用专门用于动画的requestAnimationFrame(用法类似 setTimeout,但不需要指定回调时间,浏览器会在最恰当的时候回调)
完整代码如下:
  1. const VSHADER_SOURCE = `
  2. attribute vec4 a_Position;
  3. // 所有顶点移动位置都相同,所以不用 Attribute 而用 uniform
  4. // mat4 是一种4维矩阵
  5. uniform mat4 u_xformMatrix;
  6. void main() {
  7.   // 注:必须是 "变换矩阵 * 向量",不可是 "向量 * 变换矩阵"
  8.   gl_Position = u_xformMatrix * a_Position ;
  9.   gl_PointSize = 10.0;               
  10. }
  11. `
  12. const FSHADER_SOURCE = `
  13. void main() {
  14.   gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
  15. }
  16. `
  17. function main() {
  18.     const canvas = document.getElementById('webgl');
  19.     const gl = canvas.getContext("webgl");
  20.     if (!gl) {
  21.         console.log('Failed to get the rendering context for WebGL');
  22.         return;
  23.     }
  24.     if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  25.         console.log('Failed to intialize shaders.');
  26.         return;
  27.     }
  28.     const vertices = {
  29.         data: new Float32Array([
  30.             0.0, 0.5,
  31.             -0.5, -0.5,
  32.             0.5, -0.5
  33.         ]),
  34.         vertexNumber: 3,
  35.         count: 2,
  36.     }
  37.     initVertexBuffers(gl, vertices)
  38.     tick(gl, vertices)
  39. }
  40. function initVertexBuffers(gl, { data, count }) {
  41.     // 1. 创建缓冲区对象
  42.     const vertexBuffer = gl.createBuffer();
  43.     if (!vertexBuffer) {
  44.         console.log('创建缓冲区对象失败');
  45.         return -1;
  46.     }
  47.     gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  48.     gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  49.     const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  50.     if (a_Position < 0) {
  51.         console.log('Failed to get the storage location of a_Position');
  52.         return -1;
  53.     }
  54.     gl.vertexAttribPointer(a_Position, count, gl.FLOAT, false, 0, 0);
  55.     gl.enableVertexAttribArray(a_Position);
  56. }
  57. function 变换(gl, vertices) {
  58.     const u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
  59.     if (!u_xformMatrix) {
  60.         console.log('Failed to get the storage location of u_xformMatrix');
  61.         return;
  62.     }
  63.     const { mat4 } = glMatrix
  64.     const matrix = mat4.create()
  65.     const rad = glMatrix.glMatrix.toRadian(angle)
  66.     const axis = [0, 0, 1];
  67.     mat4.fromRotation(matrix, rad, axis)
  68.     gl.uniformMatrix4fv(u_xformMatrix, false, matrix);
  69.     gl.clearColor(0, 0, 0, 1);
  70.     gl.clear(gl.COLOR_BUFFER_BIT);
  71.    
  72.     gl.drawArrays(gl.TRIANGLES, 0, vertices.vertexNumber);
  73. }
  74. let angle = 0
  75. // 每次改变的角度
  76. const seed = 1
  77. function tick(gl, vertices){
  78.     变换(gl, vertices)
  79.     // 改变角度
  80.     angle += seed;
  81.     // 动画绘制
  82.     requestAnimationFrame(() => tick(gl, vertices))
  83. }
复制代码
其他章节请看:
webgl 系列

来源:https://www.cnblogs.com/pengjiali/p/17200893.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具