跳到主要内容

高级OpenGL

本章介绍OpenGL的高级特性,包括深度测试、模板测试、混合、帧缓冲、立方体贴图等技术。

深度测试

深度测试决定哪些片段应该被显示,哪些应该被丢弃。

启用深度测试

glEnable(GL_DEPTH_TEST);

// 每帧清除深度缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

深度测试函数

glDepthFunc(GL_LESS);  // 默认

可选值:

  • GL_ALWAYS:总是通过
  • GL_NEVER:总是不通过
  • GL_LESS:小于时通过
  • GL_EQUAL:等于时通过
  • GL_LEQUAL:小于等于时通过
  • GL_GREATER:大于时通过
  • GL_NOTEQUAL:不等于时通过
  • GL_GEQUAL:大于等于时通过

深度值精度

深度缓冲通常使用16、24或32位精度。非线性深度映射使近处物体有更高的精度:

// 查看深度位精度
int depthBits;
glGetIntegerv(GL_DEPTH_BITS, &depthBits);

深度冲突

当两个面非常接近时,会出现深度冲突(Z-fighting)现象。解决方法:

  1. 永远不要把多个物体摆得太靠近
  2. 将近平面设置得远一些
  3. 使用更高精度的深度缓冲

模板测试

模板测试允许根据模板缓冲中的值来决定是否丢弃片段。

启用模板测试

glEnable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

模板操作

glStencilFunc(GL_EQUAL, 1, 0xFF);   // 比较函数
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // 操作

轮廓效果示例

// 1. 启用模板测试
glEnable(GL_STENCIL_TEST);

// 2. 绘制物体时写入模板缓冲
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilMask(0xFF);
glClear(GL_STENCIL_BUFFER_BIT);

// 绘制物体
DrawObject();

// 3. 绘制放大的轮廓
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);

// 绘制放大的物体
glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(1.1f));
shader.setMat4("model", scale);
DrawObject();

glEnable(GL_DEPTH_TEST);
glStencilMask(0xFF);

混合

混合用于实现透明效果。

启用混合

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

混合方程

glBlendEquation(GL_FUNC_ADD);  // 默认

可选值:

  • GL_FUNC_ADD:C = SrSr + DrDr
  • GL_FUNC_SUBTRACT:C = SrSr - DrDr
  • GL_FUNC_REVERSE_SUBTRACT:C = DrDr - SrSr

绘制透明物体

透明物体需要从远到近排序:

std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++) {
float distance = glm::length(camera.Position - windows[i]);
sorted[distance] = windows[i];
}

for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) {
model = glm::translate(glm::mat4(1.0f), it->second);
shader.setMat4("model", model);
DrawWindow();
}

帧缓冲

帧缓冲允许将渲染结果输出到自定义缓冲区。

创建帧缓冲

GLuint framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

// 创建纹理附件
GLuint textureColorbuffer;
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);

// 创建深度/模板附件
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

// 检查完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "Framebuffer not complete!" << std::endl;

glBindFramebuffer(GL_FRAMEBUFFER, 0);

使用帧缓冲

// 渲染到帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 场景渲染...
DrawScene();

// 渲染到默认帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);

// 使用帧缓冲纹理
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
DrawScreenQuad();

后处理效果

// 反色
FragColor = vec4(1.0 - texture(screenTexture, TexCoords).rgb, 1.0);

// 灰度
FragColor = texture(screenTexture, TexCoords);
float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
FragColor = vec4(average, average, average, 1.0);

// 锐化
float offset = 1.0 / 300.0;
vec2 offsets[9] = vec2[](
vec2(-offset, offset), vec2( 0.0f, offset), vec2( offset, offset),
vec2(-offset, 0.0f), vec2( 0.0f, 0.0f), vec2( offset, 0.0f),
vec2(-offset, -offset), vec2( 0.0f, -offset), vec2( offset, -offset)
);

float kernel[9] = float[](
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
);

vec3 sampleTex[9];
for(int i = 0; i < 9; i++)
sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
vec3 col = vec3(0.0);
for(int i = 0; i < 9; i++)
col += sampleTex[i] * kernel[i];

FragColor = vec4(col, 1.0);

立方体贴图

立方体贴图由6个2D纹理组成,用于环境映射。

创建立方体贴图

GLuint textureID;
glGenTextures(1, &textureID);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

// 6个面的纹理
std::vector<std::string> faces = {
"right.jpg", "left.jpg",
"top.jpg", "bottom.jpg",
"front.jpg", "back.jpg"
};

for (GLuint i = 0; i < faces.size(); i++) {
int width, height, nrChannels;
unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height,
0, GL_RGB, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
}

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

天空盒

// 顶点着色器
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main() {
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww; // 深度设为w,确保天空盒在最远处
}
// 片段着色器
#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main() {
FragColor = texture(skybox, TexCoords);
}

环境反射

// 反射
vec3 I = normalize(Position - cameraPos);
vec3 R = reflect(I, normalize(Normal));
FragColor = vec4(texture(skybox, R).rgb, 1.0);

// 折射
float ratio = 1.0 / 1.52;
vec3 R = refract(I, normalize(Normal), ratio);
FragColor = vec4(texture(skybox, R).rgb, 1.0);

高级数据

分离顶点属性

// 位置数据
float positions[] = { ... };
// 法线数据
float normals[] = { ... };
// 纹理坐标
float texCoords[] = { ... };

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals) + sizeof(texCoords), NULL, GL_STATIC_DRAW);

// 分配缓冲区
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(texCoords), texCoords);

复制缓冲

glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 
readOffset, writeOffset, size);

几何着色器

几何着色器可以在顶点着色器和片段着色器之间生成新图元。

#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;

out vec2 TexCoords;

uniform mat4 projection;

void build_quad(vec4 position) {
gl_Position = position + vec4(-0.05, 0.05, 0.0, 0.0);
TexCoords = vec2(0.0, 1.0);
EmitVertex();

gl_Position = position + vec4(0.05, 0.05, 0.0, 0.0);
TexCoords = vec2(1.0, 1.0);
EmitVertex();

gl_Position = position + vec4(-0.05, -0.05, 0.0, 0.0);
TexCoords = vec2(0.0, 0.0);
EmitVertex();

gl_Position = position + vec4(0.05, -0.05, 0.0, 0.0);
TexCoords = vec2(1.0, 0.0);
EmitVertex();

EndPrimitive();
}

void main() {
build_quad(gl_in[0].gl_Position);
}

实例化

实例化可以高效渲染大量相同物体。

// 设置实例化属性
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * 100, translations, GL_STATIC_DRAW);

glBindVertexArray(VAO);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glVertexAttribDivisor(2, 1); // 每个实例更新一次

// 绘制
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

抗锯齿

多重采样

glEnable(GL_MULTISAMPLE);

创建多重采样帧缓冲

glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

glGenTextures(1, &textureColorBufferMultiSampled);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGB, 800, 600, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, textureColorBufferMultiSampled, 0);

小结

高级OpenGL技术可以显著提升渲染效果:

  • 深度测试和模板测试控制片段可见性
  • 混合实现透明效果
  • 帧缓冲用于后处理
  • 立方体贴图实现环境映射
  • 实例化高效渲染大量相同物体

下一章我们将学习 模型加载,了解如何加载和渲染3D模型。

调试建议

使用 glGetError() 检查OpenGL错误,或使用 glDebugMessageCallback 设置调试回调。