OpenGL自制3D游戏引擎入门VIII
Transformations 变换
基本数学知识参见教材~~
一、矩阵与向量相乘
- 我们使用向量表示位置、颜色、纹理坐标,向量实际上就是一个$N \times 1$矩阵,这样它就可以与$M \times N$矩阵相乘
- 单位矩阵通常是生成其他矩阵的起点,其在证明定理,解线性方程非常有用的矩阵
缩放
- 对一个向量进行缩放(Scaling)就是对向量的长度进行缩放而不改变其方向
- OpenGL通常在3D空间进行操作,对于2D情况我们可以把z轴缩放1倍,保证z轴的值不改变
缩放矩阵,其中$(S_1,S_2,S_3)$为缩放向量
\begin{pmatrix}
$$
\begin{bmatrix}
1 & 0 & 0 & S_1\\
0 & 1 & 0 & S_2\\
0 & 0 & 1 & S_3\\
0 & 0 & 0 & 1\\
\end{bmatrix}
·
\begin{pmatrix}
x\\
y\\
z\\
1\\
\end{pmatrix}
S_1·x\\
S_2·y\\
S_3·z\\
1\\
\end{pmatrix}
$$
Tip:第四个缩放向量为1,因为在3D空间中缩放w是无意义的,w分量另外有其他用途,之后会提到
位移
位移(Translation)是在原始向量的基础上加上另一个向量从而获得一个在不同位置新向量的过程
位移矩阵,其中$(T_x,T_y,T_z)$为位移向量
$$
\begin{bmatrix}
1 & 0 & 0 & T_x\\
0 & 1 & 0 & T_y\\
0 & 0 & 1 & T_z\\
0 & 0 & 0 & 1\\
\end{bmatrix}
·
\begin{pmatrix}
x\\
y\\
z\\
1\\
\end{pmatrix}\begin{pmatrix}
x+T_x\\
y+T_y\\
z+T_z\\
1\\
\end{pmatrix}
$$齐次坐标(Homogeneous Coordinates):向量w的分量也叫齐次坐标,要想从齐次坐标得到3D向量,我们可以把x,y,z坐标分别处以w坐标。使用齐次坐标有几点好处:它允许我们在3D向量上进行位移(如果没有w分量我们是不能位移向量的)。如果一个向量的齐次坐标是0,这个坐标就是方向向量,因为w坐标是0,这个向量就不能位移(也就是说不能位移一个方向)
旋转
3D空间中旋转需要定义一个叫和一个旋转轴(Rotation Axis),物体沿着给定的旋转轴旋转特定角度
- 沿x轴旋转
$$
\begin{bmatrix}
1 & 0 & 0 & 0\\
0 & cos\theta & -sin\theta & 0\\
0 & sin\theta & cos\theta & 0\\
0 & 0 & 0 & 1\\
\end{bmatrix}
·
\begin{pmatrix}
x\\
y\\
z\\
1\\
\end{pmatrix}\begin{pmatrix}
x\\
cos\theta·y-sin\theta·z\\
sin\theta·y+cos\theta·z\\
1\\
\end{pmatrix}
$$
- 沿y轴旋转
$$
\begin{bmatrix}
cos\theta & 0 & sin\theta & 0\\
0 & 1 & 0 & 0\\
-sin\theta & 0 & cos\theta & 0\\
0 & 0 & 0 & 1\\
\end{bmatrix}
·
\begin{pmatrix}
x\\
y\\
z\\
1\\
\end{pmatrix}\begin{pmatrix}
\begin{pmatrix}
cos\theta·x+sin\theta·z\\
y\\
-sin\theta·x+cos\theta·z\\
1\\
\end{pmatrix}
$$
- 沿z轴旋转
$$
\begin{bmatrix}
cos\theta & -sin\theta & 0 & 0\\
sin\theta & cos\theta & 0 & 0\\
0 & 0 & 1 & 0\\
0 & 0 & 0 & 1\\
\end{bmatrix}
·
\begin{pmatrix}
x\\
y\\
z\\
1\\
\end{pmatrix}
cos\theta·x-sin\theta·y\\
sin\theta·x+cos\theta·y\\
z\\
1\\
\end{pmatrix}
$$- 通过这种方法我们可以把任意位置向量沿一个单位轴进行旋转,也可以将多个矩阵复合,完成复数操作
- 万向节死锁(Gimbal Lock):当某两轴在旋转后平行于同一平面,那么这两个轴向的旋转相同,这种情况下相当于我们失去了一个轴
- 对于3D空间中的旋转一个更好的模型是沿着任意一个轴进行旋转,而不是对一系列旋转矩阵进行复合,但这种方法也不能完全解决万向节死锁问题
(避免万向节死锁的真正解决方案是使用四元数)
二、GLM
OpenGL没有自带任何的矩阵和向量内容,因此我们引入数学库GLM(OpenGL Mathematics)
GLM是只有一个头文件的库,只需要包含对应的头文件,无需链接和编译
Tip:GLM库从0.9.9版本起初始化矩阵类型为零矩阵而非单位矩阵,如果使用0.9.9及以上版本,请在生成矩阵时通过下列语句转化为单位矩阵glm::mat4 mat = glm::mat4(1.0f)
最常用的功能可以从以下3个头文件中找到
#include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp>
举一个栗子,利用GLM进行向量位移:定义一个向量vec以及4维矩阵mat4,通过translate函数求得位移矩阵并将其赋值给mat4,位移矩阵与原向量相乘即可得到位移后的向量
using namespace glm; int main() { vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); mat4 trans; trans = translate(trans, vec3(1.0f, 1.0f, 0.0f)); vec = trans * vec; std::cout << vec.x << vec.y << vec.z << std::endl; }
再举一栗子,对之前我们制作出的对象进行旋转和缩放操作:通过scale函数将x,y,z轴都缩放0.5倍,通过radians函数将角度制转换为弧度制,然后通过rotate函数进行旋转,最终得到一个包含多种变换的复合矩阵
mat4 trans; trans = rotate(trans, radians(90.0f), vec3(0.0f, 0.0f, 1.0f)); trans = scale(trans, vec3(0.5f, 0.5f, 0.5f));
下一步将矩阵传递给着色器,GLSL中也有mat4类型变量,我们应该修改顶点着色器让其接收一个mat4的uniform变量,然后再用矩阵uniform乘以位置向量
#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; layout(location = 2) in vec2 aTexCoord; out vec3 ourColor; out vec2 TexCoord; uniform mat4 trans; void main() { gl_Position = trans * vec4(aPos, 1.0); ourColor = aColor; TexCoord = aTexCoord; }
查询uniform变量的地址,然后用有 Matrix4fv 后缀的glUniform函数将矩阵数据发送给着色器,第一参数为uniform地址,第二个参数告诉OpenGL需要发送多少矩阵,第三个参数代表是否进行矩阵置换(交换矩阵行列),最后一个参数为矩阵数据,但GLM并不是把它们的矩阵存储为OpenGL所接受的那种,因此需要GLM自带的函数value_ptr来转换这些数据
unsigned int transformLoc = glGetUniformLocation(myShader->ID, "trans"); glUniformMatrix4fv(transformLoc, 1, GL_FALSE, value_ptr(trans));
更进一步,让对象旋转起来:旋转角度参数填入时间函数glfwGetTime,即可让对象跟随时间转动
mat4 trans; trans = scale(trans, vec3(0.5f, 0.5f, 0.5f)); trans = rotate(trans, (float)glfwGetTime(), vec3(0.0f, 0.0f, 1.0f));