Unreal 渲染管线原理机制源码剖析

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 等进行自定义。
  • 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

06qsrhuca4dgihs31h585413l1.png

34luf5n28db8la2g4ls4u4p9co.png

      • FRenderSkyAtmospherePS
        • RenderSkyAtmosphereRayMarchingPS

7q7h98ppc223kvrjshstgbh7i9.png

5pob2p6rb2ql73k1ajimklib82.png

Translucency

说明

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

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
    • 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 上的新特性,但大体一致,故不再赘述,可结合源码或渲染截帧查看。

相关链接

<think>嗯,用户想了解虚幻引擎的深入知识、高级用法和内部机制。首先,我需要确定用户可能需要的具体内容。虚幻引擎非常庞大,涵盖渲染、物理、动画、AI等多个方面。用户提到“深度教程”和“高级用法”,可能希望不仅仅是基础操作,而是更底层的机制和优化技巧。 根据系统提供的引用,特别是引用[1]提到的Unreal Source Explained项目,这是一个分析虚幻引擎源代码的技术项目,涉及初始化过程、内存管理、多线程支持等。我应该优先推荐这个资源,并详细说明其内容,比如内存管理的Slab分配器和多线程的任务图系统。这些是高级开发者关心的内部机制,能帮助他们优化项目性能。 接下来,用户可能对渲染管线感兴趣。虚幻引擎的延迟渲染和Nanite虚拟几何体技术是近年来的亮点,需要解释清楚这些技术的工作原理,比如材质合并和实例化渲染,以及如何通过项目设置调整来优化性能。 物理系统的Chaos物理引擎也是一个高级话题,可以讨论碰撞优化和破坏系统的实现。动画系统的控制绑定和IK重定向则是游戏开发中提升角色动画质量的关键,需要提供实际应用案例,如如何通过蓝图扩展动画逻辑。 AI部分,环境查询系统(EQS)和行为树是构建复杂AI行为的基础,可以举例说明如何结合这些工具设计敌人的战术。另外,插件扩展和模块化开发也是高级用法,比如自定义渲染通道或物理对象,这些内容需要结合C++和蓝图混合编程的最佳实践。 用户可能还希望了解如何进一步学习,比如推荐官方资源、源码分析和社区论坛。需要确保信息准确,引用提供的资料,如引用[1]和[2]提到的引擎发展历史和内部机制分析,以及引用[3]中AI技术的实际应用,以增强回答的可信度。 最后,生成的相关问题需要覆盖用户可能感兴趣的后续方向,如渲染优化、内存管理策略、AI设计、物理模拟调试和插件开发。这些问题能引导用户深入探索不同领域,满足其进阶学习的需求。</think>### 虚幻引擎深度解析与高级用法指南 #### 一、引擎核心机制剖析 1. **初始化流程与内存管理** 虚幻引擎启动时通过$FEngineLoop::PreInit()$完成核心子系统加载,引用[1]中提到的Slab分配器($FMallocBinned$)采用分页式内存管理,可降低内存碎片。建议通过`STAT Memory`命令实时监控内存分配: ```cpp // 自定义内存追踪标记 DECLARE_MEMORY_STAT(TEXT("CustomAlloc"), STAT_CustomAlloc, STATGROUP_Memory); ``` 2. **多线程任务图系统** 任务图系统($FTaskGraphInterface$)实现自动负载均衡,引用[1]指出其通过$FWorkerThread$实现跨平台线程池管理。创建并行任务示例: ```cpp FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady([]{ // 并行计算逻辑 }, TStatId(), nullptr, ENamedThreads::AnyBackgroundThreadNormalTask); ``` #### 二、渲染管线进阶技巧 $$ \text{延迟渲染方程: } L_o = \sum_{k=1}^{n} (BDRF \cdot L_i \cdot \cos\theta) + I_{emissive} $$ - **Nanite虚拟几何体**:通过硬件加速的Mesh Shader实现,引用[1]分析其采用自适应LOD策略,运行时自动计算: $$ LOD_{level} = \log_2(\frac{ScreenSize}{PixelErrorThreshold}) $$ - **材质优化**:使用材质函数库(Material Functions)实现参数复用,通过`StaticSwitch`节点减少Shader变体 #### 三、物理系统深度优化 Chaos物理引擎的碰撞检测采用$GJK+EPA$算法组合: $$ \text{碰撞检测复杂度: } O(n \log n) $$ - **复杂碰撞体优化**:将$UCapsuleComponent$作为碰撞代理体,原始网格仅用于渲染 - **破坏系统实现**:通过$FractureTool$生成Voronoi图分割,引用[1]提到使用$APEX Destruction$兼容方案时需注意内存预分配 #### 四、AI系统高级应用 1. **环境查询系统(EQS)** 构建评分函数实现动态决策: $$ Score = W_{dist} \cdot e^{-kd} + W_{cover} \cdot C $$ ```cpp UEnvQueryTest_ScoreCover::CalculateScore() ``` 2. **行为树装饰器扩展** 自定义$BTDecorator_Cooldown$实现技能冷却逻辑,引用[3]指出可结合机器学习模型优化决策树权重 #### 五、引擎插件开发实践 创建自定义渲染通道: ```cpp class FMyRenderPass : public FSceneRenderingExtension { public: virtual void Setup() override { // 注册到PostOpaque渲染阶段 } virtual void Render() override { RHICmdList.DrawPrimitive(...); } }; ``` 通过$IMPLEMENT_MODULE()$宏实现插件模块化加载 #### 六、调试与性能分析 1. **GPU性能分析** 使用`profilegpu`命令捕获数据,分析$RHI DrawCall$分布 2. **蓝图原生事件追踪** 在`Engine.ini`中配置: ``` [ScriptDebugging] bDebuggerEnabled=true ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值