HPP_Graphics_3.4 前向渲染与延迟渲染
a). 渲染路径
- 决定光照的实现方式。简言之,就是当前渲染目标使用光照的流程。
b). 渲染方式
- 延迟渲染
- 前向渲染
可以看到前向渲染中,中间蓝色的灯并没被渲染。(场景8盏灯,Project Setting中Pixel Light Count设为7)
b.1). 前向/正向渲染-Forward Rendering
- 简介:每个Object对每个光照都计算;
b.1.1). 流程
- 简单来说就是不管光源的影响大不大,计算的时候都会把所有光源计算进去,这样就会造成一个很大的浪费
b.1.2). 规则和注意事项
发生在顶点处理阶段,会计算所有顶点的光照。全平台支持
- 规则1:最亮的几个光源会被实现为像素光照
- 规则2:然后就是,最多四个光源会被实现为顶点光照
- 规则3:剩下的光源会实现为效率较高的球面调谐光照(Spherical Hamanic),这是一种模拟光照
补充说明
- 最亮的那盏光一定是像素光照
- Light的Render Mode是important的光一定是像素光照
- 如果前面的两条加起来的像素光照小于Quality Setting里的Pixel Light Count(最大像素光照数量),那么从剩下的光源中找出最亮的那几盏光源,实现为像素光照。
- 最后剩下的光源,按照规则2或3。
- 在base pass里执行一盏像素光、所有的顶点光和球面调谐光照,并且进行阴影计算。
- 其余的像素光每盏一个Additional Pass,并且这些pass里没有阴影计算。
- 场景中看到的阴影,全是base pass里计算出最亮那盏像素光的阴影,其他像素光是不计算阴影的。
最多的光源数是可以更改的
- 以Unity中的为例,在project setting中
b.2). 延迟渲染(Deferred Rendering)
b.2.1). 简介
主要解决大量光照渲染的方案。
可以将延迟渲染(Deferred Rendering)理解为先将所有物体都先绘制到屏幕空间的缓冲(即G-buffer,Geometric Buffer,几何缓冲区)中,再逐光源对该缓冲进行着色的过程,从而避免了因计算被深度测试丢弃的片元的着色而产生的不必要的开销。也就是说延迟渲染基本思想是,先执行深度测试(应该也包括其他测试),再进行着色计算,将本来在物空间(三维空间)进行光照计算放到了像空间(二维空间)进行处理。
对应于正向渲染O(m*n)的 复杂度,经典的延迟渲染复杂度为O(n+m)。
b.2.2). 流程
可以将延迟渲染理解为两个Pass的过程:
几何处理阶段(Geometry Pass)。这个阶段中,我们获取对象的各种几何信息(Position、Normal、Albedo、Specular等),并将第二步所需的各种数据储存(也就是渲染)到多个G-buffer中;
- 由于有深度测试,所以最终写入G-buffer中的,都是离摄像机最近的片元的集合属性,这就意味着,在G-buffer中的片元必定要进行光照计算。
光照处理阶段(Lighting Pass)。在这个pass中,我们只需渲染出一个屏幕大小的二维矩形,使用第一步在G-buffer中存储的数据对此矩阵的每一个片段计算场景的光照;光照计算的过程还是和正向渲染以前一样,只是现在我们需要从对应的G-buffer而不是顶点着色器(和一些uniform变量)那里获取输入变量了。
b.2.3). G-Buffer
G-Buffer,全称Geometric Buffer ,译作几何缓冲区,它主要用于存储每个像素对应的位置(Position),法线(Normal),漫反射颜色(Diffuse Color)以及其他有用材质参数。根据这些信息,就可以在像空间(二维空间)中对每个像素进行光照处理。
如图为一个典型的G-buffer
下图是一帧中G-buffer中存储的内容:
UE5中的G-Buffer
在几何处理阶段中填充G-buffer非常高效,因为我们直接储存位置,颜色,法线等对象信息到帧缓冲中,这个过程几乎不消耗处理时间。
而在此基础上使用多渲染目标(Multiple Render Targets, MRT)技术,我们可以在一个Pass之内完成所有渲染工作。
P-Code
1
2
3
4For each object:
Render to multiple targets
For each light:
Apply light as a 2D postprocess1
2
3
4
5
6
7
8Two-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
b.2.4). 延迟渲染的优缺点
优点:
- 复杂度仅O(n+m)
- 只渲染可见的像素,节省计算量
- 用更少的shader
- 对后处理支持良好(例如深度信息:直接拿G-buffer中的就行。而前向渲染需要单独Pass再渲染一张深度图)
- 在大量光源的场景优势尤其明显;
缺点:
- 内存开销较大,且占用了大量的显存带宽;
- 需要传递G-Buffer;
- 有时需要用到G-Buffer的信息,如深度图做后处理,那将不会进行Clear;
- 只能用同一套Lighting Pass;
- 对透明物体的渲染存在问题。在这点上需要结合正向渲染进行渲染;
- 对多重采样抗锯齿(MultiSampling Anti-Aliasing, MSAA)等硬件抗锯齿的支持不友好,主要因为需开启MRT;
- MSAA是依赖于子像素,而Deffered shading处在光栅化之后(单个像素内值相等),传输数据是通过G-Buffer;
- 但可使用TAA
问FXAA、FSAA与MSAA有什么区别?效果和性能上哪个好? - 文刀秋二的回答 - 知乎
https://catlikecoding.com/unity/tutorials/rendering/part-13/
- 内存开销较大,且占用了大量的显存带宽;
b.3). Unity中渲染路径设置
b.4). 其他渲染路径
针对延迟渲染上述提到的缺点,下面简单列举一些降低 Deferred Rendering 存取带宽的改进方案。最简单也是最容易想到的就是将存取的 G-Buffer 数据结构最小化,这也就衍生出了 Light Pre-Pass(即Deferred Lighting) 方法。另一种方式是将多个光照组成一组,然后一起处理,这种方法衍生了 Tile-Based Deferred Rendering。
也就是说,常见的两种Deferred Rendering的改进是:
- 延迟光照 Light Pre-Pass(即Deferred Lighting)
- 分块延迟渲染 Tile-BasedDeferred Rendering
b.4.1). 延迟光照(LightPre-Pass / Deferred Lighting)
- 减少G-buffer占用的过多开销,支持多种光照模型
- 过程:
渲染场景中不透明(opaque )的几何体。将法线向量n和镜面扩展因子(specular spread factor)m 写入缓冲区。这个n/m-buffer 缓冲区是一个类似 G-Buffer的缓冲区,但包含的信息更少,更轻量,适合于单个输出颜色缓冲区,因此不需要MRT支持。
渲染光照。计算漫反射和镜面着色方程,并将结果写入不同的漫反射和镜面反射累积缓冲区。这个过程可以在一个单独的pass中完成(使用MRT),或者用两个单独的pass。环境光照明可以在这个阶段使用一个 full-screen pass进行计算。
对场景中的不透明几何体进行第二次渲染。从纹理中读取漫反射和镜面反射值,对前面步骤中漫反射和镜面反射累积缓冲区的值进行调制,并将最终结果写入最终的颜色缓冲区。若在上一阶段没有处理环境光照明,则在此阶段应用环境光照明。
使用非延迟着色方法渲染半透明几何体。
- 用更少的buffe信息,着色计算的时候用的是forward,所以第三步开始都是前向渲染(在3D,而不是2D的像空间中渲染)
b.4.2). Forward+(即Tiled Forward Rendering,分块正向渲染)
减少带宽,支持多光源,强制需要一个preZ
- 通过分块索引的方式,以及深度和法线信息来到需要进行光照计算的片元进行光照计算。
- 需要法线和深度的后处理需要单独渲染一个rt出来
- 强制使用了一个preZ(如果没涉及过这个概念的话,可以理解为进行了一个深度预计算
https://zhuanlan.zhihu.com/p/28489928
b.4.3). 群组渲染(Clustered Rendering)
- 带宽相对减少,多光源下效率提升
- 分为forward和deferred两种
- 详细补充拓展:https://zhuanlan.zhihu.com/p/54694743
c). 渲染管线的优化(移动端)
IMR框架渲染对于移动端有一个很大的问题:带宽占用过高。
- 因此在移动端,我们不采用PC端构架IMR(Immediate Mode Rendering),而采用移动端构架TBR(Tile-Based Rendering)或TBDR(PowerVR使用)
简而言之,为了节省成本,移动端TBR的GPU中不使用显存(GPU Memory),而使用On_Chip Memory也就是SRAM,或者L1 L2 cache
对于TBR来讲,整个光栅化和像素处理会被分为一个个Tile进行处理,通常为16×16大小的Tile。TBR的结构通过On-Chip Buffers来储存Tiling后的Depth Buffer和Color buffer。
也就是原先IMR架构中对主存中Color/Depth Buffer进行的读写操作变成直接在GPU中的高速内存操作,减少了最影响性能的系统内存传输的开销。通过下面这张图能够更好的来理解,下图的DRAM在手机上就是物理内存那一块。
d). 移动设备渲染通用优化建议
- 贴图格式能压缩就压缩。贴图内存越小,片上命中率就越高,总的传输量也少
- 能开mipmap就开mipmap(前提是能用到,UI贴图就不用开了)。减轻带宽压力,与减小实际使用的贴图内存是一个道理,但是会增加总贴图的内存占用大小,需要在内存开销和带宽开销上做一个平衡。
- 随机纹理寻址相对于相邻纹理寻址有显著开销。提高片上命中率。
- 3DTexture Sampling有显著的开销。3DTexture整体内存占用大,垂直方向相邻像素内存不相邻很容易cache miss,这是我个人推测。
- Trilinear/Anisotropic相对于Bilinear有显著的开销。Trilinear其实就相当于tex3D了(此结论不负责任),Bilinear相对于Point几乎没有额外开销(此结论负责任,texture fetch都是一次拿相邻的四个出来),所以Bilinear能忍就尽量凑合用着吧。
- 使用LUT(look up texture)很可能是负优化。需要对比权衡带宽占用+texture fetch操作增加与ALU占用增加降低并行效率,另外还很可能涉及到美术工作流和最终效果,所以是个不是很好进行操作的优化。之前看过腾讯的技术分享将引擎中Tonemapping那步的3DLUT(UE4和Unity都是这样的)替换为函数拟合的优化,理论上应该是会提升不少性能,但是要想真正应用到生产环境,保证效果,还要做好拟合工具链,是得费不少力气的
- 通道图能合并就合并,减少Shader中贴图采样次数。这个不多说了
- 控制Framebuffer大小。这个也不多说了
- 总顶点数量也是带宽开销的影响因素。虽然以现在GPU的计算能力来说,顶点数增多产生的VS计算开销增加通常是忽略不计的。但是仍不能忽略总顶点数量对于VertexBuffer所消耗带宽的影响,对于总顶点数的限制应该更多的从带宽消耗上去进行测试和分析。
d.1). AlphaTest性能消耗
- 只要Shader中包含discard指令的都会被GPU认为是AlphaTest图元(GPU对于AlphaTest绘制流程的判定是基于图元而不是像素)
- 无论是PowerVR还是Mali/Adreno芯片,AlphaTest图元的绘制都会影响整体渲染性能。
- 随着芯片的发展AlphaTest图元对于渲染性能的影响主要在于Overdraw增加而非降低硬件设计流程效率,其优化思路与AlphaBlend一样,就是少画!
- 严格按照Opaque - AlphaTest - AlphaBlend的顺序进行渲染可以最大化减小AlphaTest对于渲染性能的影响。
- 将Opaque, AlphaTest与AlphaBlend打乱顺序渲染会极大的降低渲染性能,任何情况下都不应该这么做。
- 不要尝试使用AlphaTest替代AlphaBlend,这并不会产生太多优化。
- 不要尝试使用AlphaTest替代Opaque,这会产生负优化
- 不要尝试使用AlphaBlend替代AlphaTest,这会造成错误的渲染结果。
- 在保证正确渲染顺序情况下,AlphaTest与AlphaBlend开销相似,不存在任何替代优化关系
- 增加少量顶点以减少AlphaTest图元的绘制面积是可以提升一些渲染性能的。
- 首先统一绘制AlphaTest图元的DepthPrepass,再以ZTest Equal和不含discard指令的Shader统一绘制AlphaTest图元,大多数情况下是可以显著提升总体渲染性能的(需要实际测试)
资料补充:
https://zhuanlan.zhihu.com/p/259760974