【《Real-Time Rendering 3rd》 提炼总结】(七) 第七章续 · 延迟渲染(Deferred Rendering)的前生今世



本文由@浅墨_毛星云 出品,转载请注明出处。  
文章链接: http://blog.csdn.net/poem_qianmo/article/details/77142101




题图为基于Deferred Rendering技术的渲染效果图。

 

 

在计算机图形学中,延迟渲染( Deferred Rendering) ,即延迟着色(Deferred Shading),是将着色计算延迟到深度测试之后进行处理的一种渲染方法。延迟着色技术的最大的优势就是将光源的数目和场景中物体的数目在复杂度层面上完全分开,能够在渲染拥有成百上千光源的场景的同时依然保持很高的帧率,给我们渲染拥有大量光源的场景提供了很多可能性。


图1 使用Deferred Rendering方法渲染的多光源场景

 

在《Real-Time Rendering 3rd》(实时渲染图形学第三版)的第七章“Advanced Shading · 高级着色”中,除了上篇文章中我们聊到的BRDF,还有Deferred Shading(延迟着色)这个重要概念我们没有聊到。这篇文章,就主要和大家一起聊一聊Deferred Shading和它的“前生今世”,以及文末简单提一提第八章“区域和环境光照 Area and Environmental Lighting”中的Environment Mapping(环境映射)相关的内容。下篇文章,预计直接开始全局光照(Global Illumination)的内容。

 

简而言之,通过阅读这篇文章,你将对以下要点有所了解:

  • 延迟着色/延迟渲染的概念 Deferred Shading / Deferred Rendering
  • 几何缓冲区 G-buffer
  • 延迟渲染的渲染过程
  • 延迟渲染 vs 正向渲染 Deferred Rendering vs Forward Rendering
  • 延迟渲染的优缺点
  • 延迟光照 Light Pre-Pass / Deferred Lighting
  • 分块延迟渲染Tile-Based Deferred Rendering
  • 延迟渲染 vs 延迟光照 Deferred Rendering vs DeferredLighting
  • 实时渲染中常见的Rendering Path总结
  • 环境映射Environment Mapping

 

 


一、延迟渲染 Deferred Rendering



延迟渲染( Deferred Rendering),即延迟着色(Deferred Shading),顾名思义,是将着色计算延后进行处理的一种渲染方法,在2004年的GDC上被正式提出http://www.tenacioussoftware.com/gdc_2004_deferred_shading.ppt

 

我们知道,正向渲染(Forward Rendering),或称正向着色(Forward Shading),是渲染物体的一种非常直接的方式,在场景中我们根据所有光源照亮一个物体,之后再渲染下一个物体,以此类推。


传统的正向渲染思路是,先进行着色,再进行深度测试。其的主要缺点就是光照计算跟场景复杂度和光源个数有很大关系。假设有n个物体,m个光源,且每个每个物体受所有光源的影响,那么复杂度就是O(m*n)。


正向渲染简单直接,也很容易实现,但是同时它对程序性能的影响也很大,因为对每一个需要渲染的物体,程序都要对每个光源下每一个需要渲染的片段进行迭代,如果旧的片段完全被一些新的片段覆盖,最终无需显示出来,那么其着色计算花费的时间就完全浪费掉了。

 

而延迟渲染的提出,就是为了解决上述问题而诞生了(尤其是在场景中存在大量光源的情况下)。

延迟着色给我们优化拥有大量光源的场景提供了很多可能性,因为它能够在渲染拥有成百上千光源的场景的同时还能够保持能让人接受的帧率。下面这张图展示了一个基于延迟着色渲染出的场景,这个场景中包含了1000个点光源,对于目前的硬件设备而言,用传统的正向渲染来实现几乎是不可能的。


图2 基于Deferred Rendering 渲染的含1000个点光源的场景 [J. Andersson, SIGGRAPH 2009 Beyond Programmable shading course talk] @ Frostbite 2引擎

 

可以将延迟渲染( Deferred Rendering)理解为先将所有物体都先绘制到屏幕空间的缓冲(即G-buffer,Geometric Buffer,几何缓冲区)中,再逐光源对该缓冲进行着色的过程,从而避免了因计算被深度测试丢弃的⽚元的着色而产⽣的不必要的开销。也就是说延迟渲染基本思想是,先执行深度测试,再进行着色计算,将本来在物空 间(三维空间)进行光照计算放到了像空间(二维空间)进行处理。


对应于正向渲染O(m*n)的 复杂度,经典的延迟渲染复杂度为O(n+m)。

 


图3 Unreal Engine 3中实现的Deferred Shading  @GDC 2011

 

Jimmikaelkael在2016年12月24日发推文(https://twitter.com/jimmikaelkael/status/812631802242273280),分享了一组在Unity3D中基于deferred shading渲染的SpeedTree场景,非常逼真:

 


图4 SpeedTree deferred shading with translucency @Unity3D引擎  by jimmikaelkael



图5 SpeedTree deferred shading with translucency @Unity3D引擎  by jimmikaelkael



图6 SpeedTree deferred shading with translucency @Unity3D引擎  by jimmikaelkael



图7 SpeedTree deferred shading with translucency @Unity3D引擎  by jimmikaelkael

 

延迟着色中一个非常重要的概念就是G-Buffer,下面先聊一下G-Buffer。

 



二、几何缓冲区 G-buffer

 


G-Buffer,全称Geometric Buffer ,译作几何缓冲区,它主要用于存储每个像素对应的位置(Position),法线(Normal),漫反射颜色(Diffuse Color)以及其他有用材质参数。根据这些信息,就可以在像空间(二维空间)中对每个像素进行光照处理。

 


图8 一个典型的G-buffer layout。Source: W. Engel, “Light-Prepass Renderer Mark III” @SIGGRAPH 2009Talks

 

下面是一帧中G-buffer中存储的内容:


图9 G-buffer存储的信息

 



三、延迟渲染的过程分析

 


可以将延迟渲染理解为两个Pass的过程:

 

1、几何处理阶段(Geometry Pass)。这个阶段中,我们获取对象的各种几何信息,并将第二步所需的各种数据储存(也就是渲染)到多个G-buffer中;

2、光照处理阶段(Lighting Pass)。在这个pass中,我们只需渲染出一个屏幕大小的二维矩形,使用第一步在G-buffer中存储的数据对此矩阵的每一个片段计算场景的光照;光照计算的过程还是和正向渲染以前一样,只是现在我们需要从对应的G-buffer而不是顶点着色器(和一些uniform变量)那里获取输入变量了。

 

下面这幅图片很好地展示了延迟着色的整个过程:


图10 延迟着色的过程

 

延迟渲染方法一个很大的好处就是能保证在G-buffer中的片段和在屏幕上呈现的像素所包含的片段信息是一样的,因为深度测试已经最终将这里的片段信息作为最顶层的片段。这样保证了对于在光照处理阶段中处理的每一个像素都只处理一次,所以我们能够省下很多无用的渲染调用。除此之外,延迟渲染还允许我们做更多的优化,从而渲染更多的光源。

 

在几何处理阶段中填充G-buffer非常高效,因为我们直接储存位置,颜色,法线等对象信息到帧缓冲中,这个过程几乎不消耗处理时间。

 

而在此基础上使用多渲染目标(Multiple Render Targets, MRT)技术,我们可以在一个Pass之内完成所有渲染工作。

 

总结一下,典型的Deferred Rendering 的渲染流程有两步:

 

1. 几何处理阶段:渲染所有的几何/颜色数据到G-buffer

2. 光照处理阶段:使用G-buffer计算场景的光照。

 

 

四、延迟渲染的伪代码

 

为了便于理解,这里贴出一些各种描述版本的延迟渲染算法的伪代码:

 

1、通用版本的延迟着色算法伪代码:

For each object:
	Render to multiple targets
For each light:
	Apply light as a 2D postprocess

 

2、一个通用版本的deferred shading过程描述:

“Standard”deferred shading is a 2-stage process:
(1) draw (opaque) geometry storing its attributes (i.e. position as depth,normals, albedo color, specular color and other material properties) in anumber of full screen buffers (typically 3 or 4)
(2) for each light source, draw its volume and accumulate lit surfacecolor into final render target

 

3、两个Pass的延迟着色算法伪代码:

Two-pass deferred shading algorithm
Pass 1: geometry pass
- Write visible geometry information to G-buffer
Pass 2: shading pass
For each G-buffer sample, compute shading
- Read G-buffer data for current sample
- Accumulate contribution of all lights
- Output final surface color

 

4、多光源的延迟渲染的伪代码:

Many-light deferred shading algorithm
For each light:
-Generate/bind shadow/environment maps
-Compute light’s contribution for each G-buffer sample:
For each G-buffer sample
-Load G-buffer data
-Evaluate light contribution (may be zero)
-Accumulate contribution into frame-buffer

可以将这里的多光源计算过程理解为,对每个光源创建一个屏幕空间包围矩形,然后用光照shader渲染这个矩形。


 

 

五、延迟渲染  vs 正向渲染 



这边对正向渲染和延迟渲染的特性做一个对照列举:



5.1  正向渲染


  • 正向渲染(Forward Rendering),先执行着色计算,再执行深度测试。
  • 正向渲染渲染n个物体在m个光源下的着色,复杂度为O(n*m)次。
  • Forward Rendering,光源数量对计算复杂度影响巨大,所以比较适合户外这种光源较少的场景。
  • Forward Rendering的核心伪代码可以表示为:
For each light:
    For each object affected by the light:
        framebuffer += object *light


Forward Rendering的管线流程如下:


图11 正向渲染(Forward Rendering)管线流程

 



 5.2 延迟渲染

 

  • 延迟渲染( Deferred Rendering),先执行深度测试,再执行着色计算。
  • 延迟渲染渲染n个物体在m个光源下的着色,复杂度为O(n+m)次。
  • Deferred Rendering 的最大的优势就是将光源的数目和场景中物体的数目在复杂度层面上完全分开。也就是说场景中不管是一个三角形还是一百万个三角形,最后的复杂度不会随 光源数目变化而产生巨大变化。
  • Deferred Rendering的核心伪代码可以表示如下,上文已经贴出过,这边再次贴出,方便对比:

For each object:
    Render to multiple targets
For each light:
    Apply light as a 2D postprocess

Deferred Rendering的管线流程如下:

 

图12 延迟渲染( Deferred Rendering)管线流程

 

 

 

 

 

六、延迟渲染的优缺点分析



这里列举一下经典版本的延迟渲染的优缺点。



6.1 延迟渲染的优点


Deferred Rendering 的最大的优势就是将光源的数目和场景中物体的数目在复杂度层面上完全分开。也就是说场景中不管是一个三角形还是一百万个三角形,最后的复杂度不会随光源数目变化而产生巨大变化。

一些要点:

  • 复杂度仅O(n+m)。
  • 只渲染可见的像素,节省计算量。
  • 用更少的shader。
  • 对后处理支持良好。
  • 在大量光源的场景优势尤其明显。

 

6.2 延迟渲染的缺点

  • 内存开销较大。
  • 读写G-buffer的内存带宽用量是性能瓶颈。
  • 对透明物体的渲染存在问题。在这点上需要结合正向渲染进行渲染。
  • 对多重采样抗锯齿(MultiSampling Anti-Aliasing, MSAA)的支持不友好,主要因为需开启MRT。
  • 由于Deferred Shading的Deferred阶段是在完全基于G-Buffer的屏幕空间进行,这也导致了物体材质信息的缺失,这样在处理多变的渲染风格时就需要额外的操作。

 

 



七、延迟渲染的改进



针对延迟渲染上述提到的缺点,下面简单列举一些降低 Deferred Rendering 存取带宽的改进方案。最简单也是最容易想到的就是将存取的 G-Buffer 数据结构最小化,这也就衍生出了 Light Pre-Pass(即Deferred Lighting) 方法。另一种方式是将多个光照组成一组,然后一起处理,这种方法衍生了 Tile-Based Deferred Rendering。

 

也就是说,常见的两种Deferred Rendering的改进是:

  • 延迟光照 Light Pre-Pass(即Deferred Lighting)
  • 分块延迟渲染 Tile-BasedDeferred Rendering

 

下面分别进行说明。

 




八、延迟光照 LightPre-Pass / Deferred Lighting

 


Light Pre-Pass即Deferred Lighting(延迟光照),旨在减少传统Defferred Rendering使用G-buffer 时占用的过多开销(reduce G-buffer overhead),最早由 Wolfgang Engel于2008年在他的博客

(http://diaryofagraphicsprogrammer.blogspot.com/2008/03/light-pre-pass-renderer.html)中提到的。


延迟光照的具体的思路是: 

1、渲染场景中不透明(opaque )的几何体。将法线向量n和镜面扩展因子(specular spread factor)m 写入缓冲区。这个n/m-buffer 缓冲区是一个类似 G-Buffer的缓冲区,但包含的信息更少,更轻量,适合于单个输出颜色缓冲区,因此不需要MRT支持。