【OpenGL】LearnOpenGL学习笔记06 - 坐标系统、MVP变换、绘制立方体
上接:https://blog.csdn.net/weixin_44506615/article/details/150052880?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl
一、坐标系统与MVP变换
首先我们需要了解几个坐标系统
1. 局部空间 (Local Space,或是称为物体空间 Object Space)
物体本身所在的坐标空间,例如我们在DCC软件(Max、Maya、Blender等)中创建了一个立方体,它默认在(0, 0, 0)的位置,此时基于的坐标系就是局部空间坐标系
2. 世界空间 (World Space)
在我们的场景中可能会有很多的物体,我们使用世界空间来描述这些物体在场景中的位置,通过 模型矩阵 (Model Matrix) 变换而来
3. 观察空间 (View Space)
我们观察3D场景是会在世界空间中的某一个位置,朝向某一个方向来观察,这个点就是我们摄像机所处的位置,那么基于我们这个观察点所建立的坐标系就是观察空间,通过 观察矩阵 (View Matrix) 变换而来
4. 裁剪空间 (Clip Space)
前文中提到,在顶点着色器最后,我们需要将坐标转化为标准设备坐标(NDC),这个坐标系三个轴的范围均在(-1, 1)之间,也就是说我们需要将顶点的坐标限制在这个范围内,超出的部分会被裁剪掉,这个坐标系就称为裁剪空间,通过 投影矩阵 (Projection Matrix) 变换而来
为此,我们需要一个观察箱,也被称作 平截头体(Frustum) 来表示这个范围,同时还分为正交和透视投影
5. 屏幕空间 (Screen Space)
屏幕空间即为最后输出为2D图像的坐标系,NDC坐标会基于当前分辨率转换为屏幕空间坐标
上面进行 局部 -> 世界 -> 观察 -> 裁剪 一系列变换的矩阵就统称为 MVP 变换矩阵 (Model View Projection Matrix)
二、立方体绘制
接下来我们动手应用MVP变换来绘制一个立方体,首先修改一下我们的顶点数据为立方体的顶点
float vertices[] = {-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,0.5f, -0.5f, -0.5f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f,0.5f, 0.5f, 0.5f, 1.0f, 1.0f,-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, 0.5f, 0.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,0.5f, -0.5f, -0.5f, 1.0f, 1.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,0.5f, -0.5f, 0.5f, 1.0f, 0.0f,-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,0.5f, 0.5f, -0.5f, 1.0f, 1.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,0.5f, 0.5f, 0.5f, 1.0f, 0.0f,-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
这次的顶点数据结构为顶点坐标 + UV坐标,所以我们需要修改一下顶点属性,去掉之前设置的顶点色
int step = 5, curStep = 0;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, step * sizeof(float), (void*)curStep);
glEnableVertexAttribArray(0);
curStep += 3;
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, step * sizeof(float), (void*)(curStep * sizeof(float)));
glEnableVertexAttribArray(1);
curStep += 2;
接下来我们来计算并设置MVP矩阵
// 全局定义宽高
float screenWidth = 800;
float screenHeight = 600;// ...// 由于MVP矩阵易变,所以在主循环中计算
// Model矩阵,进行旋转
glm::mat4 model = glm::mat4(1);
model = glm::rotate(model, glm::radians(50.0f), glm::vec3(1, 1, 0));// View矩阵,相当于相机向后退3个单位
glm::mat4 view = glm::mat4(1);
view = glm::translate(view, glm::vec3(0, 0, -3));// Projection矩阵
glm::mat4 projection = glm::mat4(1);
// 参数1:FOV值
// 参数2:宽高比
// 参数3:近平面距离 Near Plane
// 参数4:远平面距离 Far Plane
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);// 设置顶点着色器中的uniform变量
shader.SetMat4("model", glm::value_ptr(model));
shader.SetMat4("view", glm::value_ptr(view));
shader.SetMat4("projection", glm::value_ptr(projection));
其中,投影矩阵的相关参数可参考下图 (图片来自于LearnOpenGL)
修改顶点着色器
#version 330 core// 去掉顶点色
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;out vec2 texCoord;// MVP矩阵
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{// 注意顺序gl_Position = projection * view * model * vec4(aPos, 1.0);texCoord = aTexCoord;
}
片段着色器中去掉顶点色
#version 330 coreout vec4 FragColor;// 去掉顶点色
in vec2 texCoord;uniform sampler2D texture1;
uniform sampler2D texture2;void main()
{FragColor = mix(texture(texture1, texCoord), texture(texture2, texCoord), 0.2);
}
最后将绘制方式改为
glDrawArrays(GL_TRIANGLES, 0, 36);
编译运行,顺利的话可以看见下图
我们还可以使用glfwGetTime()
来让它旋转起来
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(1, 1, 0));
我们可以发现,这个立方体好像有点奇怪,我们无法辨别出他的内外,这是由于离我们远的平面由于后绘制的原因会盖在靠前的平面上
接下来我们就需要使用到 Z缓冲(Z - Buffer) 也称 深度缓冲 (Depth Buffer) 来解决这个问题
当片段要输出它的颜色时,OpenGL会将它的深度与Z Buffer中的这个位置的深度值进行比较,如果该片段的深度在已写入的深度值之后的话,就会丢弃该片段,反之将覆盖Z Buffer中的深度值,这个过程称为 深度测试 (Depth Testing)
我们可以通过以下代码来开启深度测试
glEnable(GL_DEPTH_TEST);
同时在清理时,除了颜色缓冲,我们还需要加上深度缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这样,我们就得到了一个正确的立方体
完整代码请见开头的git仓库