变换(Transformations)
1. 矢量(Vector)
一个矢量拥有方向(direction)和大小(magnitude)也称为长度(length)。
矢量表示
1.1 矢量与标量的运算
- 一个标量(scalar)就是一个数字。矢量与标量的加减乘除(下面的+,可以是+, -, .或÷)。
1.2 矢量的负运算
- 一个矢量的负就是反方向的矢量。
1.3 矢量的加减运算
- 两个矢量相加被定义为矢量之间对应元素之间进行相加,下面展示两个矢量进行相加,相减与此类似:
1.4 矢量的长度
- 获取一个矢量的大小或长度我们使用毕达哥拉斯原理(Pythagoras theorem)。
表示向量的长度。 - 有一个特殊类型的矢量,我们称为单位矢量(unit vector)。单位矢量的长度为1。普通矢量转换为一个单位矢量称为正则化(normalizing)。
1.5 矢量的乘运算
-
矢量乘法有两种:
- 点积(dot product),表示为。
- 叉积(cross product),表示为。
点积运算公式(其中表示两个矢量之间的角度):
对于单位矢量
点积的计算
-
叉积只定义在3D空间,且需两个非平行的矢量作为输入,结果是与两个输入矢量正交的矢量。如果两个输入矢量之间彼此正交,则叉积将产生三个彼此正交的矢量。(图片取自书中)
叉积的计算
2. 矩阵(Matrices)
- 一个矩阵(matrix)就是一个数字、符号和/或数学表达式的矩形数组。矩阵中的每个项称为矩阵的元素(element)。一个2X3的矩阵示例:
- 矩阵由(i, j)进行索引,其中i是行,j是列。而3行和2列也称为矩阵的维(dimensions)。
2.1 矩阵的加减
- 两个矩阵之间的加减是在每个元素上完成的,这意味着只能是维度相同的两个矩阵才可以进行加减运算。下面是一个2X2的矩阵相加示例(减法运算相似):
2.1 矩阵与标量的乘积
- 矩阵与标量乘积相当于矩阵每个元素都乘以标量。
2.3 矩阵之间的乘积
- 矩阵之间相乘的限制:
- 左侧矩阵的列数与右侧矩阵的行数相等;
- 矩阵相乘是不可交换(commutative) 的
2.4 矩阵与矢量乘积
- 矢量实际上是一个矩阵,其中N是矢量分部的数量(也称为N维矢量)。
2.5 单位矩阵
- 单位矩阵(identity matrix)是一个除了对角线元素为1其他元素都是0的矩阵。下面是一个单位矩阵与矢量相乘:
2.6 缩放(Scaling)
- 没个坐标轴的缩放因此不一样称为不成比例缩放(non-uniform scale),如果所有坐标轴的缩放因子相同则是成比例缩放(uniform scale)。矢量缩放可通过与矩阵相乘实现,下面运算中左侧矩阵称为缩放矩阵:
2.7 平移(Translation)
- 平移就是原来矢量加上另一个矢量产生一个指向不同位置的矢量,相当于基于一个平移矢量移动矢量。矢量平移可通过平移矩阵与矢量相乘实现:
- 矢量的w分部也称为齐次坐标(homogeneous coordinate)。要将一个齐次坐标矢量转换为3D坐标矢量,我只需将x,y和z坐标除以w坐标。使用齐次坐标的好处是:允许我们对3D矢量做矩阵平移,而且下一章我们将使用w值创建3D透视。同时,齐次坐标等于0的矢量也称为方向矢量(direction vector)。
2.8 旋转(Rotation)
角度与弧度的转换
3D坐标空间的旋转需要指定一个角度和一个旋转轴(rotation axis)。
-
3D空间中针对不同单位坐标轴的旋转矩阵如下所示,其中角度由表示:
- 绕x轴旋转
- 绕y轴旋转
- 绕z轴旋转
- 绕x轴旋转
要绕任意3D空间坐标轴旋转,我们可以通过结合上述3个旋转矩阵来实现,如先绕x轴旋转,然后y轴然后是z轴。但是,这样会引入一个叫做万向锁(Gimbal lock) 的问题。一个更好的解决方案是直接绕任意单位坐标轴进行旋转而不是通过组合旋转矩阵。任意单位坐标轴的旋转矩阵如下所示:
注意:上面的旋转矩阵也无法完全解决万向锁问题,要完全解决万向锁问题我们需要使用四元数(quaternions) 来表示旋转。
2.9 组合变换矩阵
- 矩阵用于变换的真正威力在于我们可以将多个变换操作组合到一个矩阵中。下面的示例展示将矢量缩放2和平移。
组合矩阵与矢量相乘
- 当进行矩阵相乘时,最右的矩阵最先与矢量相乘,所以我们应该从右往左看矩阵相乘。在组合矩阵时建议先进行缩放操作,然后旋转最后是平移,不然它们之间可能彼此影响。
3. GLM
- GLM代表OpenGL Mathematics,是一个header-only类库,这意味着只需包含合适的头文件,无需链接和编译即可应用类库。
- GLM类库的下载
Gitee 极速下载/glm -
GLM解压内容
- GLM类库使用:直接将glm文件夹拷贝到项目目录中。
- 我们需要的大部分GLM功能可以在下面3个头文件找到:
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
- 使用GLM类库将矢量平移(注意代码中将矢量定义为
glm::vec4
,齐次坐标设置为1.0):
glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans = glm::mat4(1.0f); // 单位矩阵
// 通过平移单位矩阵获取平移矩阵
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << std::endl;
- 下面是一个使用GLM进行缩放和旋转的例子:
glm::mat4 trans = glm::mat4(1.0f);
// 以z轴旋转90度
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
- 注意:旋转应该绕单位矢量进行,如果不是绕x,y或z轴需要先将矢量正则化。
- 修改顶点着色器,添加
mat4
的uniform变量:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
uniform mat4 transform;
void main()
{
gl_Position = transform * vec4(aPos, 1.0f);
TexCoord = aTexCoord;
}
- 在代码中为顶点着色器的uniform变量设置值:
unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
-
渲染效果
- 让图形随时间进行旋转然后平移(下面代码需在渲染循环中执行):
glm::mat4 trans = glm::mat4(1.0f);
// 平移
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
// 随时间旋转
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0, 0.0, 1.0));
-
渲染效果