高级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)现象。解决方法:
- 永远不要把多个物体摆得太靠近
- 将近平面设置得远一些
- 使用更高精度的深度缓冲
模板测试
模板测试允许根据模板缓冲中的值来决定是否丢弃片段。
启用模板测试
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 设置调试回调。