延迟渲染

延迟渲染

引入延迟渲染

  • 如果已经学习了一段时间的渲染,但还不知道什么是延迟渲染,那么之前用的渲染方式很有可能就是正向渲染(Forward Rendering)
  • 之所以没有明确的提出正向渲染这个概念,可能是因为场景渲染情况比较简单,渲染过程没有用到很特殊的技巧,就是一套按部就班组成的顺理成章,所以没有必要再提出一个新的概念
  • 但是,当场景变的复杂时,比如有一堆光源和物体,每个光源和每个物体都要进行光照计算,这时再来看看之前“按部就班”的渲染方式:
  • 在渲染时如果场景中有多个光源,每个物体的每个顶点都要遍历所有的光源,并计算每个光源的光照;然后再计算下个顶点。这种顺序式的渲染看似理所当然,但引起了严重的效率问题:每个顶点都要进行多次光照计算,关键是大量被挡住的顶点进行了很多次无用的计算。
  • 为了解决这种效率问题,产生了一种新的渲染方式,这种方式分两步完成。第一步:先将场景中的物体渲染到纹理;第二步:根据得到的纹理,逐像素的计算每个光源的光照。这样在保证相同光照效果的情况下,省去了很多无用的计算,从而提高效率。这种渲染方式叫做延迟渲染(Deferred Rendering)

引入GBuffer

  • 延迟渲染是为了解决复杂场景的渲染效率,场景复杂的原因基本离不开光照,而光照的计算又离不开顶点位置、法线、颜色、光源位置、摄像机位置等一系列数据
  • 为了提供以上数据,延迟渲染在第一个渲染过程中用了两个技巧
  • 第一个技巧:将物体的顶点位置、法线、颜色分别存储到不同的纹理当中
  • 因为这些纹理保存的都是物体的几何数据,所以就把他们叫做GBuffer(Geometry Buffer),这个渲染过程也叫做几何处理阶段
  • 第二个技巧:运用多渲染目标,实现一次渲染生成多个纹理

两个阶段

几何着色阶段

  • 在客户端创建GBuffer
  • 注意最后两行代码与片段着色器中的输出变量对应,是实现多渲染目标的关键
GLuint gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);

GLuint gPosition, gNormal, gColor;
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
//...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);

glGenTextures(1, &gNormal);
//...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);

glGenTextures(1, &gColor);
//...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gColor, 0);

//MRT
GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments);
  • 顶点着色器
  • 注意顶点位置坐标和法线坐标都转换到了世界空间中
void main()
{
    vec4 worldPos = model * vec4(aPos, 1.0);
    FragPos = worldPos.xyz; 
    TexCoords = aTexCoords;
    mat3 normalMatrix = transpose(inverse(mat3(model)));
    Normal = normalMatrix * aNormal;
    gl_Position = projection * view * worldPos;
}
  • 片段着色器
  • 注意漫反射和镜面反射强度合并存储在一个纹理中
layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gColor;
in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;
uniform sampler2D texture_diffuse1;
uniform sampler2D texture_specular1;
void main()
{
    gPosition = FragPos;
    gNormal = normalize(Normal);
    gColor.rgb = texture(texture_diffuse1, TexCoords).rgb;//存储漫反射
    gColor.a = texture(texture_specular1, TexCoords).r;//存储镜面强度
}
  • 位置纹理
    在这里插入图片描述
  • 法线纹理
    在这里插入图片描述
  • 颜色纹理
    在这里插入图片描述

延迟光照处理阶段

  • 在客户端绑定GBuffer中的纹理,并传入每个光源的位置
//...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gColor);
//...
  • 片段着色器
void main()
{             
    vec3 FragPos = texture(gPosition, TexCoords).rgb;
    vec3 Normal = texture(gNormal, TexCoords).rgb;
    vec3 Diffuse = texture(gColor, TexCoords).rgb;
    float Specular = texture(gColor, TexCoords).a;

    for(int i = 0; i < NR_LIGHTS; ++i)
    {
     //...
    }
    FragColor = vec4(lighting, 1.0);
}
  • 在模型之间加上若干光源
    在这里插入图片描述

副作用

不能混合

  • 延迟渲染其中一个缺点就是它不能进行混合(Blending),因为GBuffer中所有的数据都是从一个单独的片段中来的,而混合需要对多个片段的组合进行操作
  • 解决办法:将延迟渲染与正向渲染结合。
  • 首先是进行延迟渲染,然后利用glBlitFramebuffer(…)将几何渲染阶段的深度缓冲复制到默认帧缓冲的深度缓冲,最后结合深度缓冲进行正向渲染
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 写入到默认帧缓冲 glBlitFramebuffer( 0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST ); glBindFramebuffer(GL_FRAMEBUFFER, 0);

for (GLuint i = 0; i < lightPositions.size(); i++)
{
    //...
}
  • 将模型之间的光源显示出来
    在这里插入图片描述

不能MSAA?

  • 理论上,延迟渲染是可以进行MSAA的,但不是在光照处理阶段进行,而是在几何着色阶段进行。
  • 实际上,在几何着色阶段进行MSAA时,会对显存造成极大的浪费,所以大家都不在延迟渲染上做MSAA
  • 几何着色阶段一般都会使用多渲染目标(MRT),而MRT是硬件提供的一种能力。在十多年前的DX9时代,他不支持MSAA的MRT,这也是延迟渲染不能MSAA的一个重要原因。但从DX10以来,就支持了带MSAA的MRT

对比正向渲染

  • 在复杂的场景中,延迟渲染的效率会比正向渲染有很大的提升,因为每个像素只运行一次片段着色器;而正向渲染每个物体的每个顶点都要运行多次片段着色器
  • 然而对于简单的场景,延迟渲染的效率并不一定比正向渲染高
  • 正向渲染可以实现的效果同样可以在延迟渲染中实现
  • 延迟渲染有上面提到的副作用
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值