OpenGL 编程/科学 OpenGL 教程 05
在 上一个教程 中,我们使用网格线绘制了三维图形。这使您可以透视图形,但对于更复杂的函数,这可能相当令人困惑。将图形绘制为连续的、不透明的表面会更理想。在本教程中,我们将了解如何做到这一点。
由于我们的图形不是封闭表面,而是一个带有凸起的片状物体,因此根据方向,有可能看到图形的两个侧面。当 OpenGL 绘制三角形时,它会自动确定三角形的哪个侧面朝向相机,即正面或背面(另请参阅 双面表面 GLSL 教程)。我们将稍微更改上一个教程中的片段着色器,以使朝向背面的三角形仅绘制为正面三角形亮度的一半,从而可以轻松区分我们正在查看图形的哪一侧。我们还将介绍统一颜色,以便我们可以根据绘制的是表面还是网格来调节颜色。
#version 120
varying vec4 graph_coord;
uniform vec4 color;
void main(void) {
float factor;
if(gl_FrontFacing)
factor = 1.0;
else
factor = 0.5;
gl_FragColor = (graph_coord / 2.0 + 0.5) * color * factor;
}
在上一个教程中,我们创建了一个 VBO,它包含图形的所有 x 和 y 坐标。我们还创建了一个 IBO,它追踪水平和垂直网格线。要绘制表面,我们将重用 VBO,但必须创建一个新的 IBO 来描述如何绘制组成图形表面的三角形。
GLushort indices[100 * 100 * 6];
int i = 0;
// Triangles
for(int y = 0; y < 100; y++) {
for(int x = 0; x < 100; x++) {
indices[i++] = y * 101 + x;
indices[i++] = y * 101 + x + 1;
indices[i++] = (y + 1) * 101 + x + 1;
indices[i++] = y * 101 + x;
indices[i++] = (y + 1) * 101 + x + 1;
indices[i++] = (y + 1) * 101 + x;
}
}
GLuint surface_ibo;
glGenBuffers(1, &surface_ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surface_ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof indices, indices, GL_STATIC_DRAW);
要使用新着色器绘制表面,我们使用以下命令。
GLfloat white[4] = {1, 1, 1, 1};
glUniform4fv(uniform_color, 1, white);
glEnableVertexAttribArray(attribute_coord2d);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer(attribute_coord2d, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surface_ibo);
glDrawElements(GL_TRIANGLES, 100 * 100 * 6, GL_UNSIGNED_SHORT, 0);
如果这样做,并且旋转图形,您会注意到,根据方向,表面并不总是正确绘制。在最远端的三角形首先绘制的方向上,没有问题。但是,当最靠近端的三角形首先绘制时,更靠后的三角形可能会覆盖最靠近端的三角形。为了防止这种情况发生,当然我们需要启用深度缓冲区。在main()函数中,使用
glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
并且在init_resources()中添加
glEnable(GL_DEPTH_TEST);
也不要忘记在绘制新帧之前清除深度缓冲区。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
练习
- 我们不能简单地反转绘制三角形的顺序吗?也许在glVertexAttribPointer()调用中使用一个负步长参数?
- 尝试以各种方式更改三角形中顶点的顺序。
- 尝试通过使用 glEnable(GL_CULL_FACE) 和 glCullFace(GL_FRONT_AND_BACK) 来获得相同的结果,而无需指定背面三角形。
虽然表面有其吸引力,但更难看到细微的曲线。在表面上绘制网格会很不错。我们已经知道如何做到这一点,因此我们只需切换回网格 IBO,并在绘制完表面后使用 GL_LINES 进行绘制。但是,我们必须为网格线指定与表面不同的颜色。让我们使它们比表面亮两倍。
GLfloat bright[4] = {2, 2, 2, 1};
glUniform4fv(uniform_color, 1, bright);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_LINES, 100 * 101 * 4, GL_UNSIGNED_SHORT, 0);
当您这样做时,您会注意到两件事。网格线看起来很糟糕,但您再也看不到“隐藏的”网格线了。这是因为深度测试默认情况下会确保仅绘制比该位置任何现有片段更靠近相机的片段。表面首先绘制,因此不会绘制任何位于其后面的片段。但是,如果网格线使用与表面相同的顶点,您会期望深度完全相同。使用默认深度测试,您会认为根本不应该出现任何网格线。实际上,线条的绘制方式与三角形不同,浮点舍入误差会导致一些网格片段略微位于表面前面,而一些略微位于表面后面。
要修复网格线,它们应该比表面更靠近相机,或者表面应该比表面更远。我们可以通过对 MVP 矩阵应用平移来做到这一点,但 OpenGL 中有一个专门解决此问题的调用。
glPolygonOffset(1, 1);
glEnable(GL_POLYGON_OFFSET_FILL);
这将导致三角形(但不是线条或点)以略微增加的深度值绘制,结果是网格线现在将按预期出现。
练习
- 使用 F4 键使您可以打开和关闭多边形偏移。
- 默认深度测试函数为“小于”(GL_LESS)。尝试使用glDepthFunc(GL_LEQUAL)将其更改为“小于或等于”。这有帮助吗?
- 尝试不同的glPolygonOffset()值,更大或负数。
- 在绘制表面和网格之间,尝试清除深度缓冲区或颜色缓冲区。
- 是否可以在绘制表面之前绘制网格?