Skip to content

延迟渲染

Forward Rendering

先简单说下最早的前向渲染 —— 古早,但是符合直觉,因为它的核心单位是物体

用一份伪代码说明它的思路 ——

text
ForEach 每个可见物体 (Object):
    ForEach 场景中的每个光源 (Light):
        计算该光源对该物体像素的光照贡献
    将最终颜色写入帧缓冲 (FrameBuffer)

也就是每次取出一个物体,然后计算场景内的所有光源对他的影响,最后计算出颜色后画到屏幕

非常符合直觉,也有许多优势 ——

  • 材质表现自由度高:可以针对每个物体单独编写着色器
  • 完美支持硬件抗锯齿(MSAA):可以在几何体光栅化阶段利用硬件进行多重采样,得到极其锐利平滑的边缘。
  • 内存带宽占用小
  • 在光源较少时的极高性能

但是显然来看,它存在着

  • 无效计算 —— 假设花费了大力气渲染了一些 Objects,但是它们有可能再之后被其他物体遮住,导致计算无意义
  • 时间复杂度高 —— 渲染开销是 O(物体数量×光源数量)。光源越多,性能越差

为了解决 Forward Rendering 的痛点,于是提出延迟渲染

延迟渲染

一种空间换时间的方法,简单来说,把渲染过程分成两个pass

  • 先将所有物体信息压入到 G-Buffers
  • 接着在计算光源着色的时候只需要逐像素进行着色即可

时间复杂度降到了 O(N物体+P屏幕像素×M光源)

G-Buffer

Geometric Buffer 几何缓冲区。

首先遍历场景中所有可见的不透明物体,不进行着色,而是计算其几何信息 —— 计算所有之后进行光照着色所需要的信息并且存储到 G-buffer 中,比如

  • Base Color
  • Normal
  • Depth
  • Material

G-Buffer 的大小和分辨率应当一致,因为后期需要逐像素遍历进行着色。

当然,如果得把场景画好几遍才能收集齐所有的表面属性,这显然太贵了 —— 使用 MRT (多重渲染目标, Multiple Render Targets)技术做优化

光照阶段

抛弃原先遍历场景中的 3D 物体的方式,直接通过 G-Buffer 记录的信息进行光照计算

首先世界坐标重建:通过像素的屏幕 UV 和 G-Buffer 中的 Depth,乘以“视图投影逆矩阵”,利用透视除法反推出世界坐标

完整组装渲染管线

  1. 视锥体剔除
  2. 深度预处理
  3. 几何阶段
  4. 阴影
  5. 光照
  6. 正向渲染补偿(把半透明物体等叠到渲染结果上
  7. 后处理

缺点

1. 显存带宽爆炸!

G-Buffer 的存储是很贵的,而且在需要存储多信息的情况下,更是贵的不行

在几何计算的时候哗啦哗啦写数据,在光照阶段又必须要全部读出来

当然,通过分块延迟渲染(TBDR)或者其他什么机制,也可以缓解这个问题

2. MSAA 失效

在正向渲染里,硬件 MSAA 可以非常丝滑地处理三角形边缘的锯齿。但延迟渲染把光照推迟到了 2D 的 G-Buffer 上算。在 2D 像素层面,硬件不知道三角形的几何边缘在哪里,MSAA 就抓瞎了。 如果强行对 G-Buffer 做 MSAA,意味着 G-Buffer 的大小要翻 4 倍(4x MSAA),显存直接原地爆炸。这就是为什么现在 3A 大作全都在卷 TAA、DLSS、FSR(时间轴或 AI 抗锯齿),因为传统的 MSAA 在延迟管线下用不了了。

3. 材质

首先无法渲染半透明的物体 —— 由于 G-Buffer 无法在一个像素点记录前后重叠的多个材质状态,所以需要依赖一条额外的正向渲染 Pass 补救

其次,在延迟渲染中,所有材质的最终属性都必须塞进同一个规格的 G-Buffer 里,所以导致假设要多加一个参数,就需要一张完整的 Buffer,白白浪费内存显存空间

Released under the MIT License.