【WebGL之巅】11-使用Matrix变换矩阵实现三角形旋转、平移、缩放

By yesmore on 2021-07-26
阅读时间 12 分钟
文章共 2.4k
阅读量

对应《WebGL编程指南》代码:12-RotatedTriangle_Matrix

要点:矩阵和矢量的乘法、矩阵变换(旋转矩阵、平移矩阵、缩放矩阵)、gl.uniformMatrix4fv

知识点

一、矩阵和矢量的乘法

矩阵示例:

​ $
\begin{bmatrix}
​ 8 & 3 & 0 \\
​ 4 & 3 & 6 \\
​ 3 & 2 & 6
\end{bmatrix}
$​

矩阵相乘:

​ $
\begin{bmatrix}
​ x’ \\
​ y’ \\
​ z’
\end{bmatrix}
= $$
\begin{bmatrix}
​ a & b & c \\
​ d & e & f \\
​ g & h & i
\end{bmatrix}
$ * $
\begin{bmatrix}
​ x \\
​ y \\
​ z
\end{bmatrix}$

结果:

$ \begin{cases} x’ = ax + by + cz \\ y’ = dx + ey + fz \\ z’ = gx + hy + iz \end{cases} $​​

注意:只有在矩阵列数与矢量的行数相等时才能相乘,且不满足交换律

二、变换矩阵:旋转

对比上一节计算角度变换的公式:

$ \begin{cases} x’ = xcosβ - ysinβ \\ y’ = xsinβ + ycosβ \\ z’ = z \end{cases} $

这里假设

$ \begin{cases} a = cosβ, b = -sinβ, c = 0 \\ d = sinβ, e = cosβ, f = 0 \\ g = 0, h = 0, i = 1 \end{cases} $

那么此时矩阵的乘法就可以变成:

​ $
\begin{bmatrix}
​ x’ \\
​ y’ \\
​ z’
\end{bmatrix}
= $$
\begin{bmatrix}
​ cosβ & -sinβ & 0 \\
​ sinβ & cosβ & 0 \\
​ 0 & 0 & 1
\end{bmatrix}
$ * $
\begin{bmatrix}
​ x \\
​ y \\
​ z
\end{bmatrix}$

这个矩阵就被称为变换矩阵,将(x, y, z)变换成(x’, y’, z’),这个矩阵又可以称为旋转矩阵

接下来就是代码中的变换矩阵:

1
2
3
4
5
6
7
8
9
10
11
12
var 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
]
);

var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
// 赋值
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);
三、变换矩阵:平移

对比一般平移计算公式:

​ $x’ = ax+by+cz$

​ $x’ = x + Tx$ (一般方法)

分析:这里的Tx是一个常量,但是第一个等式中没有常量,意味着3X3的矩阵无法表示平移,所有这里使用4X4矩阵,以及具有第4个分量的矢量(通常设为1.0),也就是说,假设要进行平移的点p(x, y, z, 1),平移后坐标为p'(x', y', z', 1),如下等式:

$
\begin{bmatrix}
x’ \\
y’ \\
z’ \\
1
\end{bmatrix}
= $$
\begin{bmatrix}
a & b & c & d \\
e & f & g & h \\
i & j & k & l \\
m & n & o & p
\end{bmatrix}
$​ * $
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix}$​​​​

结果如下:

$ \begin{cases} x’ = ax + by + cz + d \\ y’ = ex + fy + gz + h \\ z’ = ix + jy + kz + l \\ 1 = mx + ny + oz + p \end{cases} $​

根据式子$1 = mx + ny + oz + p$​​,计算出$m=0,n=0,o=0,p=1$​​,其中d、h、lp均为常数​,比较等式:

$ \begin{cases} x’ = x + Tx \\ y’ = y + Ty \\ z’ = z + Tz \end{cases} $

得出平移矩阵

$
\begin{bmatrix}
x’ \\
y’ \\
z’ \\
1
\end{bmatrix}
= $$
\begin{bmatrix}
1 & 0 & 0 & Tx \\
0 & 1 & 0 & Ty \\
0 & 0 & 1 & Tz \\
0 & 0 & 0 & 1
\end{bmatrix}
$​ * $
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix}$​​

程序示例代码:

1
2
3
4
5
6
7
8
9
10
11
var xformMatrix = new Float32Array(
[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
Tx, Ty, Tz, 1.0
]
);

var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);
四、4x4旋转矩阵——先旋转再平移

分析:要达到先旋转再平移的效果,需要将两个矩阵组合起来,而旋转矩阵(3x3)与平移矩阵(4x4)的阶数不同,需要某种手段使两者阶数一致

将3x3阶矩阵转换为4X4阶矩阵,可以比较:

$ \begin{cases} x’ = xcosβ - ysinβ \\ y’ = xsinβ + ycosβ \\ z’ = z \end{cases} 与$​ $ \begin{cases} x’ = ax + by + cz + d \\ y’ = ex + fy + gz + h \\ z’ = ix + jy + kz + l \\ 1 = mx + ny + oz + p \end{cases} $

可知,例如两者x’表达式中,令a=cosβ,b=-sinβ,c=0,d=0即可相等,以此类推解出y’、z’中的系数,得出4x4的旋转矩阵:

$
\begin{bmatrix}
x’ \\
y’ \\
z’ \\
1
\end{bmatrix}
= $$
\begin{bmatrix}
cosβ & -sinβ & 0 & 0 \\
sinβ & cosβ & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
$​​ * $
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix}$​​

五、变换矩阵:缩放

仍然假设最初的点p(x, y, z),缩放过后的点p’(x’, y’, z’),假设缩放因子Sx,Sy,Sz,那么有

$ \begin{cases} x’ = Sx * x \\ y’ = Sy * y \\ z’ = Sz * z \end{cases} $ 与等式 $ \begin{cases} x’ = ax + by + cz + d \\ y’ = ex + fy + gz + h \\ z’ = ix + jy + kz + l \\ 1 = mx + ny + oz + p \end{cases} $​ 比较

可知缩放矩阵如下:

$
\begin{bmatrix}
x’ \\
y’ \\
z’ \\
1
\end{bmatrix}
= $$
\begin{bmatrix}
Sx & 0 & 0 & 0 \\
0 & Sy & 0 & 0 \\
0 & 0 & Sz & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
$​​ * $
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix}$​​

六、gl.uniformMatrix4fv(location, transpose, array);

作用:将array表示的4x4矩阵分配给location指定的uniform变量

参数:

​ location:uniform变量的存储位置

​ transpose:表示是否转置矩阵,在webGl中必须设置为false

​ array:待传输的类型化数组(按列主序的4x4矩阵)

实例

固定代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>rotate triangle</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use the browser supporting "canvas".
</canvas>

<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="RotatedTriangle_Matrix.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function initVertexBuffers(gl) {
var vertices = new Float32Array(
[0.0, 0.5, -0.5, -0.5, 0.5, -0.5]
);
var n=3; //点的个数

//创建缓冲区对象
var vertexBuffer = gl.createBuffer();
if(!vertexBuffer){
console.log("Failed to create thie buffer object");
return -1;
}

//将缓冲区对象保存到目标上
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

//向缓存对象写入数据
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if(a_Position < 0){
console.log("Failed to get the storage location of a_Position");
return -1;
}

//将缓冲区对象分配给a_Postion变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

//连接a_Postion变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position);

return n;
}
4x4旋转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var VSHADER_SOURCE =
//x' = x cos b - y sin b
//y' = x sin b + y cosb
//z' = z
'attribute vec4 a_Position;\n' +
'uniform mat4 u_xformMatrix;\n' +
'void main() {\n' +
'gl_Position = u_xformMatrix * a_Position;\n' +
'}\n';

var FSHADER_SOURCE=
'void main(){'+
'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
'}';

var ANGLE = 90.0;

function main(){

var canvas = document.getElementById("webgl");
if(!canvas){
console.log("Failed to retrieve the <canvas> element");
return;
}

var gl = getWebGLContext(canvas);
if(!gl){
console.log("Failed to get the rendering context for WebGL");
return;
}

if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// 角度转弧度制
var radian = Math.PI * ANGLE / 180.0;
var cosB = Math.cos(radian);
var sinB = Math.sin(radian);

var 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
]
);

var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
// 将旋转矩阵传输给顶点着色器
gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

gl.clearColor(0.0, 0.0, 0.0, 1.0);

gl.clear(gl.COLOR_BUFFER_BIT);

gl.drawArrays(gl.TRIANGLES, 0, n);
}
分析代码1:顶点着色器——‘uniform mat4 u_xformMatrix;\n’

​ u_xformMatrix:旋转矩阵

​ a_Position:顶点坐标,即4x4旋转矩阵右侧矢量

​ mat4类型:表示4x4的矩阵变量

效果

1

4x4平移矩阵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
var VSHADER_SOURCE =
//x' = x cos b - y sin b
//y' = x sin b + y cosb
//z' = z
'attribute vec4 a_Position;\n' +
'uniform mat4 u_xformMatrix;\n' +
'void main() {\n' +
'gl_Position = u_xformMatrix * a_Position;\n' +
'}\n';

var FSHADER_SOURCE=
'void main(){'+
'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
'}';

var Tx = 0.5, Ty = 0.5, Tz = 0.0;

function main(){

var canvas = document.getElementById("webgl");

var gl = getWebGLContext(canvas);

if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);

var xformMatrix = new Float32Array(
[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
Tx, Ty, Tz, 1.0
]
);

var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');

gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

gl.clearColor(0.0, 0.0, 0.0, 1.0);

gl.clear(gl.COLOR_BUFFER_BIT);

gl.drawArrays(gl.TRIANGLES, 0, n);
}
分析代码2:

​ 这是我们计算出的平移矩阵:

$
\begin{bmatrix}
x’ \\
y’ \\
z’ \\
1
\end{bmatrix}
= $$
\begin{bmatrix}
1 & 0 & 0 & Tx \\
0 & 1 & 0 & Ty \\
0 & 0 & 1 & Tz \\
0 & 0 & 0 & 1
\end{bmatrix}
$ * $
\begin{bmatrix}
x \\
y \\
z \\
1
\end{bmatrix}$

​ 然而,JavaScript只能定义数组,没有表示矩阵的类型,数组只是一维,其元素只能排成一行,而矩阵是多维。这里,有两种方式来存储矩阵元素:按行主序按列主序

​ WebGL与OpenGL都是采用按列主序的方式来存储矩阵:

1
2
3
4
5
6
7
8
var xformMatrix = new Float32Array(
[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
Tx, Ty, Tz, 1.0
]
);
效果

2

4x4缩放矩阵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 变换矩阵缩放
var VSHADER_SOURCE =
//x' = x cos b - y sin b
//y' = x sin b + y cosb
//z' = z
'attribute vec4 a_Position;\n' +
'uniform mat4 u_xformMatrix;\n' +
'void main() {\n' +
'gl_Position = u_xformMatrix * a_Position;\n' +
'}\n';

var FSHADER_SOURCE=
'void main(){'+
'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+
'}';

var Sx = 1.5, Sy = 1.7, Sz = 1.0;

function main(){

var canvas = document.getElementById("webgl");
if(!canvas){
console.log("Failed to retrieve the <canvas> element");
return;
}

var gl = getWebGLContext(canvas);
if(!gl){
console.log("Failed to get the rendering context for WebGL");
return;
}

if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}

var xformMatrix = new Float32Array(
[
Sx, 0.0, 0.0, 0.0,
0.0, Sy, 0.0, 0.0,
0.0, 0.0, Sz, 0.0,
0.0, 0.0, 0.0, 1.0
]
);

var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');
if(u_xformMatrix < 0){
console.log("Failed to get the storage location of u_xformMatrix");
return;
}

gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix);

gl.clearColor(0.0, 0.0, 0.0, 1.0);

gl.clear(gl.COLOR_BUFFER_BIT);

gl.drawArrays(gl.TRIANGLES, 0, n);
}
效果

3


Tips: Please indicate the source and original author when reprinting or quoting this article.