GLM库
在了解坐标系统之前,必须在OpenGL中添加数学库,GLM。GLM是一个只包含头文件的库,它易于使用,方便安装,专门为OpenGL定做的数学库。
只需要下载后,将文件解压,把glm文件夹复制到你的工程文件中,对着glm文件夹右键,点击:包含在项目中,即可使用。
一般来说,GLM的大多数功能在以下三个文件中就有包含:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>码片
矩阵的知识在此不做过多介绍,直接举例如何利用GLM创建矩阵,并加以如何在着色器中加以使用。
glm::mat4 matrix(1.0f); //创建4*4的单位矩阵
model = glm::scale(model, glm::vec3(0.5, 0.5, 0.5)); //创建缩放矩阵
model = glm::rotate(model, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0)); //创建旋转矩阵
model = glm::translate(model, glm::vec3(1.0f, 1.0f, 0.0f)); //创建变换矩阵
其实我们观察源码后,也能发现原理是一样的,就是将数字模板化,然后直接计算结果。而在游戏开发中,只会涉及到三维空间或二维空间的操作。因此在44矩阵中,即三维空间移动,一般情况只需要vec3作为第二个参数,之所以需要44矩阵,是用于避免三位运动中的欧拉死锁,同时也能更好的表述三维空间运动。
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER mat<4, 4, T, Q> scale(mat<4, 4, T, Q> const& m, vec<3, T, Q> const& v)
{
mat<4, 4, T, Q> Result;
Result[0] = m[0] * v[0];
Result[1] = m[1] * v[1];
Result[2] = m[2] * v[2];
Result[3] = m[3];
return Result;
}
在着色器代码中,我们需要定义一个uniform mat4 用于传输数据。uniform可以传输数据进着色器中,mat4对应匹配model的数据类型,之后便可直接使用。其余例子不过多介绍,原理是相同的。
//shader code
#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 = vec2(aTexCoord.x, 1.0 - aTexCoord.y);
}
//project code
glm::mat4 matrix(1.0f); //创建4*4的单位矩阵
model = glm::scale(model, glm::vec3(0.5, 0.5, 0.5)); //创建缩放矩阵
int location = int location = glGetUniformLocation(program, "transform");
glUniformMatrix4fv(location, 1, GL_FALSE, &model[0][0]);
坐标系统
了解如何使用glm库,如何创建矩阵进行运算后,需要介绍各个坐标系统。在OpenGL中,分五个坐标系统,或五个空间坐标系:
- 局部空间,或物体空间
- 世界空间
- 观察空间,或摄像机视角空间
- 裁剪空间
- 屏幕空间
通常来说,我们创建一个物体后需要对物体进行操作,例如改变形状,局部调整颜色,材质等,都需要先在局部空间上操作。在局部空间上作用的矩阵,称之为模型(Model) 。
接着多个物体如何拜访,拜访的位置具体在哪,旋转角度如何,是在世界空间上进行操作。局部坐标之后会变成世界坐标,同用Model进行计算。
然后需要调整摄像头的视角,就需要在观察空间进行调整,例如将摄像头拉远拉近,或是围着物体旋转,物体不变但是屏幕上的物体会一直变化。作用在观察空间的矩阵称之为观察(View)。
最后我们对摄像头下的视角图片,进行裁剪,然后计算投影到屏幕的每个像素点中,就需要用到**投影(Projection)**矩阵。
裁剪空间
OpenGL是希望所有坐标都在一个特定的范围中,即可视空间。超过这个范围的顶点(Vertex)都会被裁剪掉。有个更常见的现象就是视角穿模,当物体超过可视范围就不会显示顶点坐标。
这个范围可以理解为一个箱子,在这个箱子内的物体才会被显示,而如何理解这个箱子,会有两种观察模式:正射投影、透视投影。
更贴合生活的投影方式就是透视投影,在远处的东西会越来越小,即透视现象。
原理上,是将远处的坐标xyz都除以w值,从而做到缩小的效果。在OpenGL中可以调用函数创建一个透视投影矩阵:
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
第一个参数就是我们常见的视野(FOV),第二个参数是宽高比,往往与窗口宽高比一致,第三第四表示的是近平面和远平面距离。当我们知道视野、宽高比、平面距离,就能确定出一个特定的视角空间,或裁剪空间,在此空间中的物体才会显示。
应用矩阵
在着色器中,需要创建三个uniform mat4 矩阵,分别代表模型矩阵,视角矩阵和投影矩阵。
注意:矩阵运算是新的矩阵进来需要添加在最左侧,矩阵计算是没有交换律的。
因此顺序,位置也是十分重要的,往往无法产生图像可能就是这里出了问题。
#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0);
...
}
在main()函数的主题循环中创建矩阵
//model matrix
glm::mat4 model(1.0f);
model = glm::rotate(model, 45.0f, glm::vec3(1.0f, 0.0f, 0.0f));
//view matrix
glm::mat4 view(1.0f);
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//projection matrix :
glm::mat4 projection(1.0f);
projection = glm::perspective(glm::radians(45.0f), (800.0f / 600.0f), 0.1f, 100.0f);
最终结果:
同理我们可以构造3D物体了,需要给出8个顶点即可。但是直接构造会出现问题,OpenGL绘制图形是不断绘制三角形的,有些三角形是会重复覆盖某些区域,OpenGL不知道哪些区域应该是哪些三角形覆盖的,就会导致立方体别扭奇怪。
对此需要让OpenGL生成深度缓冲,同时OpenGL存储深度的信息存放在Z缓冲中,在调用片段着色器时,OpenGL会对比深度值和z缓冲,自动计算当前的三角形,是被覆盖还是在最外层,从而达到3D视觉的效果。
我们需要开启深度测试的功能,同时每次渲染迭代都会清除之前的深度缓冲:
glEnable(GL_DEPTH_TEST);
//在主体循环中
while(!glfwWindowShouldClose(window)){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
最终结果展示: