当前位置: 首页 > web >正文

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);
}
http://www.xdnf.cn/news/9175.html

相关文章:

  • 基于Robust Video Matting 使用Unity 实现无绿幕实时人像抠图
  • GJOI 5.24 题解
  • 时空弯曲和测地线浅谈
  • 开卡包的期望
  • 第12次03 :登录状态的保持
  • 一个简单的系统插桩实现​
  • 龙虎榜——20250526
  • C++虚函数和纯虚函数
  • 云原生技术在企业数字化转型中的战略价值与实践路径
  • MySql(三)
  • 高精度装配人形机器人|产品参数详细介绍
  • Day03
  • 架空线路智能云台监控系统介绍
  • 大数据学习(122)-分区与分桶表
  • 【前端】Proxy对象在控制台中惰性求值_vue常见开发问题
  • AI换场景工具:图生生1分钟完成电商商拍
  • Vue 样式穿透(深度选择器)::v-deep
  • 多空间投影:提示微调的革命性突破
  • 车载通信网络 --- OSI模型中物理层和数据链路层
  • 【Netty】- 聊天室1
  • sse和streamablehttp
  • 基于Windows原生工具搭建本地文件服务器 IIS(Internet Information Services)​
  • STM32G0xx基于串口(UART)Ymodem协议实现OTA升级包括Bootloader、上位机、应用程序
  • 两个Ubuntu机器(内网)免密登录设置
  • MFC: 文件加解密(单元测试模块)
  • 如何做好一份“系统设计“文档
  • 2025河北秦皇岛CCPC补题
  • I/O外设管理(第七章)
  • 本周 edu教育邮箱注册可行方案
  • Python函数异常处理底层实现原理