OpenGL多重渲染
在 OpenGL 中,多重渲染(Multiple Render Targets, MRT) 是一种高级渲染技术,允许在一次绘制调用中同时向多个颜色缓冲区写入数据。这项技术在现代图形编程中非常重要,尤其用于:
- 延迟渲染(Deferred Shading)
- G-Buffer 生成
- 多通道输出(如颜色 + 法线 + 深度等)
🎯 一、什么是多重渲染(MRT)
传统上,一个渲染管线的 fragment shader 只能输出到一个颜色缓冲:
out vec4 fragColor;
而启用了 MRT 后,可以输出到多个颜色缓冲,比如:
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
这些输出将同时写入绑定的多个帧缓冲目标。
🧱 二、如何使用 MRT(基本步骤)
1. 创建并绑定 FBO(Frame Buffer Object)
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2. 创建并附加多个颜色纹理作为输出目标
GLuint tex0, tex1;
// 创建颜色附件
glGenTextures(1, &tex0);
glBindTexture(GL_TEXTURE_2D, tex0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);// 第二个输出目标
glGenTextures(1, &tex1);
glBindTexture(GL_TEXTURE_2D, tex1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex1, 0);
3. 设置绘制的输出目标
GLenum drawBuffers[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};
glDrawBuffers(2, drawBuffers);
4. 在 Fragment Shader 中输出多个值
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;void main() {outColor0 = vec4(1.0, 0.0, 0.0, 1.0); // 红色outColor1 = vec4(0.0, 1.0, 0.0, 1.0); // 绿色
}
5. 使用这些纹理进行后续渲染处理(如合成、光照)
🧠 三、MRT 应用场景示例
🎮 延迟渲染 G-Buffer 输出:
渲染目标(Attachment) | 存储内容 |
---|---|
COLOR_ATTACHMENT0 | 片元位置(vec3) |
COLOR_ATTACHMENT1 | 法线(vec3) |
COLOR_ATTACHMENT2 | 漫反射颜色 |
COLOR_ATTACHMENT3 | 镜面反射等 |
这些缓冲用于后续的光照 pass,从而避免重复几何处理。
🔧 注意事项
-
必须检查 FBO 状态:
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {std::cerr << "Framebuffer not complete!" << std::endl; }
-
着色器输出的
location
必须和glDrawBuffers
顺序一致。 -
确保 GPU 支持 MRT(OpenGL 3.0+ 一般都支持)。
代码
// Deferred Shading with MRT - Basic Example using GLFW + GLAD + OpenGL 3.3 Core
// Requirements: GLFW, GLAD, stb_image, GLM#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>// Shader loading utility functions here (omitted for brevity, use your own loader)
GLuint LoadShader(const char* vertexPath, const char* fragmentPath);// Screen quad vertices
float quadVertices[] = {// positions // texcoords-1.0f, 1.0f, 0.0f, 1.0f,-1.0f, -1.0f, 0.0f, 0.0f,1.0f, -1.0f, 1.0f, 0.0f,-1.0f, 1.0f, 0.0f, 1.0f,1.0f, -1.0f, 1.0f, 0.0f,1.0f, 1.0f, 1.0f, 1.0f
};GLuint quadVAO, quadVBO;
void renderQuad() {if (quadVAO == 0) {glGenVertexArrays(1, &quadVAO);glGenBuffers(1, &quadVBO);glBindVertexArray(quadVAO);glBindBuffer(GL_ARRAY_BUFFER, quadVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));}glBindVertexArray(quadVAO);glDrawArrays(GL_TRIANGLES, 0, 6);glBindVertexArray(0);
}int main() {glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);GLFWwindow* window = glfwCreateWindow(1280, 720, "Deferred Shading", nullptr, nullptr);glfwMakeContextCurrent(window);gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);glEnable(GL_DEPTH_TEST);// Load shadersGLuint geometryPassShader = LoadShader("geometry.vs", "geometry.fs");GLuint lightingPassShader = LoadShader("lighting.vs", "lighting.fs");// Create G-bufferGLuint gBuffer;glGenFramebuffers(1, &gBuffer);glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);GLuint gPosition, gNormal, gAlbedoSpec;glGenTextures(1, &gPosition);glBindTexture(GL_TEXTURE_2D, gPosition);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 1280, 720, 0, GL_RGB, GL_FLOAT, nullptr);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);glGenTextures(1, &gNormal);glBindTexture(GL_TEXTURE_2D, gNormal);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 1280, 720, 0, GL_RGB, GL_FLOAT, nullptr);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);glGenTextures(1, &gAlbedoSpec);glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1280, 720, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };glDrawBuffers(3, attachments);GLuint rboDepth;glGenRenderbuffers(1, &rboDepth);glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1280, 720);glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)std::cout << "Framebuffer not complete!" << std::endl;glBindFramebuffer(GL_FRAMEBUFFER, 0);while (!glfwWindowShouldClose(window)) {glfwPollEvents();// Geometry PassglBindFramebuffer(GL_FRAMEBUFFER, gBuffer);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(geometryPassShader);// renderScene(geometryPassShader); // draw models hereglBindFramebuffer(GL_FRAMEBUFFER, 0);// Lighting PassglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(lightingPassShader);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, gPosition);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, gNormal);glActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);renderQuad();glfwSwapBuffers(window);
}
这个是使用 GLFW + GLAD 编写的完整延迟渲染(Deferred Shading)框架,包括:
- 几何阶段(Geometry Pass)生成 G-Buffer(位置、法线、颜色+镜面信息)
- 光照阶段(Lighting Pass)从 G-Buffer 中采样进行光照计算
- 使用屏幕空间四边形渲染最终图像
着色器文件:
geometry.glsl
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;
} vs_out;void main()
{vs_out.FragPos = vec3(model * vec4(aPos, 1.0));vs_out.Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.TexCoords = aTexCoords;gl_Position = projection * view * vec4(vs_out.FragPos, 1.0);
}
geometry.glsl
#version 330 core
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;in VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;
} fs_in;uniform sampler2D texture_diffuse;
uniform sampler2D texture_specular;void main()
{gPosition = fs_in.FragPos;gNormal = normalize(fs_in.Normal);vec3 albedo = texture(texture_diffuse, fs_in.TexCoords).rgb;float spec = texture(texture_specular, fs_in.TexCoords).r;gAlbedoSpec = vec4(albedo, spec);
}
lighting.glsl
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;out vec2 TexCoords;void main()
{TexCoords = aTexCoords;gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
lighting.glsl
#version 330 core
out vec4 FragColor;in vec2 TexCoords;uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;void main()
{vec3 FragPos = texture(gPosition, TexCoords).rgb;vec3 Normal = normalize(texture(gNormal, TexCoords).rgb);vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;float Specular = texture(gAlbedoSpec, TexCoords).a;vec3 lightPos = vec3(10.0, 10.0, 10.0);vec3 lightColor = vec3(1.0);vec3 viewPos = vec3(0.0, 0.0, 5.0);// diffusevec3 lightDir = normalize(lightPos - FragPos);float diff = max(dot(Normal, lightDir), 0.0);// specularvec3 viewDir = normalize(viewPos - FragPos);vec3 reflectDir = reflect(-lightDir, Normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0) * Specular;vec3 lighting = (diff + spec) * lightColor * Albedo;FragColor = vec4(lighting, 1.0);
}