现在就引入了坐标系统的概念。先前我们算是直接在裁剪空间上绘制物体(裁剪空间到屏幕空间的转换OpenGL自动帮我们处理)。
LearnOpenGL的图
其中比较有意思的就是在裁剪空间中,会存在一个叫作透视除法的东西。
透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。
碰巧看到了Sebastian Lague大神制作传送门的视频。其中4:35-5:50这一段很形象的展示了w分量的作用,以及从观察空间到裁剪空间的效果。视频:Youtube、BiliBili
为上述的每一个步骤创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标:
Vclip = Mprojection ⋅ Mview ⋅ Mmodel ⋅ Vlocal
注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。
我们需要在顶点着色器中传出裁剪空间的坐标。根据公式我们还需要三个矩阵,如下。
glm::mat4 model; // 模型矩阵
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); // 在x上旋转-55度
glm::mat4 view; // 观察矩阵
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); // 摄像机在(0,0,3),即物体相反移动 为移动(0,0,-3)
glm::mat4 projection; // 投影矩阵
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); // Fov是45度,屏幕的宽高比是800/600,近的裁剪面距离是0.1,远面距离100
然后在顶点着色器里面写一下uniform,乘起来(注意顺序)。
#version 330 core
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;
layout(location = 2) in vec2 aTexCoord;
out vec3 Color;
out vec2 TexCoord;
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
Color = aColor;
TexCoord = aTexCoord;
}
在渲染循环中传入三个矩阵就完成了(用的我之前封装的Shader类的函数)。
/*Uniform*/
myShader.setMat4("model", model);
myShader.setMat4("view", view);
myShader.setMat4("projection", projection);
教程给了个立方体的36个顶点(吐槽:怎么突然就不用EBO了,本来就8个顶点完事)。改成要用
glDrawArrays的方式,就要改很多地方了。首先EBO都没用了,删掉。然后glDrawElements改成Arrays。顶点数据换一下。运行???
这是什么鬼畜情况。
仔细想了想原来是新的顶点数据里面少了颜色的数据,VAO的设置也要改。(原教程就给个顶点,其他啥都不说,(。・∀・)ノ゙害)
好了,可以“正常”显示了。现在就是不能正确的判断哪些像素在前哪些在后。
// 顶点数据
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
};
解决刚才的问题就需要用到深度测试,在同一个位置的像素通过比较z缓存上的数值就可以知道哪个在前哪个在后,从而舍弃掉不会显示的像素。
在渲染循环前开启即可。
glEnable(GL_DEPTH_TEST);
然后我们还要清除色彩和深度缓存,要在渲染循环中交换颜色缓存后清空。我之前放在交换颜色缓冲前清空画面就会变黑。 重新更正一下,其实之前变黑不是在交换颜色缓前清空的原因。是因为我在绘制之后清空了((。・∀・)ノ゙害)。所以应该是在绘制之前清空,清空颜色缓存之前写过了,现在加上一个清空深度缓冲就可以了。
至此看到这个位运算符我终于明白,原来他是用二进制位来表示的意思。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
然后可以让他不停旋转。
model = glm::rotate(model, 0.002f * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
定义十个位置的向量。
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
在渲染循环中改成渲染十次。
glBindVertexArray(VAO);
myShader.use();
for (unsigned int i = 0; i < 10; i++)
{
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
myShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}