11 数学库与投影矩阵
标准化设备坐标系¶
引言¶
我们先做几个试验。
-
在原先的代码中,长方形的四个顶点是0.5
//顶点数据 float positions[] = { //顶点位置xy, UV坐标xy -0.5f, -0.5f, 0.0f, 0.0f, // 0 0.5f, -0.5f, 1.0f, 0.0f, // 1 0.5f, 0.5f, 1.0f, 1.0f, // 2 -0.5f, 0.5f, 0.0f, 1.0f // 3 };
-
将长方形的四个顶点改成1
//顶点数据 float positions[] = { //顶点位置xy, UV坐标xy -1.0f, -1.0f, 0.0f, 0.0f, // 0 1.0f, -1.0f, 1.0f, 0.0f, // 1 1.0f, 1.0f, 1.0f, 1.0f, // 2 -1.0f, 1.0f, 0.0f, 1.0f // 3 };
-
将长方形的四个顶点改成2
//顶点数据 float positions[] = { //顶点位置xy, UV坐标xy -2.0f, -2.0f, 0.0f, 0.0f, // 0 2.0f, -2.0f, 1.0f, 0.0f, // 1 2.0f, 2.0f, 1.0f, 1.0f, // 2 -2.0f, 2.0f, 0.0f, 1.0f // 3 };
不难发现,无论我们怎么更改顶点的坐标值,在窗口中,每次只显示了x∈[-1, 1],y∈[-1, 1]的内容。
回想,在这份代码中(basic.shader),positions
的顶点坐标直接赋值给了gl_Position
#shader vertex
//...
void main()
{
gl_Position = position;
v_TexCoord = texCoord;
};
#shader fragment
//...
因此,引出了一个背景知识:gl_Position
实际上被GPU当成 [-1, 1] 的坐标(标准化设备坐标)进行处理了。GPU在之后,会将**标准化设备坐标系** 映射成 屏幕坐标系,从而绘制到我们的屏幕上。
标准化设备坐标系¶
标准化设备坐标系(normalized device coordinate system, NDC)
- 是一个二维的空间直角坐标系
- 它的X、Y的范围都在[-1, 1]之间
GPU就会将 标准化设备坐标系 映射成 屏幕坐标系(window device coordinate system),从而将我们的图案显示到屏幕当中。
glm数学库¶
glm第三方库的全程叫做OpenGL Mathematics。Yes,它就是OpenGL的数学库,所以有很多标准都是基于OpenGL标准编写的。glm旨在模仿GLSL并与OpenGL一起使用。
例如:glm矩阵的内存布局与OpenGL一致,都是按列优先来存储的!
- 因此在使用
glUniformMatrix4fv
时,就无需转置,直接将glm
的矩阵内存拷贝到GPU即可。
void Shader::SetUniformMat4f(const std::string& name, const glm::mat4& matrix) {
GLCall(
glUniformMatrix4fv(
GetUniformLocation(name),
1, //1个矩阵
GL_FALSE, //true需要转置,false不需要转置
&matrix[0][0] //传入矩阵的内存地址
)
);
//f表示float
//v表示我们在传入一个数组
}
- 但如果其他数学库是以行优先来存储矩阵的,那么就需要传入
GL_TRUE
,OpenGL会自行对其进行转置
投影矩阵¶
介绍¶
假设,你有一个3D世界,里面有山丘、地形、人物、建筑物等等,这个3D世界是以何种方式显示在2D的屏幕当中的?
- 投影矩阵
投影矩阵,本质上是将三维空间投影到二维屏幕上的矩阵,即将三维坐标转换为标准化设备坐标。
它有两种:
- 透视投影:近大远小,常用于3D世界
- 正交投影:远近一样,常用语2D世界当中,但是3D中也会有应用(例如关卡编辑器、3D建模程序)
在代码中使用投影矩阵¶
-
Application.cpp
#include "glm/gml.hpp" #include "glm/gtc/matrix_transform.hpp" int main() { //... IndexBuffer ib(indices, 6); //正交矩阵(把所有坐标映射到2D平面上,离的远的物体并不会变小) glm::mat4 proj = glm::ortho( -2.0f, //左边 2.0f, //右边 -1.5f, //底部 1.5f, //顶部 -1.0f, //远 1.0f //近 ); //x∈[-2, 2]会被映射到NDC的[-1, 1]上,其他的会被删掉。因此x=0.5即在窗口的1/4处 //y∈[-1.5, 1.5]会被映射到NDC的[-1, 1]上,其他的会被删掉 //注:遵循4x3的纵横比(将这些数字乘以2,就得到了4x3) //顶部到底部有3个单位距离;从左到右4个单位距离 //... shader.SetUniformMat4f("u_MVP", proj); //传入着色器 }
-
basic.shader
#shader vertex #version 330 core layout(location = 0)in vec4 position; layout(location = 1)in vec2 textCoord; out vec2 v_TextCoord; uniform mat4 u_MVP; //模型视图矩阵,目前只传入了一个投影矩阵 void main() { gl_Position = u_MVP * position; //将坐标做一个投影变换 v_TextCoord = textCoord; }; #shader fragment #version 330 core layout(location = 0)out vec4 color; in vec2 v_TextCoord; uniform vec4 u_Color; uniform sampler2D u_Texture; void main() { vec4 textColor = texture(u_Texture, v_TextCoord); color = textColor; };
-
shader.cpp
在完成代码之后,我们可以做几个试验来加深对投影矩阵的理解
- 我们可以在CPU中计算投影之后的坐标是多少,然后观察这个点在窗口的位置是否正确
- 更改
glm::ortho
中x、y的阈值