延迟渲染

Posted PireannUE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了延迟渲染相关的知识,希望对你有一定的参考价值。

 

基础概念

概念

缩写

中文名

解析

Render Target

RT

渲染目标/渲染纹理

一种位于GPU显存的纹理缓冲区,可用作颜色写入和混合目标

Geometry Buffer

GBuffer

几何缓冲区

一种特殊的RenderTarget,延迟着色技术的几何数据存储区

Rendering Path

-

渲染路径

将场景物体渲染到渲染纹理的过程中所使用的技术步骤

Forward Render

-

前向渲染

一种普通存在和天然支持的渲染路径

延迟渲染技术概述

延迟渲染技术的核心思想是将渲染分成两个Pass:几何Pass和光照Pass,目的是将计算量大的光照Pass延迟,让物体数量和光照数量解耦,以提升着色效率。以下是延迟渲染的流程图:

 

 

 

两个通道各自的作用:

几何通道

这个阶段将场景中所有不透明的物体和Masked物体用无光照材质渲染,然后将物体的几何信息写到GBuffer中。

其中几何信息由:物体位置或者深度,法线分布,材质信息(RGBA,镜面值等)

void RenderBasePass()

    SetupGBuffer(); // 设置几何数据缓冲区。

    // 遍历场景的非半透明物体

    for each(Object in OpaqueAndMaskedObjectsInScene)

        SetUnlitMaterial(Object);    // 设置无光照的材质

        DrawObjectToGBuffer(Object); // 渲染Object的几何信息到GBuffer,通常在GPU光栅化中完成。

    



 

 

光照通道

光照阶段利用几何通道阶段生成的GBuffer数据执行光照计算,它的核心逻辑是遍历渲染分辨率相同的像素,根据每个像素的uv坐标从GBuffer中采样获取该像素的几何信息,从而执行光照计算。伪代码如下:

void RenderLightingPass()



    BindGBuffer(); // 绑定几何数据缓冲区。

    SetupRenderTarget(); // 设置渲染纹理。

    

    // 遍历RT所有的像素

    for each(pixel in RenderTargetPixels)

    

        // 获取GBuffer数据。

        pixelData = GetPixelDataFromGBuffer(pixel.uv);

        // 清空累计颜色

        color = 0;    

        // 遍历所有灯光,将每个灯光的光照计算结果累加到颜色中。

        for each(light in Lights)

        

            color += CalculateLightColor(light, pixelData);

        

        // 写入颜色到RT。

        WriteColorToRenderTarget(color);

    

 

延迟渲染的优劣

优点:相较于前向渲染,延迟渲染的时间复杂度会低很多。做到了光照计算与物体解耦,只和RenderTarget相关

缺点:延迟渲染对MSAA的支持不太好,对Alpha Test的支持不太好

延迟渲染变种

1. Deferred Lighting(Light Pre-Pass)

需要三个Pass

第一个Pass是几何Pass:只输出每个像素光照计算需要的几何信息(法线,深度, PowSpecular)GBuffer

第二个Pass是光照Pass:对GBuffer的数据取用,计算各个输入光源作用后的信息,存储这些信息到LBuffer

第三个PassSecondary Geometry PassLBuffer中的数据与DiffuseColor进行混合得到最红的输出Color

 

优点:相对延迟渲染,需要存储在GBuffer的信息急剧减少,可以较好地支持MSAA

缺点:需要渲染两边场景,增加了DrawCall

 

 

 

LBuffer

2. Tiled-Based Deferred Rendering(TBDR)

TBDR是基于瓦片的渲染,核心思想在于将渲染纹理分成规则的一个个四边形,然后利用四边形的包围盒剔除无用的光源,只保留有作用的光源,从而减少了实际光照计算中无效光源的计算量

具体流程如下:

(1) 将渲染纹理分成一个个均等面积的小块

(2) 根据Tile内的Depth范围计算出包围盒(视锥)

(3) 根据Tile的包围盒与Light的包围盒求交

(4) 抛弃不相交的Light,得到对该区域有作用的光源列表

(5) 遍历所有Tile,获得每个Tile的有作用的光源索引,计算该Tile内的光照信息

3. Clustered Deferred Rendering

分簇延迟渲染,相较于TBDR,分簇延迟渲染在不同深度进行了更加细粒度的划分

4. Decoupled Deferred Shading

解耦的延迟渲染,核心思想是增加compact geometry buffer用以存储着色采样数据(独立于可见性,避免存储了冗余数据和表面一致的或者shader计算结果之前存在的),通过memoization cache,压缩计算量,提升随机光栅化的着色重用率,降低抗锯齿的消耗。不同于之前将阴影样本存储在framebuffer中,可见性样本将对阴影样本的引用存储在紧凑的线性缓冲区中(memoizaiotn cache),以允许阴影重用。几何缓冲区的大小不随超采样密度而增长,而是由遮光率控制。

 

 

阴影样本信息的存储

5. Visibility Buffer

为了减少GBuffer占用,不渲染GBuffer,改成渲染Visibility BufferVisibility Buffer只存三角形和实例ID。在光照计算时分别从UAVbindless texture里面读取真正需要的vertex attributes和贴图的属性,根据uv的差分自行计算mip-map

 

6. Fine Pruned Tiled Light Lists

Tiled Shading的优化,由两个Pass来计算物体和光源求交

第一个Pass计算光源在屏幕空间的AABB,与物体的AABB进行初步判断

第二个PassTile和光源进行精确求交

7. Deferred Attribute Interpolation Shading

对内存的优化,不保存GBuffer,而是保存三角形信息,再通过插值计算,降低内存的消耗

 

 

第一个Pass,生成VisibilityPass

第二个Pass,采用Visibility,保证所有像素只有一个三角形写入memoization cache(记录三角形id和实际映射的缓冲区)

最后的Pass,计算每个三角形的屏幕空间的偏导数,以供插值使用

8. Deferred Coarse Pixel Shading

主要解决传统延迟渲染在屏幕空间的光照计算阶段高消耗的问题,在生成GBuffer时,额外生成ddxddy,再利用Compute Shader对某个大小的分块找到变化不明显的区域,使用低频着色(几个像素公用一次着色),从而提升着色效率,降低消耗。

 

 

9. Deferred Adaptive Compute Shading

核心思想在于将屏幕像素按照某种方式划分成5Level的不同粒度的像素块,以便决定是直接从邻接level插值还是重新着色

示意图如下:

 

 

算法流程:

  1. 第一个Level,渲染1/16的像素
  2. 遍历2-5Level,逐Level按照下面的步骤着色:

(1) 计算上一个Level相邻4个像素的相似度

(2) 若相似度小于某个阈值,认为此像素与周围像素表接近,当前像素可以通过周围像素插值获得

(3) 否则则开启着色计算

 

前向渲染及其变种

前向渲染

遍历场景中的所有物体,每个物体调用一次绘制指令,经由渲染管线光栅化之后显示在屏幕中。

// 遍历RT所有的像素

for each(object in ObjectsInScene)



    color = 0;

    // 遍历所有灯光,将每个灯光的光照计算结果累加到颜色中。

    for each(light in Lights)

    

        color += CalculateLightColor(light, object);

    

    // 写入颜色到RT。

    WriteColorToRenderTarget(color);

 

Forward+Rendering

相比传统的前向渲染,Forward+Rendering增加了光源剔除Pass

DepthPrePass->LightCullingPass->RenderingPass

LightCullingPass和瓦片的延迟渲染类似,将屏幕划分成若干个Tile,将每个Tile和光源求交,有效光源写入Tile列表,以减少shader的光照计算量

 

存在的问题:由于街头视锥拉长之后在几何边界会有误差,可以使用SAT改善

Cluster Foward Rendering

将屏幕空间划分成均等Tile,深度细分成一个个簇

Volume Tiled Forward Rendering

步骤如下:

  • 初始化阶段

(1) 计算Grid(Volume Tile)的尺寸,给定Tile

 

 

 

 

(2) 计算每个Volume TileAABB

 

 

  • 更新阶段

(1) 深度PrePass,只记录半透明物体的深度

(2) 标记激活的Tile

(3) 创建和压缩Tile列表

(4) 将光源赋给Tile,每个线程执行一个激活的Tile,利用TileAABB和光源求交(可以用BVH优化),将相交的光源索引记录到对应的Tile的光源列表

(5) 着色

前向渲染和延迟渲染的对比

 

 

延迟渲染算法的比较

 

延迟着色场景渲染器

FSceneRenderer

FSceneRenderer用于处理和渲染场景,生成RHI层的渲染指令。FSceneRender由游戏线程FRenderModule::BeginRenderingViewFamily负责创建和初始化,并传递给渲染线程,渲染线程会调用FSceneRenderer::Render(),渲染完成后返回并删除FSceneRenderer实例。也就是说每帧FSceneRenderer实例都会被创建删除。

Tick中渲染的流程:计算ViewFamilyView的各种属性->发送渲染命令

发送渲染命令流程:创建场景渲染器->向场景渲染器发送渲染指令

向场景渲染器发送渲染指令流程:调用场景渲染器的Render接口->等待完成任务后删除实例

渲染器包括延迟渲染器(包含前向和延迟渲染)和移动端渲染器(移动端渲染器默认采用前向渲染)

FDeferredShadingSceneRenderer

FDeferredShadingSceneRendererUE在主机和PC端的渲染器,包含延迟渲染和前向渲染,本文主要介绍其延迟渲染路径

渲染器Render的阶段如下

阶段

解析

FScene::UpdateAllPrimitiveSceneInfos

更新所有图元信息到GPU,若启用了GPUScene,将会用二维纹理或者StructBuffer来存储图元信息

FSceneRenderTarget::Allocate

如果需要,重新分配场景的渲染纹理,以保存由足够大的内存去进行渲染

InitView

采用裁剪等若干方式设置图元可视性,设置可见的动态阴影

PrePass/Depth only pass

提前深度测试,用来渲染不透明物体的深度

Base pass

几何通道。用来渲染不透明物体的几何信息,写道GBuffer中,此阶段不会计算动态光源的贡献,但是会计算Lightmap和天空的贡献

Issue Occlusion Queries / BeginOcclusionTest

开启遮挡测试,此帧的渲染数据用于下一帧InitView阶段的遮挡剔除。可能会打包相近物体的包围盒来减少DrawCall

Lighting

光照通道,会计算开启阴影的光源的阴影图,并计算每个光源对屏幕空间的贡献,并累积到SceneColor

Fog

屏幕空间计算雾和大气对不透明物体表面像素的影响

Translucency

渲染半透明物体阶段,由远到近逐个绘制离屏渲染纹理,接着用单个pass正确计算和混合光照结果

Post Processing

后处理阶段。包含了不需要GBufferBloom,色调映射,Gamma矫正等以及需要GBufferSSR, SSAO, SSGI等。此阶段会将半透明的渲染纹理混合到最终的场景中

可以用profilegpu来查看完整渲染过程

 

 

FScene::UpdateAllPrimitiveSceneInfos

主要作用是删除、增加、更新CPU侧的图元数据,且同步到GPU端。GPU存储结构有两种

I.每个图元独有一个UniformBuffer。在shader中访问UniformBuffer即可。

II.使用Texture2D或者StructedBufferGPUScene,所有图元按照数据规律存放,shader需要访问图元时需要从GPU的对应位置获取。以下时GPUScene的定义

class FGPUScene

public:

    // 是否更新全部图元数据,通常用于调试,运行期会导致性能下降。

    bool bUpdateAllPrimitives;

    // 需要更新数据的图元索引.

    TArray<int32> PrimitivesToUpdate;

    // 所有图元的bit,当对应索引的bit为1时表示需要更新(同时在PrimitivesToUpdate中).

    TBitArray<> PrimitivesMarkedToUpdate;

    // 存放图元的GPU数据结构, 可以是TextureBuffer或Texture2D, 但只有其中一种会被创建和生效, 移动端可由Mobile.UseGPUSceneTexture控制台变量设定.

    FRWBufferStructured PrimitiveBuffer;

    FTextureRWBuffer2D PrimitiveTexture;

    // 上传的buffer

    FScatterUploadBuffer PrimitiveUploadBuffer;

    FScatterUploadBuffer PrimitiveUploadViewBuffer;

    // 光照图

    FGrowOnlySpanAllocator    LightmapDataAllocator;

    FRWBufferStructured        LightmapDataBuffer;

    FScatterUploadBuffer    LightmapUploadBuffer;

;

FScene::UpdateAllPrimitiveSceneInfos操作:

删除流程:

example swap chain of removing X

PrimitiveSceneProxies[0,0,0,6,X,6,6,6,2,2,2,2,1,1,1,7,4,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,X,2,2,2,1,1,1,7,4,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,X,1,1,1,7,4,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,X,7,4,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,X,4,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,4,X,8]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,1,1,1,7,4,8,X]

添加流程:

example swap chain of inserting a type of 6 at the end

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8,6]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,1,1,1,7,4,8,2]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,7,4,8,1]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,4,8,7]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,7,8,4]

PrimitiveSceneProxies[0,0,0,6,6,6,6,6,6,2,2,2,2,1,1,1,7,4,8]

变化流程: MVP

 

 

对于所有被增加或者删除或者更改了数据的图元索引,会调用AddPrimitiveToUpdateGPU将它们放到待更新列表,GPUScenePrimitiveToUpdatePrimitivesMarkedToUpdate收集好所有要更新的图元索引后,会在InitView阶段同步给GPU,流程为:更新GPUScene资源->初始化View数据->同步CPUGPUSceneGPU

 

同步的逻辑:

根据不同的GPUScene存储类型调用不同的接口(UpdateGPUSceneInternal)->判断图元信息是否同步->获取GPU的镜像资源(GPUScene.PrimitiveBuffer或者GPUScene.PrimitiveTexture, 如果资源不足就扩展,扩展资源会创建新资源然后将旧的拷贝到新的)->将所有需要更新的数据收集到PrimitiveUploadBuffer中,如果超过了Buffer的存储上限,会分批加载->收集完成后将UAV状态转换到Compute状态,以便上传数据时GPU拷贝数据->上传数据到GPU(Scene.GPUScene.PrimitiveUploadBuffer.ResourceUploadTo)->上传完以后将UAV状态转换到Gfx,以便渲染物体时shader可用->使镜像资源生效

InitView

主要负责可见性判断,收集场景图元数据和标记,创建可见网格命令,初始化Pass渲染所需要的数据等

流程:创建可见性帧设置预备阶段->特效系统初始化->创建可见性网格指令->计算可见性->胶囊阴影->初始化大气效果->创建可见性帧设置后置阶段->GDoInitViewLightingAfterPrePass决定是否在PrePass之后初始化View->初始化view 的所有uniform bufferRHI资源->计算体积雾->发送开始渲染事件

 

其中调用了很多接口:

PreVisibilityFrameSetup

设置场景中哪些需要渲染的(修改标记位),调用FSceneRenderer::PreVisibilityFrameSetup

做了大量初始化的工作,如静态网格,GroomSkinCache,特效,TAAViewState

ComputeViewVisibility

ComputeViewVisibility用于计算可见性

流程:分配可见光源列表->更新不判断可见性的静态网格->初始化所有View数据->创建光源信息->获取并解压上一帧的数据->冻结可见性->裁剪->处理隐蔽物体->处理静态场景->处理判断可见性的静态网格->收集所有View的动态网格->创建每个网格的MeshPass数据

粗略地预计算可见性:计算哈希值和桶索引,绘制可见性物体的包围盒,通过包围盒和视点去计算是否可见

PostVisibilityFrameSetup

主要防止视线外或者屏幕占比很小或者没有光照强度的光源进入shader计算

流程:处理贴花排序和调整历史RenderTarget->处理光源可见性,遍历所有光源,结合View的视锥判断光源的可见性(平行光源不需要裁剪,只有局部光源需要裁剪,判断View的视锥是否和光源包围盒相交,如果相交,视锥需要针对最大距离进行矫正,剔除距离较远的光源)

InitRHIResources

流程:创建和设置缓存视图的UniformBuffer->创建和设置视图的UniformBuffer->重置缓存的UniformBuffer->初始化透明体积光照参数

OnStartRender

流程:场景MRT的初始化->通知ViewState初始化->初始化和设置体积光传输体积->分配软件级场景遮挡剔除(针对不支持硬件剔除的低端机)

 

综上所述InitView的流程分为:

  • PreVisibilityFrameSetup:可见性判断预处理阶段,主要是初始化和设置静态网格、GroomSkinCache、特效、TAAViewState
  • 初始化特效系统
  • ComputeViewVisibility:计算视图相关的可视性

(1) FPrimitiveSceneInfo::UpdateStaticsMeshes:更新静态网格数据

(2) ViewState::GetPrecomputedVisibilityData:获得可视性预计算数据

(3) FrustumCull:视锥体剔除

(4) ComputeAndMarkRelevanceForViewParallel:计算和标记视图并行处理的关联数据

(5) GatherDynamicMeshElements:收集view中的动态可见元素

(6) SetupMeshPass:设置网格Pass数据,将FMeshBatch转化为FMeshDrawsCommand

  • CreateIndirectCapsuleShadows:创建胶囊阴影
  • UpdateSkyIrradianceGPUBuffer:更新天空环境光照GPU数据
  • InitSkyAtmosphereForViews:初始化大气效果
  • PostVisibilityFrameSetup:可见性判断后置阶段,利用视锥剔除影响较小或者无影响的光照信息
  • View.InitRHIResources:初始化视图的部分RHI资源
  • SetupVolumetricFrog:初始化体积雾
  • OnStartRender:通知RHI开启渲染

 

 

 

PrePass

提前深度测试,可以由DBuffer或者ForwadingShading触发,通常用建立Hierarchial-Z,以便开启硬件的Early-Z技术

RenderPrePass的整体逻辑流程如下:
非并行模式(等待之前任务结束),并行模式(分配深度缓冲),每个View都需要去绘制一遍深度缓冲(创建和设置UniformBuffer->处理渲染状态)

PrePass是禁止写入颜色,开启深度测试和写入,深度比较函数是接近或者相等

 

WorldGridMaterial中的Defult Lit中存在冗余节点。可以在配置文件中修改,以提升效率

 

深度绘制的模式有四种:

  1. DDM_Node:不绘制深度
  2. DDM_NonMaskedOnly:只绘制Opaque材质
  3. DDM_AllOcculders:OpaqueMasked材质,但是不包含关闭了bUseAsOccluder的物体
  4. DDM_AllOpaque:全部不透明物体模式,所有物体需绘制,且每个像素都要匹配BasePass的深度
  5. DDM_MaskedOnly:只绘制Masked材质

 

而深度绘制的模式由Engine\\Source\\Runtime\\Renderer\\Private\\RendererScene.cpp”中的UpdateEarlyZPassMode()中设置,通过命令行重写,也可以由工程设置中指定

TipsDBuffer贴图和模板LOD抖动强制全部模式

BasePass

延迟渲染中的几何通道,通过GBuffer来存储几何信息

流程:绘制场景深度->深度纹理解析(只有开启了MSAA才会被执行)->光源格子化,常用于Cluster光照计算中快速剔除光源->分配场景GBuffer->渲染BasePass

 

渲染BasePass需要遍历所有View去渲染BasePass

BasePass渲染状态

void SetupBasePassState(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, const bool bShaderComplexity, FMeshPassProcessorRenderState& DrawRenderState)



    DrawRenderState.SetDepthStencilAccess(BasePassDepthStencilAccess);

 

    (......)

    

    

        // 所有GBuffer都开启了混合.

        static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.BasePassOutputsVelocityDebug"));

        if (CVar && CVar->GetValueOnRenderThread() == 2)

        

            DrawRenderState.SetBlendState(TStaticBlendStateWriteMask<CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA, CW_NONE>::GetRHI());

        

        else

        

            DrawRenderState.SetBlendState(TStaticBlendStateWriteMask<CW_RGBA, CW_RGBA, CW_RGBA, CW_RGBA>::GetRHI());

        

 

        // 开启了深度写入和测试, 比较函数为NearOrEqual.

        if (DrawRenderState.GetDepthStencilAccess() & FExclusiveDepthStencil::DepthWrite)

        

            DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());

        

        else

        

            DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI());

        

    



 

BasePass渲染材质
void FBasePassMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId)



    if (MeshBatch.bUseForMaterial)

    

        const FMaterialRenderProxy* FallbackMaterialRenderProxyPtr = nullptr;

        const FMaterial& Material = MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(FeatureLevel, FallbackMaterialRenderProxyPtr);

        

    (.....)

 

由此可见,BasePass一般情况下也是使用FMeshBatch收集到的材质,唯一不同的是shader中不开启光照计算

所以伪代码如下:

foreach(scene in scenes)

    foreach(view in views)

        foreach(mesh in meshes)

            DrawMesh(...) // 每次调用渲染就执行一次BasePassVertexShaderBasePassPixelShader的代码.

LightingPass

光照通道,此阶段会计算开启阴影的光源的阴影图,也会计算每个灯光对屏幕空间像素的贡献量,累计到SceneColor中,此外也会计算光源对Transluency lighting volumes的贡献值。

流程:渲染非直接漫反射和AO->渲染非直接胶囊阴影->会修改从BasePass输出的已经添加了非直接光源的场景颜色->渲染距离长AO->清理光照体素->渲染光源->添加CubeMap的透明体素光照->过滤透明体素光照->光照组合预备阶段(LPV)->渲染天空漫反射和只作用于不透明物体的反射->解析场景颜色(把场景颜色的RT变为RSV)->计算SSS效果

 

LightingPass会先绘制无阴影光照,起始索引由SortedLightSet.SimpleLightEnd决定;再绘制带阴影的光照,起始索引由SortedLightSet.AttenuaionLightStart决定,无阴影的光源才支持TiledClustered

 

渲染光源的代码再RenderLights中,流程如下:

RenderLights

  1. 设置混合状态为叠加,以便所有光源的光照强度叠加到同一张纹理中
  2. 遍历所有View,将此光源给每个View都绘制一遍
  3. 判断是否为平行光

(1) 若为平行光

① 设置延迟光源的shaderpso参数

② 使用全屏范围的矩形来绘制

(2) 若为局部光源

① 决定是否开启深度包围盒测试

② 设置延迟光源的shaderpso参数

③ 深度包围盒测试只在带阴影的光源中有效,可以有效剔除在深度范围之外的像素(UE中使用了逆反的深度所有far<near)

④ 点光源用球体绘制,聚光灯用锥体绘制

 

伪代码如下:
foreach(scene in scenes)

    foreach(light in lights)

    

        foreach(view in views)

        

            RenderLight() // 每次调用渲染光源就执行一遍DeferredLightVertexShadersDeferredLightPixelShaders的代码.

        

    

 

 

 

Translucency

渲染半透明物体的阶段,所有半透明物体是由远到近渲染的,接着用单独的pass以正确计算和混合光照效果。在该阶段会渲染半透明的颜色、扰动纹理(用于折射)、速度缓冲(用于TAA)

 

渲染半透明物体的代码逻辑在RenderTranslucency

RenderTranslucency

// Source\\Runtime\\Renderer\\Private\\TranslucentRendering.cpp

void FDeferredShadingSceneRenderer::RenderTranslucency(FRHICommandListImmediate& RHICmdList, bool bDrawUnderwaterViews)



    TRefCountPtr<IPooledRenderTarget> SceneColorCopy;

    if (!bDrawUnderwaterViews)

    

        ConditionalResolveSceneColorForTranslucentMaterials(RHICmdList, SceneColorCopy);

    

 

    // Disable UAV cache flushing so we have optimal VT feedback performance.

    RHICmdList.BeginUAVOverlap();

 

    // 在景深之后渲染半透明物体。

    if (ViewFamily.AllowTranslucencyAfterDOF())

    

        // 第一个Pass渲染标准的半透明物体。

        RenderTranslucencyInner(RHICmdList, ETranslucencyPass::TPT_StandardTranslucency, SceneColorCopy, bDrawUnderwaterViews);

        // 第二个Pass渲染DOF之后的半透明物体, 会存储在单独的一张半透明RT中, 以便稍后使用.

        RenderTranslucencyInner(RHICmdList, ETranslucencyPass::TPT_TranslucencyAfterDOF, SceneColorCopy, bDrawUnderwaterViews);

        // 第三个Pass将半透明的RT和场景颜色缓冲在DOF pass之后混合起来.

        RenderTranslucencyInner(RHICmdList, ETranslucencyPass::TPT_TranslucencyAfterDOFModulate, SceneColorCopy, bDrawUnderwaterViews);

    

    else // 普通模式, 单个Pass即渲染完所有的半透明物体.

    

        RenderTranslucencyInner(RHICmdList, ETranslucencyPass::TPT_AllTranslucency, SceneColorCopy, bDrawUnderwaterViews);

    

 

    RHICmdList.EndUAVOverlap();

 

如果需要在景深之后渲染半透明物体,需要经历三个Pass

1.渲染标准半透明物体

2.渲染景深之后的半透明物体,会存储在一张半透明RenderTarget

3.将半透明RenderTarget和场景颜色缓冲在景深之后混合起来

否则则在单个Pass中由远到近渲染半透明物体(RenderTranslucencyInner)

RenderTranslucencyInner

对所有的半透明物体进行渲染,与BasePass类似,只不过只存储半透明物体的UniformBuffer,以及设置半透明物体渲染的RenderState。并且光照计算上有区别。

半透明物体渲染处于分离队列中(在工程的Render中设置,separate Translucency)

如果要将透明物体加入分离队列,只需要将其使用的材质开启Render After DOF

PostProcessing

后处理没有直接使用RHICommandList,而是用GraphBuilder代替。GraphBuilder是依赖性渲染图,可以自动裁剪无用的Pass,自动管理Pass之间的依赖性和资源的生命周期,以及资源、PSO、渲染指令的优化

 

渲染依赖图系统

运行在pass建立之后。并非立即执行pass,而是延迟到整个帧已记录到依赖图标数据结构之中后再执行。当完成了对所有pass的收集之后,会执行各类裁剪和优化,可以自动裁剪无用的pass,再按照依赖性的排序顺序进行编译和执行。这样可以做到上层渲染逻辑和下层资源隔离

 

帧依赖图关系:

 

 

AddPostProcessingPasses

    

// 后处理的所有Pass.

    enum class EPass : uint32

    

        MotionBlur,

        Tonemap,  //色调映射

        FXAA,

        PostProcessMaterialAfterTonemapping,

        VisualizeDepthOfField,

        VisualizeStationaryLightOverlap,

        VisualizeLightCulling,

        SelectionOutline,

        EditorPrimitive,

        VisualizeShadingModels,

        VisualizeGBufferHints,

        VisualizeSubsurface,

        VisualizeGBufferOverview,

        VisualizeHDR,

        PixelInspector,

        HMDDistortion,

        HighResolutionScreenshotMask,

        PrimaryUpscale,

        SecondaryUpscale,

        MAX

    ;

 

 

 

    // 后处理的所有Pass对应的名字.

    const TCHAR* PassNames[] =

    

        TEXT("MotionBlur"),

        TEXT("Tonemap"),

        TEXT("FXAA"),

        TEXT("PostProcessMaterial (AfterTonemapping)"),

        TEXT("VisualizeDepthOfField"),

        TEXT("VisualizeStationaryLightOverlap"),

        TEXT("VisualizeLightCulling"),

        TEXT("SelectionOutline"),

        TEXT("EditorPrimitive"),

        TEXT("VisualizeShadingModels"),

        TEXT("VisualizeGBufferHints"),

        TEXT("VisualizeSubsurface"),

        TEXT("VisualizeGBufferOverview"),

        TEXT("VisualizeHDR"),

        TEXT("PixelInspector"),

        TEXT("HMDDistortion"),

        TEXT("HighResolutionScreenshotMask"),

        TEXT("PrimaryUpscale"),

        TEXT("SecondaryUpscale")

    ;

用来增加后处理通道

流程如下:初始化数据和标记->根据标记开启或者关闭对应的后处理通道->后处理(

  1. 处理MotionBlur, Tonemap, PostProcessMaterialAfterTonemapping等:获取材质输入->处理各类标记->开启或者关闭特殊后处理->半透明混合进场景颜色RT前的后处理->色调映射前的后处理->TAA
  2. SSR: 材质输入后处理材质-SSR输入/运动模糊/泛光->Bloom是否有效->色调映射->FXAA->颜色映射后的处理材质
  3. 组合分离队列的半透明RT和伽马矫正

)

 

后处理渲染分为以下几个阶段:

BL_AfterTonemapping, // 色调映射之后.

BL_BeforeTonemapping, // 色调映射之前.

BL_BeforeTranslucency, // 半透明组合之前.

BL_ReplacingTonemapper, // 替换掉色调映射.

BL_SSRInput, // SSR输入.

BL_MAX,

 

 

默认混合阶段是在色调映射之后,以下为添加色调映射为例,讲解如果向渲染器添加后处理

// Engine\\Source\\Runtime\\Renderer\\Private\\PostProcess\\PostProcessing.cpp

 

static FRCPassPostProcessTonemap* AddTonemapper(

    FPostprocessContext& Context,

    const FRenderingCompositeOutputRef& BloomOutputCombined,

    const FRenderingCompositeOutputRef& EyeAdaptation,

    const EAutoExposureMethod& EyeAdapationMethodId,

    const bool bDoGammaOnly,

    const bool bHDRTonemapperOutput,

    const bool bMetalMSAAHDRDecode,

    const bool bIsMobileDof)



    const FViewInfo& View = Context.View;

    const EStereoscopicPass StereoPass = View.StereoPass;

 

    FRenderingCompositeOutputRef TonemapperCombinedLUTOutputRef;

    // LUT Pass.

    if (IStereoRendering::IsAPrimaryView(View))

    

        TonemapperCombinedLUTOutputRef = AddCombineLUTPass(Context.Graph);

    

 

    const bool bDoEyeAdaptation = IsAutoExposureMethodSupported(View.GetFeatureLevel(), EyeAdapationMethodId) && EyeAdaptation.IsValid();

    // 向Context.Graph注册一个Pass, 对象类型是FRCPassPostProcessTonemap.

    FRCPassPostProcessTonemap* PostProcessTonemap = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessTonemap(bDoGammaOnly, bDoEyeAdaptation, bHDRTonemapperOutput, bMetalMSAAHDRDecode, bIsMobileDof));

 

    // 设置输入纹理.

    PostProcessTonemap->SetInput(ePId_Input0, Context.FinalOutput);

    PostProcessTonemap->SetInput(ePId_Input1, BloomOutputCombined);

    PostProcessTonemap->SetInput(ePId_Input2, EyeAdaptation);

    PostProcessTonemap->SetInput(ePId_Input3, TonemapperCombinedLUTOutputRef);

 

    // 设置输出.

    Context.FinalOutput = FRenderingCompositeOutputRef(PostProcessTonemap);

 

    return PostProcessTonemap;

 

Metal2剖析:传统延迟渲染和TBDR

针对延迟渲染,官方给出了一个Demo,分别实现了传统的双Pass延迟渲染和利用Metal的特性实现的单一Pass延迟渲染。单Pass延迟渲染主要依靠iOS和tvOS平台的Tile based特性来实现。这篇文章主要根据官方的延迟渲染Demo来分析两种延迟渲染的原理区别,扩展Programmable blending特性实现延迟渲染和ImageBlock实现延迟渲染进行优化的原理,挖掘总结用到的Metal引擎特性和相关知识点。

官方Metal Demo列表地址:https://developer.apple.com/metal/sample-code/

延迟渲染Demo地址:https://developer.apple.com/documentation/metal/deferred_lighting?language=objc

文章目录

以上是关于延迟渲染的主要内容,如果未能解决你的问题,请参考以下文章

延迟渲染

延迟渲染与前向渲染

延迟渲染与前向渲染

当来自服务器的响应延迟时如何延迟组件渲染

尝试渲染到多个纹理以实现延迟渲染。但所有纹理都是平等的

一步步学OpenGL(35) -《延迟渲染》