入门图形学:Deferred Rendering

       这一篇来聊一聊Deferred Rendering,中文名延迟渲染。

       之前我们简单聊过主机游戏和手机游戏的差别,其中聊到主机图形API全是专用开发的,而专用开发的API具有更好的运行效率。这一点相信写过代码的都理解,通用代码框架和API因为要考虑兼容性,一般都会添加接口层、抽象层,而针对不同硬件(一般情况下只区分硬件构架的不同,例如移动端GPU构架三巨头:PowerVR/Adreno/Mali)就需要派生实现不同的代码分支,同时为了实现最大化兼容性,主体抽象层就需要实现全集API,而不同硬件下的API实现属于子集API。继续来以opengl为例,opengl为了跨硬件跨平台考虑,不得不牺牲运行效率达到最佳的兼容性,不过啊,初期再怎么好的框架设计(相比其他图形API算差)也顶不住后面越来越多的平台(硬件)加入,所以考虑嵌入式设备上运行,不得不重新精简开发opengles(opengl for embedded systems),而就算是精简后的opengles也照样面临构架设计和运行效率问题(相比metal/vulkan)。而专用图形API就不需要考虑这些,API直接调用图形驱动,效率更高不用多说了吧。

        继续,图形API的自身代码设计导致的运行效率是一方面,还有就是渲染计算的不同。我们一般讲的渲染,也就是光照计算,而光照计算大类别分为forwarded rendering(前向渲染/前向计算)和deferred rendering(延迟渲染/延迟计算)。

        先上官方:forward rendering

        前向计算大家想必都知道,可能都写过无数遍了,就是在着色器中计算每个片段经过灯光着色(称为:片段光照)或者每个顶点经过灯光着色(称为:顶点光照),也就是我们shader中经常写的基于视口/顶点/光源坐标和顶点法向量之间的diffuse/specular计算那些,但是这种计算有一个很严重的效率问题,如下:

         1.如果我们场景中灯光很多,那么计算所需的时间就是乘数倍上升,这个好理解吧,就是多一个灯光就多一倍的计算。但是!我们可以通过烘培场景解决这个问题,烘培完毕关闭灯光,通过lightmap去表现效果。

          2.如果我们场景中物体对象很多,也会造成计算时间加数级上升(这不是废话吗?n个不同物体肯定n个shader所以n个计算单位,不过场景中会存在大量摄像机照射不到的物体也参与了渲染),但是!我们可以通过遮挡剔除解决这个问题,构建完毕遮挡剔除数据后,被遮挡的物体会被的隐藏,也就是剔除出应用阶段的DrawCall采集。

          可以看出来前向计算面临的两大问题和解决方案,不过凡事都有例外,如果某些情况下不能满足使用解决方案的条件,那么前向计算带来的渲染效率问题就会非常严重。

          而延迟计算就是为了解决前向计算效率问题而存在的,延迟计算的核心就是以屏幕(纹理)分辨率为基准,只计算该分辨率下每个像素的着色。

          先上官方:deferred rendering

          延迟计算通过将原始场景对象光栅处理成Texture2D数组对象(MRT:Multi-Render-Target),其中包含(可能并不止):

          1.ARGB32格式: Diffuse color (RGB), occlusion (A)

          2. ARGB32格式: Specular Color(RGB), roughness (A)

          3.ARGB2101010格式: World space normal (RGB), unused (A)

          4.ARGB2101010 (non-HDR) or ARGBHalf (HDR)格式: Emission + lighting + lightmaps + reflection probes buffer

          5.Depth+Stencil buffer

          这个MRT缓存称为G-Buffer,也就是我们后面用来计算的数据对象(以纹理形式保存),延迟计算的延迟就是这个意思,不直接计算每个物体基于坐标空间下的光照颜色,而是通过光栅化到视图/深度变换流程才计算光照,那么1080p的设备分辨率,只需要5张纹理包含的数据参与计算,其中主要参数包含:

         1.一帧内显存带宽消耗:5*1080*1920*32bit = 39.55mb,那么144fps(我用144hz屏幕)= 5695mb,那么需要显存带宽支持5.695gb/s才能达到要求

          2.光照计算(默认前向计算和延迟计算复杂度一致),那么很明显只需要1080*1920=2073600像素的颜色计算,比前向少多了

          所以我们也看得出来,延迟计算缺点也是有的:显存带宽要求非常高,优点就是显卡的浮点运算能力(光照计算≈浮点运算)要求不高(相比前向计算)。

          同时我们使用延迟计算还会遇到下面的问题:

          1.我们以前聊过抗锯齿,而延迟计算类似后处理,那么超采样受限,无法使用SSAA和MSAA,只能使用基于后处理的FXAA。这里如果有钱的小伙伴们可以杠一下:“我就是豪就是有钱,我PC是八路RTX3090,我就是要在延迟计算上用超采样”,也不是不行!我们通过超采样将MRT的5张(甚至更多)数据纹理全部4X 8X 16X,然后再计算光照,就是豪就是爆炸,显存带宽xxTB/s就是不怕,做延迟计算的MSAA小意思!当然了比如我这种垃圾GTX750还是刚不住,而且延迟计算目的总归是要降低显卡综合性能消耗的,所以一般不会使用超采样,而是做屏幕后处理。

          2.光照算法单一,毕竟光栅化到5张RT后就不存在渲染对象(gameobject)了,就是几张纹理而已,所以无法多种光照算法混合使用

          3.半透明渲染处理问题,因为延迟计算的原理,半透明片段会覆盖不透明片段在MRT上的pixel,那么最终渲染效果就会有问题。所以只能分层(不透明和半透明分层)渲染,同时也就带来了更多性能消耗。

          不过就算是缺点众多,也难掩延迟计算的最大优点:降低光照计算复杂度和性能消耗,主机(包含PC大作)基本使用延迟计算,通过美术和程序们的配合,根据特定的游戏场景环境,将延迟计算可能碰到的瓶颈一一妥协处理。而我们手机开发,就会因为兼容性问题麻烦啦,目前市面上保守估计50%的设备GPU都不支持延迟计算,想做你都做不了,除非你能和米哈游等大厂一样走高端精品路线,一切为了游戏品质。

          最后,那前向计算就一无事处了吗?当然不会,一般我们做的中小手游通过前面说的前向计算解决方案就能克服渲染效率问题,无非就是相对而言做不了很丰富的美术效果。不过啊,前向计算也有自己的解决方案的,以后有时间再聊。

猜你喜欢

转载自blog.csdn.net/yinhun2012/article/details/108553264