Unreal 渲染管线原理机制源码剖析
https://zhuanlan.zhihu.com/p/641367884
概述
- 本文借助 Unreal ProfileGPU 命令和 RenderDoc 工具对游戏进行截帧,结合引擎源码,对虚幻引擎(UE4/UE5)的渲染管线(含延迟渲染管线和前向渲染管线)进行分析说明和总结记录。
说明
- 本文首先着重以 UE5 的延迟渲染管线为例做分析,但也会对手机渲染管线(前向和延迟)及 UE4 的异同进行对比分析。
- 本文着重介绍渲染管线尤其是 GPU 层面一帧的渲染流程,不会详细介绍 Unreal 从游戏线程、渲染线程到 RHI 等的全流程,也不会详细介绍图形渲染管线全流程。
- 不论 Unreal 自带的命令 ProfileGPU 还是 RenderDoc 插件或工具,均可捕获到大致一致的渲染帧数据。本文可能借助 ProfileGPU 和 RenderDoc 进行辅助分析,但不会详细介绍相关使用说明。
- 个人研究,仅供参考。如有谬误,欢迎指出。
测试环境
- UE版本
- UE4.27.2 、UE5.2
- 场景地图
- StarterContent 的 StarterMap
- RenderDoc
- 1.27
- 调试开关
- 如果需要对Shader和渲染流程进行Debug,建议开启如下设置:
流程解析
说明
- 本段主要基于 UE5 延迟管线 做说明,其他情况见“异同对比”。
截帧数据
- 说明
- 使用 ProfileGPU 命令与使用 RenderDoc 插件来截帧,得到的流程数据基本一致,除了:如果有自定义的 Pass,ProfileGPU 里会显示出来,RenderDoc 的事件视图里则不会显示。RenderDoc 还有很多其它功能,比如资源视图、Pipeline 视图和 Shader 、Debug 功能等,因本文暂不需要相关功能,所以之后涉及截帧,除了两种方式输出有差异的情况下,可能只给出一种输出。
- ProfileGPU
3cfghgnjdqvenpvdfegld4oaj7.png
- RenderDoc
7eg8deca5u52iq6viu4314k6q9.png
主要流程
说明
- 本小节基于源码结合截帧数据对延迟渲染管线进行分析和解释。
- 本段流程基于源码和截帧数据而来,涵盖渲染管线的主要流程,忽略了一些细节调用。
UpdateAllPrimitiveSceneInfos
- 更新场景中所有场景图元信息,包括处理已删除的场景图元信息、已添加的场景图元信息、更新的实例和变换、自定义原始场景参数的更新、距离场景数据的更新以及遮挡边界的更新,来确保场景中的场景图元信息保持最新和正确。 为之后的渲染做准备。
关键函数
- FScene::UpdateAllPrimitiveSceneInfos
- 代码解释
- 准备工作,包括等待清理任务完成和更新延迟缓存的统一表达式。然后,开始一个RDG事件作用域,用于在渲染图中记录更新所有场景图元信息的操作。
- 调用UpdateAllLightSceneInfos函数来更新所有光源的场景信息。
- 如果启用了光线追踪功能,调用UpdateRayTracingGroupBounds_RemovePrimitives和UpdateRayTracingGroupBounds_AddPrimitives函数来更新光线追踪组的边界。
- 处理已删除的场景图元信息。它首先将这些信息按照一定的排序规则进行排序,然后遍历这些已删除的场景图元信息,并在虚拟阴影贴图缓存管理器中进行处理。
- 处理已添加的场景图元信息。它首先将这些信息按照一定的排序规则进行排序,然后遍历这些已添加的场景图元信息,并在GPUScene中进行相应的调整和处理。
- 处理更新的实例和变换。它遍历已更新的实例和变换,并在虚拟阴影贴图缓存管理器中进行处理。
- 处理自定义原始场景参数的更新。它遍历已更新的自定义原始场景参数,并在相应的原始场景代理中进行更新。
- 处理距离场景数据的更新。它遍历已更新的距离场景数据,并在距离场景数据中进行相应的更新。
- 处理更新的遮挡边界。它遍历已更新的遮挡边界,并在场景图元信息中进行相应的更新。
- 处理已删除的场景图元信息。它遍历已删除的场景图元信息,并在GPUScene中进行相应的调整和处理。如果需要,函数还会使路径追踪输出无效。
- 清空一些临时数据结构,并进行一些断言检查。
- 渲染截帧
- 代码解释
33694v6tsqr0e1ho8a238ovns9.png
GSceneRenderTargets.Allocate
说明
- 根据需要重新分配全局场景渲染目标,使其足够大以容纳当前视图。
渲染截帧
47u5e6stbg8cecmos74eo3u50i.png
InitViews
说明
- 通过多种剔除方法为视图初始化基元可见性,设立此帧可见的动态阴影、按需要交叉阴影视锥与世界场景(对整个场景的阴影或预阴影)。
渲染截帧
1e3g96fji1mimicfp7uh9nuoag.png
关键函数
- FSceneRenderer::PreVisibilityFrameSetup
- 做计算可见性图元的预处理工作,各种相关检查和设置。
- 代码流程
- 通过调用RHICmdList.BeginScene()通知RHI开始渲染场景。
- 检查是否有视图(Views)和是否需要显示HitProxies。如果满足条件,函数会创建FHairStrandsBookmarkParameters对象,并调用RunHairStrandsBookmark函数来处理LOD选择和Guide插值。
- 检查是否启用了Hair Strands,并且视图数量大于0且不需要显示HitProxies。如果满足条件,创建FHairStrandsBookmarkParameters对象,并调用RunHairStrandsBookmark函数来处理Guide插值。
- 通知FX系统即将执行可见性检查。
- 根据引擎的显示标志(ViewFamily.EngineShowFlags)绘制与选定的网格相关的光源影响线。这个功能只在编辑器模式下可用,并且需要设置EngineShowFlags.LightInfluences标志。
- 根据编译选项判断是否冻结Temporal Histories和Temporal Sequences。如果是Shipping版本,则不会冻结,否则会根据CVarFreezeTemporalHistories和CVarFreezeTemporalSequences的值来决定是否冻结。
- 根据引擎的显示标志(ViewFamily.EngineShowFlags)和视图(Views)的数量来更新视图状态(ViewState)。函数会检查是否需要重置相机、是否有大的相机移动、是否需要重置光线追踪迭代次数等,并更新视图状态的相关属性。
- 根据引擎的显示标志(ViewFamily.EngineShowFlags)和视图(Views)的数量来更新运动模糊时间缩放。
- 更新视图状态的PrevFrameNumber和PendingPrevFrameNumber属性,并调用UpdateLastRenderTime和UpdateTemporalLODTransition函数来完成视图状态的更新。
- 根据场景(Scene)和视图(Views)的数量来初始化光线追踪场景(RayTracingScene)和调试实例GPU场景索引缓冲区(DebugInstanceGPUSceneIndexBuffer)。
- 设置全局的混色渐变缓冲区(DitherFadeOutUniformBuffer和DitherFadeInUniformBuffer)。
- FSceneRenderer::ComputeViewVisibility
- 计算哪些图元对于每个视图可见且有相关性,并生成相应的渲染命令。
- 代码流程
- 循环遍历每个视图(View),对每个视图进行可见性计算。在计算可见性之前,会初始化视图的一些数据结构,如视图的可见性Map(PrimitiveVisibilityMap)、动态网格的结束索引(DynamicMeshEndIndices)等。
- 调用 ComputeAndMarkRelevanceForViewParallel 方法,计算并标记视图中每个图元(Primitive)的相关性。该方法会根据视图的位置、剔除等条件,判断图元是否对当前视图可见,并更新图元的相关性信息。
- 等待光照可见性计算任务的完成,并等待创建光照图元交互任务的完成。
- 在准备收集动态网格元素之前,会调用 PreGatherDynamicMeshElements 方法,用于在收集动态网格元素之前进行一些准备工作。
- 调用 GatherDynamicMeshElements 方法,从场景代理(Scene Proxy)中收集 FMeshBatch。该方法会遍历场景中的每个图元,并从其场景代理中获取相应的 FMeshBatch。
- 对每个视图进行一些额外的处理,如更新命中代理缓冲区(Hit Proxy Buffer)、处理图元等。
- 根据视图的渲染条件,设置网格通道(Mesh Pass)并生成渲染命令。
- FSceneRenderer::PostVisibilityFrameSetup
- 在可见性帧设置之后执行一些与渲染相关的操作,主要是调用 GatherReflectionCaptureLightMeshElements 进行视锥剔除等。
- 代码流程
- 循环遍历Views数组中的每个元素,并对每个View对象执行如下操作。
- 在循环中,它首先对View.MeshDecalBatches进行排序。
- 如果View.State不为空,调用TrimHistoryRenderTargets函数来修剪Scene中的历史渲染目标。
- 调用GatherReflectionCaptureLightMeshElements函数来收集反射捕获光源的网格元素。
- FSceneRenderer::GatherReflectionCaptureLightMeshElements
- 在每个视图中对可见的反射捕捉光源进行视锥剔除,并根据光源的属性绘制相应的光源元素。
- 代码流程
- 遍历每个视图中的可见反射捕捉光源。
- 获取光源的位置和方向,并计算与视图原点的距离和半径。
- 根据光源的属性,计算光源的颜色和衰减系数。
- 根据光源的类型,绘制相应的光源元素,如矩形光源、球形光源或圆柱光源。
- FViewInfo::InitRHIResources
- 初始化一些RHI资源,并根据输入参数OverrideNumMSAASamples来设置CachedViewUniformShaderParameters的属性值,并计算一些与体积光照相关的变量。
PrePass / Depth only pass
说明
- RenderPrePass / FDepthDrawingPolicy。渲染遮挡物,对景深缓冲区仅输出景深。该通道可以在多种模式下工作:禁用、仅遮蔽,或完全景深,具体取决于活动状态的功能的需要。该通道通常的用途是初始化 Hierarchical Z 以降低 Base 通道的着色消耗(Base 通道的像素着色器消耗非常大)。
- 按照硬件要求做设置和渲染以便硬件能正常使用 early z 。
渲染截帧
7433hvjgidbo2rp07d0814j7hd.png
关键函数
- FDeferredShadingSceneRenderer::RenderPrePass
- 代码流程
- 检查是否启用了栅格化模板抖动,并调用AddDitheredStencilFillPass函数来添加栅格化模板填充通道。
- 根据DepthPass.EarlyZPassMode的值来决定是否绘制深度通道。如果启用了并行深度通道,使用RDG_WAIT_FOR_TASKS_CONDITIONAL宏等待任务完成,并对每个视图执行深度通道的绘制操作。否则,对每个视图执行深度通道的绘制操作。
- 在深度通道的绘制过程中,设置一些渲染状态,并调用View.ParallelMeshDrawCommandPasses[EMeshPass::DepthPass].BuildRenderingCommands函数来构建渲染命令。然后,使用GraphBuilder.AddPass函数将深度通道的渲染命令添加到渲染图中。
- 在深度通道的绘制之后,调用RenderPrePassEditorPrimitives函数来渲染预处理的编辑器图元。
- 接下来,检查是否启用了栅格化模板抖动的LOD过渡,并调用GraphBuilder.AddPass函数来添加一个清除栅格化模板的通道。
- 最后,根据条件判断是否启用了前向渲染,并调用StampDeferredDebugProbeDepthPS函数来在非前向渲染模式下对深度通道进行处理。
- 代码流程
- FDeferredShadingSceneRenderer::ShouldRenderPrePass
- PrePass 的渲染仅在 EarlyZPassMode 不为 DDM_None 且 bEarlyZPassMovable 不为 0 时才开启。
- EDepthDrawingMode
- DDM_None
- 在更高水平上进行测试
- DDM_NonMaskedOnly
- 仅不透明材质
- DDM_AllOccluders
- 不透明和遮罩材质,但没有禁用 bUseAsOcclusionr 的对象
- DDM_AllOpaque
- 完整的预通道,必须绘制每个对象并且每个像素必须匹配基础通道深度
- DDM_MaskedOnly
- 仅蒙版材质
- DDM_AllOpaqueNoVelocity
- 完整的预通道,必须绘制每个对象并且每个像素必须与基础通道深度匹配,除了将在速度通道中渲染的动态几何体
- 可以使用 r.EarlyZPass 和 r.EarlyZPassMovable 等进行自定义。
- DDM_None
- SetupDepthPassState
- 设置深度渲染状态,以控制深度测试和深度写入
- 调用DrawRenderState.SetBlendState方法来设置混合状态为CW_NONE,这将禁用颜色写入。
- 调用DrawRenderState.SetDepthStencilState方法来设置深度模板状态为true和CF_DepthNearOrEqual,这将启用深度测试和深度写入。
渲染截帧
171habtuvm6d2ae1n2fa5d63ct.png
Base pass
说明
- RenderBasePass / TBasePassDrawingPolicy。渲染不透明和遮盖的材质,向 GBuffer 输出材质属性。光照图贡献和天空光照也会在此计算并加入场景颜色。
- 渲染基础通道(Base Pass)。基础通道是渲染管线中的一个重要步骤,用于绘制场景中的几何体并填充G缓冲区(G-buffer)。G缓冲区是一个包含深度、法线、颜色等信息的纹理集合,用于后续的光照计算和渲染。Unreal 使用了 MRT 技术来进行 GBuffer 的渲染。
渲染截帧
280a77h68ncrpc0f7pkm61qtol.png
关键函数
- FDeferredShadingSceneRenderer::RenderBasePass
- 渲染基础通道,绘制场景几何体并填充G-buffer
- 代码流程
- 这段代码是一个用于渲染基础通道(Base Pass)的函数。基础通道是渲染管线中的一个重要步骤,用于绘制场景中的几何体并填充G缓冲区(G-buffer)。G缓冲区是一个包含深度、法线、颜色等信息的纹理集合,用于后续的光照计算和渲染。
- 根据一些条件判断是否需要进行场景清除操作。清除操作可以选择使用RHICmdList.Clear函数、使用远处Z平面进行清除,或者不进行清除。具体的清除方法可以通过r.ClearSceneMethod变量进行配置。
- 根据一些视图标志(ViewFamily.EngineShowFlags)和配置,判断是否需要进行特殊的渲染操作,比如绘制线框模式、着色器复杂度视图模式、静态光照重叠视图模式等。
- 根据是否启用了前向渲染(Forward Shading),设置基础通道所需的纹理绑定。如果启用了前向渲染,会设置场景深度、屏幕空间环境光遮蔽(SSAO)和屏幕空间阴影遮罩(Shadow Mask)等纹理。如果没有启用前向渲染且深度写入被禁用,会将场景深度纹理设置为只读。
- 调用RenderBasePassInternal函数进行实际的基础通道渲染。该函数会使用之前设置的纹理绑定、深度模板访问权限、前向渲染相关纹理等进行渲染。
- 在渲染完成后,会依次调用各个场景视图扩展(ViewExtension)的PostRenderBasePassDeferred_RenderThread函数,进行基础通道渲染后处理。
- 根据需要进行远处Z平面的清除、各向异性通道的渲染和调试探针材质的处理。
- FDeferredShadingSceneRenderer::RenderBasePassInternal
- 渲染基础通道的内部函数
- 代码流程
- 函数检查是否需要渲染Nanite深度通道。如果不需要预渲染通道,它会调用RenderNaniteDepthPass函数来渲染Nanite深度。该函数使用了一些参数和场景信息来生成深度目标。
- 函数使用条件语句和循环来处理不同的视图。对于每个视图,它执行以下操作:
- 设置视图的渲染状态和参数。
- 创建基础通道的参数对象。
- 构建并添加基础通道的渲染命令。
- 如果启用了Nanite,它会调用RenderNaniteBasePass函数来渲染Nanite基础通道。
- 渲染编辑器原语。
- 如果需要渲染大气效果,它会调用RenderSkyPass函数来渲染天空通道。
- 渲染步骤
- 全流程(折叠了各材质的渲染)
1nhcruf6l01h7q1ihu08qidfdq.png
-
-
- 一个材质的渲染
-
20s7ruip9nev28vjk9ekdprc7s.png
-
-
- Unreal GBuffer 中的数据
-
4ha6cde4rtai9esidghtjsovb4.png
-
-
- Unreal GBuffer 中的数据截帧示例
-
38d4e1mvd09ajcp2dt0lg82ir2.png
-
-
- 关于 Nanite 的渲染步骤以及启用并行与否的步骤差异,见后文。
-
Issue Occlusion Queries / BeginOcclusionTests
说明
- 提出将用于下一帧的 InitViews 的延迟遮蔽查询。这会通过渲染所查询物体周围的相邻的框、有时还会将相邻的框组合在一起以减少绘制调用来完成。
- 遮挡测试根据 DepthPass.EarlyZPassMode 是否为 EDepthDrawingMode::DDM_AllOccluders 且 bIsEarlyDepthComplete ,可以在 BasePass 之前执行,否则在之后执行。
渲染截帧
5f59p2t34p855m7i1n1437os7p.png
关键函数
- FDeferredShadingSceneRenderer::RenderOcclusion
- 根据bIsOcclusionTesting的值执行不同的操作,包括更新深度缓冲区、发出遮挡测试的查询、渲染HZB以及提交命令到GPU。
- 代码流程
- 如果使用了降采样的遮挡查询,则将DownsampleFactor设置为SceneTextures.Config.SmallDepthDownsampleFactor,将OcclusionDepthTexture设置为SceneTextures.SmallDepth,并为每个视图执行降采样深度的操作。
- 分配并发出遮挡测试的查询。
- 计算需要进行批处理的遮挡查询的数量。
- 创建渲染目标参数,并设置深度模板绑定和遮挡查询的数量。
- 添加一个渲染通道,用于开始遮挡测试。
- 如果bUseHzbOcclusion为true或bIsOcclusionTesting为true,则向RHI提交命令的提示。
- 对遮挡测试进行栅格化。
- 调用RenderHzb函数,根据bUseHzbOcclusion和bIsOcclusionTesting的值来渲染HZB(Hierarchical Z-Buffer)。
- 提示RHI尽可能将命令提交到GPU,以避免下一帧在某些平台上等待这些查询结果时出现CPU停顿。
- 如果bIsOcclusionTesting为true,则对遮挡测试进行栅格化。
- BeginOcclusionTests
- 实现了一个遮挡测试的函数,用于在给定的视图中执行遮挡测试,并将结果提交给相关属性。
- 代码流程
- 使用一些图形渲染管线的初始化器对象GraphicsPSOInit,并设置了一些渲染状态,如深度测试、光栅化状态等。
- 遍历给定的Views数组,对每个视图执行遮挡测试。
- 在每个视图的遮挡测试中,它首先设置一些渲染状态,如视口、顶点着色器等。
- 然后,它根据给定的查询信息,准备顶点数据,并创建一个顶点缓冲区。
- 接下来,它执行一系列的光源遮挡查询和反射遮挡查询。
- 最后,如果需要刷新查询,它会将查询结果提交给View对象的相关属性。
- 渲染步骤
18bsrace94do08viv40sj54opj.png
6rumkupgci0kubc8dflhhs2o2h.png
Lighting
说明
- 阴影图将对各个光照渲染,光照贡献会累加到场景颜色,并使用标准延迟和平铺延迟着色。光照也会在透明光照体积中累加。
- 根据BasePass输出的场景颜色信息(GBuffer)渲染光照。
渲染截帧
2s8bq2ijq3f5k0qmel10nkp9ca.png
关键函数
- FDeferredShadingSceneRenderer::RenderDiffuseIndirectAndAmbientOcclusion
- 渲染间接漫反射和环境光遮蔽
- 代码流程
- 检查是否需要跳过渲染,根据全局变量GLumenVisualizeIndirectDiffuse和视图的光照剔除标志来判断。如果需要跳过渲染,则直接返回。
- 循环遍历所有的视图。在每个视图中,函数获取视图的管线状态,并根据一些条件判断是否需要跳过当前视图的渲染。
- 设置一些参数,包括场景纹理参数、场景颜色纹理等。
- 根据视图的不同的漫反射间接方法,执行不同的渲染逻辑。如果漫反射间接方法是SSGI(屏幕空间全局光照),则会进行屏幕空间光线追踪,并对之前的场景颜色进行降采样。然后,函数调用屏幕空间去噪器进行去噪处理。
- 根据是否存在Lumen光源和是否只合成常规Lumen光照,决定是否需要执行光照合成或预计算光照。
- 函数结束循环,完成渲染。
- 渲染步骤
1bteqnkkcgdnnv2ja0esv6jq88.png
- FDeferredShadingSceneRenderer::RenderIndirectCapsuleShadows
- 渲染胶囊体阴影
- FDeferredShadingSceneRenderer::RenderDFAOAsIndirectShadowing
- 渲染DFAO,计算并应用距离场光照效果
- 代码流程
- 创建一个用于渲染距离场法线的纹理。
- 分配用于对象瓦片交集的缓冲区。
- 如果使用对象距离场环境光遮蔽,则分配用于对象剔除的缓冲区,并将对象剔除到视图中。
- 计算距离场法线。
- 如果使用对象距离场环境光遮蔽,则构建对象瓦片列表。
- 渲染距离场环境光遮蔽屏幕网格。
- 渲染可移动天光的胶囊阴影。
- 如果需要调制到场景颜色或可视化环境光遮蔽,则将弯曲法线环境光遮蔽纹理上采样到全分辨率,并写入输出纹理。
- 将输出纹理赋值给OutDynamicBentNormalAO。
- 渲染步骤
37c096s90rhrmhcf5nvmv6r5lo.png
- FDeferredShadingSceneRenderer::RenderLights
- 渲染直接光照
- 代码流程
- 检查是否需要使用头发光照(bUseHairLighting)。如果需要,会创建一个虚拟的头发透射遮罩数据(DummyTransmittanceMaskData)。
- 如果启用了 EngineShowFlags.DirectLighting 并且支持体积纹理渲染,会进入一个名为 InjectTranslucencyLightingVolume 的作用域。在这个作用域中,会将简单光源的透明度光照注入到透明度光照体积纹理中。
- 处理有阴影和光函数的光源。首先,会收集所有光源的注入数据。然后,根据是否绘制阴影和光源的遮罩类型,渲染光函数和预览阴影指示器。如果不绘制阴影,会增加一个统计计数器。
- 如果没有渲染到遮罩中,会将遮罩纹理设置为 nullptr。
- 将光源渲染到场景颜色缓冲区,根据是否省略屏幕空间阴影遮罩,条件地使用衰减缓冲区或一个1x1的白色纹理作为输入。
- 如果启用了头发光照,会对每个视图进行处理,渲染头发的光照。
- RenderLight
- 根据光源类型和其他参数设置着色器的参数,并调用InternalRenderLight函数进行光源的实际渲染。
- 代码流程
- 在函数的开头,会检查光源是否应该在当前视图中进行渲染,如果不应该,则直接返回。接下来,会记录光源的渲染时间和使用标准延迟渲染的光源数量。
- 函数中的一部分代码是用于调试重叠着色器的。如果bRenderOverlap参数为true,则会创建一个FRenderLightOverlapParameters结构体,并设置其成员变量。然后根据光源类型和其他参数设置着色器的参数,并调用InternalRenderLight函数进行光源的实际渲染。这部分代码主要用于在调试过程中查看光源的重叠情况。
- 接下来的代码是光源的实际渲染部分。首先创建一个FRenderLightParameters结构体,并设置其成员变量。然后根据光源类型和其他参数设置着色器的参数,并调用InternalRenderLight函数进行光源的实际渲染。这部分代码会根据光源类型的不同,分别渲染不同类型的瓷砖。
- 最后,根据是否启用了Strata功能,决定是否进行瓷砖渲染。如果启用了Strata功能,则会分别渲染复杂、单一和简单类型的瓷砖。否则,会渲染标准延迟渲染的光源。
- InternalRenderLight
- 渲染光源的函数模板,根据光源类型的不同,使用不同的着色器和渲染状态来进行渲染。
- 代码流程
- 函数首先获取光源的一些属性,如是否透明、边界球和光源类型。然后,它创建了一个FGraphicsPipelineStateInitializer对象,并设置了一些渲染状态,如混合状态、图元类型和视口。
- 接下来,根据光源类型的不同,函数分为两个分支进行处理。如果光源类型是方向光源(LightType_Directional),则使用相应的顶点着色器和像素着色器来渲染光源。在这个分支中,还有一些与Strata渲染相关的代码,用于设置瓦片参数和绘制间接调用。
- 如果光源类型是径向光源(LightType_Point、LightType_Spot、LightType_Rect),则使用另一组顶点着色器和像素着色器来渲染光源。在这个分支中,还有一些与深度边界测试相关的代码,用于设置深度边界和剔除不需要处理的像素。
- 最后,函数将渲染任务添加到渲染图构建器中,以便在渲染过程中执行。
- 渲染步骤
3ps00d3m7n5na2jfngi2pjg798.png
- AddSubsurfacePass
- 渲染次表面散射(SSR)
- FDeferredShadingSceneRenderer::RenderDeferredReflectionsAndSkyLighting
- 渲染仅对不透明像素起作用的漫射天空照明和反射
- 代码流程
- 判断是否需要计算天空光照的贡献,并根据一些条件来决定是否应用天空阴影。如果需要应用天空阴影,则会调用RenderDistanceFieldLighting函数来计算距离场光照。
- 并判断是否需要进行反射环境计算。
- 会获取场景纹理参数,并创建一些用于屏幕空间去噪的输入纹理。然后,对每个视图进行循环处理。
- 在循环中,根据一些条件来决定是否执行光线追踪反射或屏幕空间反射。如果需要执行光线追踪反射,则会调用RenderRayTracingReflections函数进行渲染。如果需要执行屏幕空间反射,则会调用ScreenSpaceRayTracing::RenderScreenSpaceReflections函数进行渲染。
- 根据是否需要去噪来决定是否应用去噪器。如果需要去噪,则会调用相应的去噪器函数进行去噪操作。
- 根据一些条件来决定是否需要渲染平面反射,并调用RenderDeferredPlanarReflections函数进行渲染。
- 根据一些条件来决定是否需要应用天空光照,并调用相应的函数进行渲染。如果存在头发条带数据,则会调用RenderHairStrandsEnvironmentLighting函数进行渲染。
- 调用AddResolveSceneColorPass函数来解析场景颜色纹理。
- 渲染步骤
2359gl0hrvmlvogve9gnusrpng.png
- ScreenSpaceRayTracing::RenderScreenSpaceReflections
- 根据不同的SSR质量和输入条件来渲染屏幕空间反射效果。
- 代码流程
- 根据SSR质量和输入条件确定输入颜色InputColor。
- 根据是否需要进行SSR模板预处理和是否存在平铺反射来选择不同的渲染路径。
- 如果不需要进行SSR模板预处理且不存在平铺反射,函数会创建一个全屏三角形,并使用像素着色器FScreenSpaceReflectionsPS来渲染屏幕空间反射效果。
- 如果存在平铺反射,函数会使用顶点着色器FScreenSpaceReflectionsTileVS和像素着色器FScreenSpaceReflectionsPS来渲染屏幕空间反射效果。
- 无论使用哪种渲染路径,函数都会设置视口、初始化图形管线状态、设置着色器参数,并绘制全屏三角形或间接绘制。
- 最后,函数释放不再使用的图形资源。
- 渲染步骤
1h3bdk7s1s7ck78pt0sq3873tn.png
Fog & SkyAtmosphere
说明
- 雾和大气在延迟通道中对不透明表面进行逐个像素计算。
渲染截帧
51kc3msg5hh9te046vk3p4scqm.png
4mepool4tq5uhn3e6pk5cjnb1v.png
6cosabhneuonfnqnpmrdpf7dun.png
关键函数
- FDeferredShadingSceneRenderer::RenderFog
- 实现了在渲染场景时处理雾效的功能。它使用了RDG命令来描述渲染过程中的依赖关系,并通过循环遍历所有的视图来执行雾效的渲染
- 步骤
- 使用条件语句来确定是否需要渲染雾效。条件包括检查场景中是否存在指数雾效(Scene->ExponentialFogs.Num() > 0)以及是否启用了前向渲染(!IsForwardShadingEnabled(ShaderPlatform))。只有当这两个条件都满足时,才会执行雾效的渲染。
- 使用RDG(Render Dependency Graph)命令来设置雾效的渲染。RDG是一种用于描述渲染过程中资源依赖关系的图形语言。通过使用RDG命令,可以将渲染任务分解为多个独立的步骤,并在这些步骤之间建立正确的依赖关系。
- 在循环中遍历所有的视图(Views),并对每个视图进行雾效的渲染。只有当视图是透视投影时,才会执行雾效的渲染。在每个视图的渲染过程中,使用RDG命令来设置渲染目标、深度模板等参数,并最终调用了RenderViewFog函数来实际执行雾效的渲染。
- RenderViewFog
- 实现了一个渲染视图的雾效果。使用了一些图形管线状态和着色器来控制渲染过程,并通过深度边界优化来提高性能
- 步骤
- 声明一个FGraphicsPipelineStateInitializer对象GraphicsPSOInit,用于初始化图形管线状态。然后,通过调用RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit)和RHICmdList.SetViewport(ViewRect.Min.X, ViewRect.Min.Y, 0.0f, ViewRect.Max.X, ViewRect.Max.Y, 1.0f)来设置渲染目标和视口。
- 设置图形管线状态的属性,包括光栅化器状态、深度模板状态和混合状态。这些属性将影响后续的渲染操作。
- 创建一个顶点着色器对象VertexShader和一个像素着色器对象PixelShader,并将它们绑定到图形管线状态中。
- 根据一些条件设置了像素着色器的一些参数,包括是否支持雾散射纹理、是否支持雾的方向光散射和是否支持体积雾。这些参数将影响像素着色器的执行。
- 根据平台的支持情况设置了深度边界优化。如果支持深度边界测试,则计算了雾的起始距离,并根据计算结果设置了深度边界。这个优化可以跳过离相机更近的像素的着色器执行,从而提高渲染性能。
- 调用辅助函数来清除未使用的图形资源,并设置了顶点着色器和像素着色器的参数。
- 通过绘制一个覆盖整个视图的四边形来执行渲染操作。
- FSceneRenderer::RenderSkyAtmosphere
- 渲染天空大气效果,并根据不同的视图设置不同的属性和参数来实现不同的效果
- FSceneRenderer::RenderSkyAtmosphereInternal
- 检查场景是否具有天空大气效果,获取相关信息和设置,然后在场景亮度缓冲区上渲染天空和大气透视效果。
- 步骤
- 检查场景是否具有天空大气效果。获取与天空大气相关的信息,如天空大气场景代理和大气设置。获取视图矩阵,并设置一些常量和采样器状态。
- 在渲染天空和大气透视效果之前,首先计算一些参数,如云阴影在大气层上的强度、是否需要采样云阴影等。然后根据这些参数创建像素着色器和顶点着色器,并为它们分配参数。
- 调用GraphBuilder.AddPass函数来添加一个渲染通道。在这个渲染通道中,设置视口、图形管线状态和着色器参数,并绘制一个三角形。
- 相关 Shader
- FRenderSkyAtmosphereVS
- SkyAtmosphereVS
- FRenderSkyAtmosphereVS
06qsrhuca4dgihs31h585413l1.png
34luf5n28db8la2g4ls4u4p9co.png
-
-
- FRenderSkyAtmospherePS
- RenderSkyAtmosphereRayMarchingPS
- FRenderSkyAtmospherePS
-
7q7h98ppc223kvrjshstgbh7i9.png
5pob2p6rb2ql73k1ajimklib82.png
Translucency
说明
- 按照由后往前(由远及近)的顺序对半透明物体进行渲染
- 排序算法:OIT::AddSortTrianglesPass,不详述,有需要的可以阅读源码或参阅:【UE5】浅析UE5.1OIT半透明排序算法
0lf3tlvs0o13oo05uj0f7s2jv3.png
- 可以有多个 pass
49qag704t67fp3ute5jrvtk1qf.png
- 根据 AllowTranslucencyAfterDOF (可通过 r.SeparateTranslucency 来控制),决定半透明景深对半透明的影响。
- r.SeparateTranslucency 0,关闭(半透明度受景深影响)
0go31fj53rb3vegq5nc5macjci.png
-
- r.SeparateTranslucency 1:会消耗 GPU 性能和内存,但保持半透明度不受景深的影响。 (默认)
381gci8p1iniocl46426epii9r.png
23do590g13sf8ihrrm2tsnndcq.png
- 根据 r.ParallelTranslucency 决定是否启用并行半透渲染
- r.ParallelTranslucency 1
3jrhfsopss8mgl0216ula0m84b.png
-
- r.ParallelTranslucency 0
1n7iai9okb1lbr8ps9eoaccau5.png
68qralbtvp2u0oe3rdh48qglbi.png
- 透明度累加到屏外渲染目标,在其中它应用了逐个顶点的雾化,因而可以整合到场景中。光照透明度在一个通道中计算最终光照以正确融合。
渲染截帧
706aqlod159ne85vff93riqn3d.png
关键函数
- FDeferredShadingSceneRenderer::RenderTranslucency
- FDeferredShadingSceneRenderer::RenderTranslucencyInner
- RenderTranslucencyViewInner
Post Processing
说明
- 多种后期处理效果均通过 GBuffers 应用。透明度将合成到场景中。
- 根据输入参数和条件判断来确定需要执行的后处理效果(如MotionBlur、Bloom、Tonemap、FXAA等),并将结果存储到相应的纹理中。
00dc8g1j4gkpdcudsnd2nv4src.png
- 支持通过 SceneViewExtension 添加自己的后处理效果到渲染管线后处理阶段之前或开始时。
236lu2iv424629lsadff0nmsqp.png
082l66pplavg3q55v3g9ouqgpq.png
- 各后效也有在 EPass 中定义
67o5vlfqbfvuh1pbdpklatup2t.png
- 各后效会根据配置、环境或 EngineShowFlags 等设置可用性
6cudfgrmi04ko8i8v6ebhjj43u.png
渲染截帧
757uftl90m4o43l2pqhpbtg3fk.png
关键函数
- AddPostProcessingPasses
Nanite 管线
说明
- Nanite 的渲染整合在延迟渲染管线中,主要逻辑和流程为 Nanite::CullRasterize 和 Nanite::BasePass 等 ,分别位于 PrePass 之后 InitView 之前及 Base Pass 之中。
- 此处仅放出延迟渲染管线下的 Nanite 相关流程步骤的截帧,不对每个步骤做解释说明。
渲染截帧
1369vjud5ek51sfoomtbqinun2.png
关键函数
- Nanite::InitContext
7t5dm88gjv6665fgduf3sfnff6.png
- Nanite::VisBuffer
77cbo3tuv34rmikkoaj548da5v.png
- Nanite::EmitDepthTargets
2svkr1mfsae9e3vckiueu9q9km.png
- Nanite::Readback
29ik19pe1kj1lrrvh5d620gg98.png
Mobile 管线
说明
- 对 Unreal 的手机管线做简单介绍,部分底层原理机制跟延迟渲染管线重叠或类似,所以本节不会很详细。
关键函数
- FMobileSceneRenderer::Render
- 此为手机渲染管线的入口,与延迟渲染管线入口(FDeferredShadingSceneRenderer::Render)一样,基于基类接口统一调用执行。
- 代码流程
- 设置命令列表统计信息和GPU统计范围。
- 更新场景中所有基本场景信息。
- 准备视图矩形以进行渲染。
- 检查是否需要渲染天空大气效果,并根据需要准备太阳光源。
- 检查是否需要渲染,并执行一些性能统计和计时。
- 如果不需要渲染,则直接返回。
- 等待遮挡测试完成。
- 初始化场景纹理配置和全局系统纹理。
- 创建和初始化渲染目标和缓冲区。
- 初始化视图,包括剔除和排序可见的物体。
- 如果是第一次渲染场景,则初始化一些渲染相关的设置。
- 如果启用了延迟渲染,则更新矩形光源图集和IES图集的纹理。
- 创建和初始化实例剔除管理器。
- 初始化虚拟阴影贴图数组。
- 设置命令列表统计信息。
- 渲染前的准备工作,包括设置渲染目标和清除缓冲区。
- 渲染场景,根据是否启用延迟渲染选择渲染方式。
- 结束遮挡测试。
- 如果没有启用完整深度预处理,则等待遮挡测试完成。
- 设置移动场景纹理的配置模式。
- 如果需要渲染物体的速度,则渲染可移动物体的速度。
- 渲染后处理效果,包括渲染不透明物体之后的扩展效果。
- 渲染不透明特效。
- 如果需要渲染像素投影的平面反射,则渲染像素投影的平面反射。
- 如果使用虚拟纹理,则更新虚拟纹理。
- 如果需要解析场景颜色,则进行后处理。
- 广播后渲染委托。
- 设置命令列表统计信息。
- 渲染结束,包括一些清理工作。
- 执行遮挡查询。
- 队列场景纹理提取。
- FMobileSceneRenderer::RenderForwardSinglePass
- 代码流程
- 函数的主要目的是在移动设备上进行场景渲染。它使用了GraphBuilder对象来构建渲染图,并将PassParameters对象传递给渲染Pass中的各个阶段。此外,它还使用了ViewContext对象来获取视图信息,并使用SceneTextures对象来存储场景纹理。
- 函数的实现包含了多个渲染阶段。首先,它设置了渲染目标的子渲染提示为DepthReadSubpass。然后,它根据一些条件计算是否执行遮挡查询,并将结果存储在PassParameters对象的NumOcclusionQueries成员变量中。
- 接下来,函数使用GraphBuilder对象添加了一个 SceneColorRendering 渲染Pass。这个过程具有一些特定的标志,如ERDGPassFlags::Raster和ERDGPassFlags::NeverMerge。这个过程使用了一个lambda函数作为回调,其中包含了一系列渲染命令。
- 在lambda函数中,首先检查了一些条件,如果满足,则绘制一个清除屏幕的四边形。然后,执行了深度预渲染、不透明和遮罩渲染、移动设备基础渲染、移动设备调试视图渲染等一系列渲染操作。在这些渲染操作之间,还进行了遮挡查询的轮询和基础渲染的后处理。
- 接下来,函数调用了RHICmdList.NextSubpass(),表示进入下一个子渲染Pass。在这个过程中,执行了一些与透明度相关的渲染操作,如渲染贴花、渲染调制阴影投影、渲染雾等。最后,绘制了透明物体。
- 如果满足条件bDoOcclusionQueries,则执行了遮挡查询的渲染操作。在这个操作中,首先设置了一些统计信息,然后根据一些条件判断是否启用了Adreno遮挡模式,并在需要时刷新渲染命令。最后,执行了遮挡渲染。
- 最后,函数根据条件!bIsFullDepthPrepassEnabled,执行了一个解析多重采样抗锯齿(MSAA)深度的渲染操作。
- 渲染步骤
- 代码流程
05507tj7na7frr1rtfqj87jlf1.png
- FMobileSceneRenderer::RenderDeferredSinglePass
- 在移动设备上进行延迟渲染的单次渲染Pass。它按照一定的顺序执行一系列的渲染操作,包括深度预渲染、不透明和遮罩渲染、后处理、透明渲染、场景颜色和GBuffer写入、移动设备延迟着色、雾效渲染、半透明渲染以及遮挡查询的渲染(如果适用)。
- 代码流程
- 设置渲染目标的子渲染提示为DeferredShadingSubpass。
- 根据一些条件判断是否进行遮挡查询,并设置遮挡查询的数量。
- 使用GraphBuilder.AddPass方法添加一个渲染Pass。这个过程包含了一系列的渲染操作,使用了之前设置的参数。
- 在渲染Pass中,首先进行深度预渲染(Depth pre-pass),然后进行不透明和遮罩渲染(Opaque and masked),接着进行后处理(PostRenderBasePass),然后进行透明渲染(RenderDecals),再进行场景颜色和GBuffer写入(SceneColor + GBuffer write),最后进行移动设备延迟着色(MobileDeferredShadingPass)。
- 如果使用了像素本地存储(Pixel Local Storage),则会进行相应的拷贝操作(MobileDeferredCopyBuffer)。
- 进行雾效渲染(RenderFog)和半透明渲染(RenderTranslucency)。
- 如果进行了遮挡查询,最后会进行遮挡查询的渲染(RenderOcclusion)。
- 渲染步骤
4c91b5dnsceh560ftrel7b4qfh.png
渲染截帧
见异同对比
异同对比
说明
- 本节对各种条件下的渲染管线等进行异同对比分析。
- 请注意:对比为逐层级进行,每一层只关注当前对比项的主要异同,对比项内部的其它细节在对应层内部进行。如首层对 UE 和 OpenGL 等图形API进行对比,只比较他们的异同,对 UE 下的具体细节(如延迟vs前向、手机vsPC等)的对比在 UE 层内部进行。
概要
- Unreal 通过 Render Hardware Interface(渲染硬件接口,以下简称 RHI )封装了对各图形API(如DirectX/OpenGL/Vulkan/Metal等)的调用,使得gamethread 和 renderthread 可以通过同样的代码接口和资源来实现对不同平台的渲染支持。
- Unreal 的渲染管线和 OpenGL 等图形API的渲染管线在基本概念上是相似的,但在实现细节和功能上有很大的区别。Unreal 对管线进行了定制化,提供了更高级的渲染特性(如全局光照、物理基础渲染(PBR)、后处理效果等)和更多的优化(如 预编译着色器、批处理渲染命令、使用高效的数据结构等)。这些特性和优化在OpenGL等中可能需要开发者自己实现。
- 以通过 RenderDoc 对 Unreal 的场景渲染流程进行截帧分析为例,在其延迟渲染管线的 basepass 阶段发起的对一个 mesh 的绘制中,涉及对图形API(此处为D3D12)的多个调用(RenderDoc 所示的 event 和 action),这些调用涉及该图形渲染管线 pipeline 的多个阶段。
7lucku28gbq5cnulrqkr990225.png
DirectX/OpenGL/Vulkan/Metal等图形API
- 现代图形API的渲染管线是用于将3D模型转换为最终图像的过程。它是图形渲染的核心部分,负责处理几何数据、光照计算、纹理映射和最终的像素输出。
- 这些阶段在现代图形API中通常由GPU硬件执行,以实现高效的图形渲染。不同的图形API(D3D11/D3D12/OpenGL/Vulkan/Metal等)可能会有一些细微的差异,但总体上渲染管线的基本原理是相似的。
- 一个典型的现代图形API渲染管线
- 顶点输入阶段(Vertex Input Stage):该阶段负责接收顶点数据,并将其传递给下一个阶段。顶点数据包括顶点坐标、法线、纹理坐标等。
- 顶点着色器阶段(Vertex Shader Stage):该阶段对每个顶点进行处理,执行一些计算和变换操作。例如,可以对顶点进行平移、旋转、缩放等操作。
- 图元装配阶段(Primitive Assembly Stage):该阶段将顶点组装成图元,如点、线、三角形等。这些图元将用于后续的光栅化操作。
- 几何着色器阶段(Geometry Shader Stage):该阶段可以对图元进行进一步的处理和变换。例如,可以生成新的图元,或者对图元进行剪裁操作。
- 光栅化阶段(Rasterization Stage):该阶段将图元转换为像素,并确定每个像素的位置和颜色。
- 片段着色器阶段(Fragment Shader Stage):该阶段对每个像素进行处理,执行光照计算、纹理采样等操作,最终确定像素的最终颜色。
- 输出合并阶段(Output Merger Stage):该阶段将最终的像素颜色与深度值进行合并,并输出到帧缓冲区。
Unreal Engine
概要
- UE5和UE4的渲染管线虽然从流程上看相差不大,但效果和性能其实都有了很多的改进,主要因为如下方面。
- 光线追踪
- UE5引入了光线追踪技术,使得渲染效果更加真实。传统的UE4渲染管线使用的是基于光栅化的渲染方法,而UE5在这方面有了非常大的改进,使得光线追踪成为UE5渲染管线的一个重要组成部分。
- Lumen全局光照
- UE5引入了Lumen全局光照技术,它是一种真正的实时全局光照解决方案,使得场景中的光照更加真实和自然。而UE4使用的是间接光照的近似计算方法,无法达到真正的全局光照效果。
- Nanite几何体渲染
- UE5引入了Nanite几何体渲染技术,它可以实现高精度、高效率的几何体渲染,使得场景中的细节更加丰富。而UE4使用的是标准的三角形渲染方法,无法达到Nanite的渲染效果。
- 其他细节改进
- UE5还在一些细节上进行了改进,比如细节几何、动态全局照明、GPU粒子等,使得UE5的渲染效果和性能都有了很大的提升。
- 光线追踪
UE5
PC(Deferred Shadering)
启用 lumen
- ProfileGPU
2l1m44q2783n14k8mduiknjcr1.png
- RenderDoc
7hjph8jrupi2d4p5sjl6c7f9l9.png
禁用 lumen
- ProfileGPU
2ti7jtlrlkd0tfahfs7kosvm1e.png
- RenderDoc
4aetv3vpj7ulip5rvum3tud234.png
概要
- 说明
- Lumen 是一种完全动态的全局照明和反射解决方案,当与硬件光线追踪和禁用距离场一起使用时,Lumen 支持几何缓存和骨架网格物体。
- Lumen 不适用于照明通道。光照通道仅影响动态光源的直接光照,因此这些光源的间接光照不会影响场景中的 Lumen 计算。
- 使用开箱即用的 Lumen 时,屏幕空间环境光遮挡 (SSAO) 默认处于关闭状态。这是因为环境光遮挡是一种古老的技巧,用于在光线追踪之前给人留下正确 GI 的印象。Lumen 的照明模型基于光线追踪并模仿正确的 GI - 从而消除了对环境光遮挡的需要。
- 因为 Lumen 是一种光照解决方案,所以它对管线的影响也主要是光照相关的部分,涉及多个步骤。
- 影响步骤
6sjov6jht27mdh83b2bfvg700g.png
- 关键函数
- FDeferredShadingSceneRenderer::BeginUpdateLumenSceneTasks
- 重建场景距离场体素
- 代码步骤
- 检查是否有任何Lumen活动,并重置LumenCardRenderer。
- 更新LumenSceneData的一些属性,并根据需要重新分配纹理图集。
- 更新Lumen场景的几何体和远景场景。
- 根据相机的位置和设置更新Lumen场景的表面缓存。
- 填充帧临时数据,并根据需要更新Lumen卡片场景的统一缓冲区。
- 上传页面表并设置网格渲染命令。
- FDeferredShadingSceneRenderer::UpdateLumenScene
- 更新 Lumen 场景和捕捉卡片数据,以便后续的渲染操作可以使用最新的数据。
- 步骤
- 遍历所有的视图,检查是否有任何一个视图启用了Lumen效果。如果有,更新该视图对应的Lumen场景数据。
- 分配用于捕捉卡片的临时渲染目标,并检查是否有需要渲染的卡片。如果有,进行卡片的剔除和光栅化操作。
- 更新Lumen表面缓存的纹理图集。
- FDeferredShadingSceneRenderer::RenderLumenSceneLighting
- 渲染Lumen场景的光照效果(主要是直接光照和辐射光照)
- 步骤
- 检查是否有任何Lumen相关的操作需要执行。如果有,就会执行一系列的操作来渲染Lumen场景的光照效果。
- 具体来说:增加表面缓存的更新帧索引,然后检查是否有卡片页面。如果有,执行一些清除渲染目标的操作,然后构建卡片更新上下文。
- 接下来,调用RenderDirectLightingForLumenScene和RenderRadiosityForLumenScene函数来渲染直接光照和辐射光照。
- 最后,设置LumenSceneData.bFinalLightingAtlasContentsValid为true。
- FDeferredShadingSceneRenderer::RenderDiffuseIndirectAndAmbientOcclusion
- 渲染间接漫反射和环境遮挡,将视图的漫反射间接光照渲染为场景颜色。
- 步骤
- 首先检查是否需要跳过渲染,如果需要跳过,则直接返回。然后,设置渲染参数和纹理,并对每个视图进行循环处理。
- 在每个视图中,根据视图的不同设置参数,并进行光线追踪的配置。然后,调用名为ApplyDiffuseIndirect的函数根据不同的EStrataTileType参数进行渲染操作。
- 接下来,检查是否需要应用环境立方体贴图,并调用ApplyAmbientCubemapComposite根据不同的EStrataTileType参数进行渲染操作。
- 最后,根据bCompositeRegularLumenOnly的值,调用DoneComposite或DonePreLights函数。
- 也可以查阅源码或参阅:UE5-lumen的光照计算RenderLumenSceneLighting
Mobile
概要
- Mobile 上用 Forward Shading 还是 Deferred Shading ,主要判断为 IsMobileDeferredShadingEnabled ,其根据 r.Mobile.ShadingPath 以及平台支持度来决定。
前向渲染(Forward Rendering )和延迟渲染( Deferred Rendering )的异同
- 前向渲染(Forward Rendering)
- 前向渲染是一种传统的渲染方法。它在渲染每个像素时,会直接计算所有光源对该像素的影响。这种方式对于每个像素,都会计算所有的光源,即使实际上光源对该像素没有影响,这样的计算在光源较多的场景中效率会较低。
- 优点
- 前向渲染的技术成熟,支持多种材质类型。
- 比延迟渲染在处理透明物体和反射时更有优势。
- 缺点
- 光源数量增加,计算量会大幅增加,对性能有较大影响。
- 对于大量像素和复杂光照的场景,效率较低。
- 适用场合
- 适用于光源较少,需要处理透明物体和复杂反射的场景,或者对性能要求较高的场景(如VR)。
- 限制
- 主要的限制是性能,尤其是在光源多的场景中,前向渲染的性能可能会迅速下降。
- 延迟渲染(Deferred Rendering)
- 延迟渲染首先会创建一个叫做G-Buffer的数据结构,其中包含了场景的几何信息和材质属性,然后在一个后处理步骤中对每个光源进行渲染。这样做的好处是,只有那些真正受光源影响的像素才会进行光照计算,从而提高了效率。
- 延迟着色将渲染流程分为两个处理阶段:
- 几何体处理阶段 :处理基础颜色(BaseColor)、金属感(Metallic)和粗糙度(Roughness)参数,并将参数存储在通常称为 Gbuffer 的临时缓冲区。
- 光照处理阶段 :从GBuffer读取材质属性,为其计算光照,然后将着色像素写入帧缓冲区。
- 同时,正向着色会在绘制时间计算材质与光影的互动。这意味着,材质必须包括光影代码以及所有其他参数。延迟着色可以忽略此信息,并使用更简单的着色器和需要编译时间更少的材质。
- 因为延迟着色不需要绑定阴影和反射纹理,对于每次绘制调用,所需的图形状态管理也更少,这样可提升CPU性能。这意味着, 渲染硬件接口(Render Hardware Interface,简称RHI) 线程工作减少,进而释放大核心用于其他线程,减少可用线程间的冲突。
- 优点
- 对大型场景和大量光源的渲染效率高。
- 减少了对每个像素和每个光源的重复计算。
- 缺点
- G-Buffer需要大量的内存和带宽。
- 对透明物体和复杂反射渲染的支持不如前向渲染。
- 延迟渲染不易与某些后处理效果(如SSAO,屏幕空间环境光遮蔽)结合。
- 适用场合
- 对于光源较多,场景复杂度较高的场景,延迟渲染可以提供更好的性能。但如果场景中包含大量的透明物体或需要复杂的反射处理,可能需要结合使用前向渲染。
- 限制
- 主要的限制是对透明物体和某些后处理效果的支持不足,同时也需要更多的内存和带宽来存储和处理G-Buffer。对GPU的并行处理能力也有要求。
- 延迟渲染需要支持一些特定的硬件功能,如多渲染目标(MRT)、蒙版缓存(Stencil Buffer)等。因此,不是所有的GPU都能完全支持延迟渲染。在一些低端设备中,可能会出现不支持延迟渲染的情况。
- 优点
Forward Shading
- ProfileGPU
5isk6pf2qta01m7ui29t57ckjt.png
- RenderDoc
2i34uhourpuu3bbdmbk6a26fne.png
Deferred Shading
- ProfileGPU
3m2c3c4cp6m4j8kdtcke2jnvkb.png
- RenderDoc
193glm5f9m11a13iga7cdpqlqo.png
备注
- Mobile 管线修改
- 配置文件
- DefaultEngine.ini
- 配置项
- [/Script/Engine.RendererSettings]
- 修改项
- 管线切换为前向或延迟
- r.Mobile.ShadingPath=1
- 0: Forward shading (default)
- 1: Deferred shading
- r.Mobile.ShadingPath=1
- UE5在编辑器中修改设置
- 配置文件
7798h4en25mlrk8u3vq2fcj7kk.png
概要
- 最大的不同在于,PC 上默认用的延迟渲染(Deferred Shading)而 Mobile 上默认用的前向渲染( Forward Shading ),且 Mobile 也支持切换为 Deferred Shading 。
- PC 上 scene 渲染的入口为 FDeferredShadingSceneRenderer::Render ,而 Mobile 的则为 FMobileSceneRenderer::Render。
UE4
PC(Deferred Shading)
- ProfileGPU
5sinof430lfokb62t40frgpggq.png
- RenderDoc
53gv16a28lk59dikl2vbin11lm.png
Mobile
- Forward Shading
- ProfileGPU
113tat6v7ei6rjms7goejkcog5.png
* RenderDoc
00jaoe133jshsd5ikcg86f5v9h.png
33p0chs5qi0crf8395jfdt2i0f.png
- Deferred Shading
- ProfileGPU
5uoos7mm4k5urpfl7rsr7mqv74.png
* RenderDoc
72d3eeol60lnqn5ni7a1kgq6k1.png
2fabb27c4emin84jq3v6ojv8u8.png
概要
- UE4 上 PC 和 Mobile 渲染管线的异同跟 UE5 差不多,主要差异在 UE5 上的新特性,但大体一致,故不再赘述,可结合源码或渲染截帧查看。
相关链接
- 从源码深入理解Unreal渲染管线 https://blog.csdn.net/hacning/article/details/131243242
- UE5渲染管线概览 https://zhuanlan.zhihu.com/p/508372052
- UE5【理论】2.延迟渲染管线DeferredShadingPipeline https://zhuanlan.zhihu.com/p/574117143
- 虚幻4渲染编程(Shader篇)【第一卷:虚幻自身的延迟渲染管线】 https://zhuanlan.zhihu.com/p/36630694
- 图形编程介绍 https://docs.unrealengine.com/5.0/zh-CN/graphics-programming-overview-for-unreal-engine/
- 虚幻引擎UE渲染框架 https://zhuanlan.zhihu.com/p/484960867
- UE4源码解析-渲染流程 https://zhuanlan.zhihu.com/p/464265397
- 调试&截帧 过一遍虚幻渲染管线下 GPU流程 https://zhuanlan.zhihu.com/p/341431133
- 移动预览器 https://docs.unrealengine.com/5.0/zh-CN/using-the-mobile-previewer-in-unreal-engine/
- 虚幻4修改引擎渲染管线解析【第一卷】通过添加自定义ShadingModle实现卡通着色来熟悉渲染管线 https://blog.csdn.net/qq_16756235/article/details/79324750
- UE5-lumen的光照计算RenderLumenSceneLighting https://blog.csdn.net/llsansun/article/details/123039025
- Lighting https://dev.epicgames.com/community/learning/tutorials/Le7b/unreal-engine-lighting
- Cursor https://www.cursor.so/
- RenderDoc https://renderdoc.org/docs/index.html
- RenderDoc使用详解 https://zhuanlan.zhihu.com/p/568990608
- Event Browser https://renderdoc.org/docs/window/event_browser.html