当前位置:   article > 正文

剖析虚幻渲染体系(12)- 移动端专题Part 1(UE移动端渲染分析)_ue在rendertarget技术性能

ue在rendertarget技术性能

目录

12.1 本篇概述

前面的所有篇章都是基于PC端的延迟渲染管线阐述UE的渲染体系的,特别是剖析虚幻渲染体系(04)- 延迟渲染管线详尽地阐述了在PC端的延迟渲染管线的流程和步骤。

此篇只要针对UE的移动端的渲染管线进行阐述,最终还会对比移动和和PC端的渲染差异,以及特殊的优化措施。本篇主要阐述UE渲染体系的以下内容:

  • FMobileSceneRenderer的主要流程和步骤。
  • 移动端的前向和延迟渲染管线。
  • 移动端的光影和阴影。
  • 移动端和PC端的异同,以及涉及的特殊优化技巧。

特别要指出的是,本篇分析的UE源码升级到了4.27.1,需要同步看源码的同学注意更新了。

如果要在PC的UE编辑器开启移动端渲染管线,可以选择如下所示的菜单:

等待Shader编译完成,UE编辑器的视口内便是移动端的预览效果。

12.1.1 移动设备的特点

相比PC桌面平台,移动端在尺寸、电量、硬件性能等诸多方面都存在显著的差异,具体表现在:

  • 更小的尺寸。移动端的便携性就要求整机设备必须轻巧,可置于掌中或口袋内,所以整机只能限制在非常小的体积之内。

  • 有限的能量和功率。受限于电池存储技术,目前的主流锂电池普通在1万毫安,但移动设备的分辨率、画质却越来越高,为了满足足够长的续航和散热限制,必须严格控制移动设备的整机功率,通常在5w以内。

  • 散热方式受限。PC设备通常可以安装散热风扇、甚至水冷系统,而移动设备不具备这些主动散热方式,只能靠热传导散热。如果散热不当,CPU和GPU都会主动降频,以非常有限的性能运行,以免设备元器件因过热而损毁。

  • 有限的硬件性能。移动设备的各类元件(CPU、带宽、内存、GPU等)的性能都只是PC设备的数十分之一。

    2018年,主流PC设备(NV GV100-400-A1 Titan V)和主流移动设备(Samsung Exynos 9 8895)的性能对比图。移动设备的很多硬件性能只是PC设备的几十分之一,但分辨率却接近PC的一半,更加突显了移动设备的挑战和窘境。

    到了2020年,主流的移动设备性能如下所示:

  • 特殊的硬件架构。如CPU和GPU共享内存存储设备,被称为耦合式架构,还有GPU的TB(D)R架构,目的都是为了在低功耗内完成尽可能多的操作。

    PC设备的解耦硬件架构和移动设备的耦合式硬件架构对比图。

除此之外,不同于PC端的CPU和GPU纯粹地追求计算性能,衡量移动端的性能有三个指标:性能(Performance)、能量(Power)、面积(Area),俗称PPA。(下图)

衡量移动设备的三个基本参数:Performance、Area、Power,其中Compute Density(计算密度)涉及性能和面积,能耗比涉及性能和能力消耗,越大越好。

与移动设备一起崛起的还有XR设备,它是移动设备的一个重要发展分支。目前存在着各种不同大小、功能、应用场景的XR设备:

各种形式的XR设备。

随着近来元宇宙(Metaverse)的爆火,以及FaceBook改名为Meta,加之Apple、MicroSoft、NVidia、Google等科技巨头都在加紧布局面向未来的沉浸式体验,XR设备作为能够最接近元宇宙畅想的载体和入口,自然成为一条未来非常有潜力能够出现巨无霸的全新赛道。

12.2 UE移动端渲染特性

本章阐述一下UE4.27在移动端的渲染特性。

12.2.1 Feature Level

UE在移动端支持以下图形API:

Feature Level说明
OpenGL ES 3.1安卓系统的默认特性等级,可以在工程设置(Project Settings > Platforms > Android Material Quality - ES31)配置具体的材质参数。
Android Vulkan可用于某些特定Android设备的高端渲染器,支持Vulkan 1.2 API,轻量级设计理念的Vulkan,多数情况下会比OpenGL更高效。
Metal 2.0专用于iOS设备的特性等级。可以在Project Settings > Platforms > iOS Material Quality配置材质参数。

在目前的主流安卓设备,使用Vulkan能获得更好的性能,原因在于Vulkan轻量级的设计理念,使得UE等应用程序能够更加精准地执行优化。下面是Vulkan和OpenGL的对照表:

VulkanOpenGL
基于对象的状态,没有全局状态。单一的全局状态机。
所有的状态概念都放置到命令缓冲区中。状态被绑定到单个上下文。
可以多线程编码。渲染操作只能被顺序执行。
可以精确、显式地操控GPU的内存和同步。GPU的内存和同步细节通常被驱动程序隐藏起来。
驱动程序没有运行时错误检测,但存在针对开发人员的验证层。广泛的运行时错误检测。

如果在Windows平台,UE编辑器也可以启动OpenGL、Vulkan、Metal的模拟器,以便在编辑器时预览效果,但可能跟实际的运行设备的画面有差异,不可完全依赖此功能。

开启Vulkan前需要在工程中配置一些参数,具体参看官方文档Android Vulkan Mobile Renderer

另外,UE早前几个版本就移除了windows下的OpenGL支持,虽然目前UE编辑器还存在OpenGL的模拟选项,但实际上底层是用D3D渲染的。

12.2.2 Deferred Shading

UE的Deferred Shading(延迟着色)是在4.26才加入的功能,使得开发者能够在移动端实现较复杂的光影效果,诸如高质量反射、多动态光照、贴花、高级光照特性。

上:前向渲染;下:延迟渲染。

如果要在移动端开启延迟渲染,需要在工程配置目录下的DefaultEngine.ini添加r.Mobile.ShadingPath=1字段,然后重启编辑器。

12.2.3 Ground Truth Ambient Occlusion

Ground Truth Ambient Occlusion (GTAO)是接近现实世界的环境遮挡技术,是阴影的一种补偿,能够遮蔽一部分非直接光照,从而获得良好的软阴影效果。

开启了GTAO的效果,注意机器人靠近墙面时,会在墙面留下具有渐变的软阴影效果。

为了开启GTAO,需要勾选以下所示的选项:

此外,GTAO依赖Mobile HDR的选项,为了在对应目标设备开启,还需要在[Platform]Scalability.ini的配置中添加r.Mobile.AmbientOcclusionQuality字段,并且值需要大于0,否则GTAO将被禁用。

值得注意的是,GTAO在Mali设备上存在性能问题,因为它们的最大Compute Shader线程数量少于1024个。

12.2.4 Dynamic Lighting and Shadow

UE在移动端实现的光源特性有:

  • 线性空间的HDR光照。
  • 带方向的光照图(考虑了法线)。
  • 太阳(平行光)支持距离场阴影 + 解析的镜面高光。
  • IBL光照:每个物体采样了最近的一个反射捕捉器,而没有视差校正。
  • 动态物体能正确地接受光照,也可以投射阴影。

UE移动端支持的动态光源的类型、数量、阴影等信息如下:

光源类型最大数量阴影描述
平行光1CSMCSM默认是2级,最多支持4级。
点光源4不支持点光源阴影需要立方体阴影图,而单Pass渲染立方体阴影(OnePassPointLightShadow)的技术需要GS(SM5才有)才支持。
聚光灯4支持默认禁用,需要在工程中开启。
区域光0不支持目前不支持动态区域光照效果。

动态的聚光灯需要在工程配置中显式开启:

在移动BasePass的像素着色器中,聚光灯阴影图与CSM共享相同的纹理采样器,并且聚光灯阴影和CSM使用相同的阴影图图集。CSM能够保证有足够的空间,而聚光灯将按阴影分辨率排序。

默认情况下,可见阴影的最大数量被限制为8个,但可以通过改变r.Mobile.MaxVisibleMovableSpotLightsShadow的值来改变上限值。聚光灯阴影的分辨率是基于屏幕大小和r.Shadow.TexelsPerPixelSpotlight

在前向渲染路径中,局部光源(点光源和聚光灯)的总数不能超过4个。

移动端还支持一种特殊的阴影模式,那就是调制阴影(Modulated Shadows),只能用于固定(Stationary)的平行光。开启了调制阴影的效果图如下:

调制阴影还支持改变阴影颜色和混合比例:

左:动态阴影;右:调制阴影。

移动端的阴影同样支持自阴影、阴影质量等级(r.shadowquality)、深度偏移等参数的设置。

此外,移动端默认使用了GGX的高光反射,如果想切换到传统的高光着色模型,可以在以下配置里修改:

12.2.5 Pixel Projected Reflection

UE针对移动端做了一个优化版的SSR,被称为Pixel Projected Reflection(PPR),也是复用屏幕空间像素的核心思想。

PPR效果图。

为了开启PPR效果,需要满足以下条件:

  • 开启MobileHDR选项。

  • r.Mobile.PixelProjectedReflectionQuality的值大于0。

  • 设置Project Settings > Mobile and set the Planar Reflection Mode成正确的模式:

    Planar Reflection Mode有3个选项:

    • Usual:平面反射Actor在所有平台上的作用都是相同的。
    • MobilePPR:平面反射Actor在PC/主机平台上正常工作,但在移动平台上使用PPR渲染。
    • MobilePPRExclusive:平面反射Actor将只用于移动平台上的PPR,为PC和Console项目留下了使用传统SSR的空间。

默认只有高端移动设备才会在[Project]Scalability.ini开启r.Mobile.PixelProjectedReflectionQuality

12.2.6 Mesh Auto-Instancing

PC端的网格绘制管线已经支持了网格的自动化实例和合并绘制,这个特性可以极大提升渲染性能。4.27已经在移动端支持了这一特性。

若想开启,则需要打开工程配置目录下的DefaultEngine.ini,添加以下字段:

  1. r.Mobile.SupportGPUScene=1
  2. r.Mobile.UseGPUSceneTexture=1

重启编辑器,等待Shader编译完即可预览效果。

由于需要GPUSceneTexture支持,而Mali设备的Uniform Buffer最大只有64kb,以致无法支持足够大的空间,所以,Mali设备会使用纹理而非缓冲区来存储GPUScene数据。

但也存在一些限制:

  • 移动设备上的自动实例化主要有利于CPU密集型项目,而不是GPU密集型项目。虽然启用自动实例化不太可能会对GPU密集型的项目造成损害,但不太可能看到使用它带来的显著性能改进。

  • 如果一款游戏或应用需要大量内存,那么关闭r.Mobile.UseGPUSceneTexture并使用缓冲区可能会更有好处,因为它无法在Mali设备上正常运行。

    也可以针对Mali设备关闭r.Mobile.UseGPUSceneTexture,而其它GPU厂商的设备正常使用。

自动实例化的有效性很大程度上取决于项目的确切规范和定位,建议创建一个启用了自动实例化的构建,并对其进行概要分析,以确定是否会看到实质性的性能提升。

12.2.7 Post Processing

由于移动设备存在更慢的依赖纹理读取(dependent texture read)、有限的硬件特性、特殊的硬件架构、额外的渲染目标解析、有限的带宽等限制性因素,故而后处理在移动设备上执行起来会比较耗性能,有些极端情况会卡住渲染管线。

尽管如此,在某些画质要求高的游戏或应用,依然非常依赖后处理的强劲表现力,为高品质迈上几个台阶。UE不会限制开发者使用后处理。

为了开启后处理,必须先开启MobileHDR选项:

开启后处理之后,就可以在后处理体积(Post Process Volume)设置各种后处理效果。

在移动端可以支持的后处理有Mobile Tonemapper、Color Grading、Lens、Bloom、Dirt Mask、Auto Exposure、Lens Flares、Depth of Field等等。

为了获得更好的性能,官方给出的建议是在移动端只开启Bloom和TAA。

12.2.8 其它特性和限制

  • Reflection Capture Compression

移动端支持反射捕捉器组件(Reflection Capture Component)的压缩,可以减少Reflection Capture运行时的内存和带宽,提升渲染效率。需要在工程配置中开启:

开启之后,默认使用ETC2进行压缩。另外,也可以针对每个Reflection Capture Component进行调整:

  • 材质特性

移动平台上的材质(特性级别Open ES 3.1)使用与其他平台相同的基于节点的创建过程,并且绝大多数节点在移动端都支持。

移动平台支持的材质属性有:BaseColorRoughnessMetallicSpecularNormalEmissiveRefraction,但不支持Scene Color表达式、Tessellation输入、次表面散射着色模型。

移动平台支持的材质存在一些限制:

  • 由于硬件限制,只能使用16个纹理采样器。
  • 只有DefaultLit和Unlit着色模型可用。
  • 自定义UV应该用来避免依赖纹理读取(没有纹理uv的数学计算)。
  • 半透明和Masked材质是及其耗性能, 建议尽量使用不透明材质。
  • 深度淡出(Depth fade)可以在iOS平台的半透明材质中使用,但在硬件不支持从深度缓冲区获取数据的平台上,是不受支持的,将导致不可接受的性能成本。

材质属性面板存在一些针对移动端的特殊选项:

这些属性的说明如下:

  • Mobile Separate Translucency:是否在移动端开启单独的半透明渲染纹理。

  • Use Full Precision:是否使用全精度,如果否,可以减少带宽占用和能耗,提升性能,但可能会出现远处物体的瑕疵:

    左:全精度材质;右:半精度材质,远处的太阳出现了瑕疵。

  • Use Lightmap Directionality:是否开启光照图的方向性,若勾选,会考虑光照图的方向和像素法线,但会提升性能消耗。

  • Use Alpha to Coverage:是否为Masked材质开启MSAA抗锯齿,若勾选,会开启MSAA。

  • Fully Rough:是否完全粗糙,如果勾选,将极大提升此材质的渲染效率。

此外,移动端支持的网格类型有:

  • Skeletal Mesh
  • Static Mesh
  • Landscape
  • CPU particle sprites, particle meshes

除上述类型以外的其它都不被支持。其它限制还有:

  • 单个网格最多只能到65k,因为顶点索引只有16位。
  • 单个Skeletal Mesh的骨骼数量必须在75个以内,因为受硬件性能的限制。

12.3 FMobileSceneRenderer

FMobileSceneRenderer继承自FSceneRenderer,它负责移动端的场景渲染流程,而PC端是同样继承自FSceneRenderer的FDeferredShadingSceneRenderer。它们的继承关系图如下:

FSceneRenderer

FMobileSceneRenderer

FDeferredShadingSceneRenderer

前述多篇文章已经提及了FDeferredShadingSceneRenderer,它的渲染流程尤为复杂,包含了复杂的光影和渲染步骤。相比之下,FMobileSceneRenderer的逻辑和步骤会简单许多,下面是RenderDoc的截帧:

以上主要包含了InitViews、ShadowDepths、PrePass、BasePass、OcclusionTest、ShadowProjectionOnOpaque、Translucency、PostProcessing等步骤。其中这些步骤在PC端都是存在的,但实现过程可能会有所不同。见后续章节剖析。

12.3.1 渲染器主流程

移动端的场景渲染器的主流程也发生在FMobileSceneRenderer::Render中,代码和解析如下:

  1. // Engine\Source\Runtime\Renderer\Private\MobileShadingRenderer.cpp
  2. void FMobileSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)
  3. {
  4. // 更新图元场景信息。
  5. Scene->UpdateAllPrimitiveSceneInfos(RHICmdList);
  6. // 准备视图的渲染区域.
  7. PrepareViewRectsForRendering(RHICmdList);
  8. // 准备天空大气的数据
  9. if (ShouldRenderSkyAtmosphere(Scene, ViewFamily.EngineShowFlags))
  10. {
  11. for (int32 LightIndex = 0; LightIndex < NUM_ATMOSPHERE_LIGHTS; ++LightIndex)
  12. {
  13. if (Scene->AtmosphereLights[LightIndex])
  14. {
  15. PrepareSunLightProxy(*Scene->GetSkyAtmosphereSceneInfo(), LightIndex, *Scene->AtmosphereLights[LightIndex]);
  16. }
  17. }
  18. }
  19. else
  20. {
  21. Scene->ResetAtmosphereLightsProperties();
  22. }
  23. if(!ViewFamily.EngineShowFlags.Rendering)
  24. {
  25. return;
  26. }
  27. // 等待遮挡剔除测试.
  28. WaitOcclusionTests(RHICmdList);
  29. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  30. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  31. // 初始化视图, 查找可见图元, 准备渲染所需的RT和缓冲区等数据.
  32. InitViews(RHICmdList);
  33. if (GRHINeedsExtraDeletionLatency || !GRHICommandList.Bypass())
  34. {
  35. QUICK_SCOPE_CYCLE_COUNTER(STAT_FMobileSceneRenderer_PostInitViewsFlushDel);
  36. // 可能会暂停遮挡查询,所以最好在等待时让RHI线程和GPU工作. 此外,当执行RHI线程时,这是唯一将处理挂起删除的位置.
  37. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  38. FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
  39. }
  40. GEngine->GetPreRenderDelegate().Broadcast();
  41. // 在渲染开始前提交全局动态缓冲.
  42. DynamicIndexBuffer.Commit();
  43. DynamicVertexBuffer.Commit();
  44. DynamicReadBuffer.Commit();
  45. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  46. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_SceneSim));
  47. if (ViewFamily.bLateLatchingEnabled)
  48. {
  49. BeginLateLatching(RHICmdList);
  50. }
  51. FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
  52. // 处理虚拟纹理
  53. if (bUseVirtualTexturing)
  54. {
  55. SCOPED_GPU_STAT(RHICmdList, VirtualTextureUpdate);
  56. FVirtualTextureSystem::Get().Update(RHICmdList, FeatureLevel, Scene);
  57. // Clear virtual texture feedback to default value
  58. FUnorderedAccessViewRHIRef FeedbackUAV = SceneContext.GetVirtualTextureFeedbackUAV();
  59. RHICmdList.Transition(FRHITransitionInfo(FeedbackUAV, ERHIAccess::SRVMask, ERHIAccess::UAVMask));
  60. RHICmdList.ClearUAVUint(FeedbackUAV, FUintVector4(~0u, ~0u, ~0u, ~0u));
  61. RHICmdList.Transition(FRHITransitionInfo(FeedbackUAV, ERHIAccess::UAVMask, ERHIAccess::UAVMask));
  62. RHICmdList.BeginUAVOverlap(FeedbackUAV);
  63. }
  64. // 已排序的光源信息.
  65. FSortedLightSetSceneInfo SortedLightSet;
  66. // 延迟渲染.
  67. if (bDeferredShading)
  68. {
  69. // 收集和排序光源.
  70. GatherAndSortLights(SortedLightSet);
  71. int32 NumReflectionCaptures = Views[0].NumBoxReflectionCaptures + Views[0].NumSphereReflectionCaptures;
  72. bool bCullLightsToGrid = (NumReflectionCaptures > 0 || GMobileUseClusteredDeferredShading != 0);
  73. FRDGBuilder GraphBuilder(RHICmdList);
  74. // 计算光源格子.
  75. ComputeLightGrid(GraphBuilder, bCullLightsToGrid, SortedLightSet);
  76. GraphBuilder.Execute();
  77. }
  78. // 生成天空/大气LUT.
  79. const bool bShouldRenderSkyAtmosphere = ShouldRenderSkyAtmosphere(Scene, ViewFamily.EngineShowFlags);
  80. if (bShouldRenderSkyAtmosphere)
  81. {
  82. FRDGBuilder GraphBuilder(RHICmdList);
  83. RenderSkyAtmosphereLookUpTables(GraphBuilder);
  84. GraphBuilder.Execute();
  85. }
  86. // 通知特效系统场景准备渲染.
  87. if (FXSystem && ViewFamily.EngineShowFlags.Particles)
  88. {
  89. FXSystem->PreRender(RHICmdList, NULL, !Views[0].bIsPlanarReflection);
  90. if (FGPUSortManager* GPUSortManager = FXSystem->GetGPUSortManager())
  91. {
  92. GPUSortManager->OnPreRender(RHICmdList);
  93. }
  94. }
  95. // 轮询遮挡剔除请求.
  96. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  97. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  98. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Shadows));
  99. // 渲染阴影.
  100. RenderShadowDepthMaps(RHICmdList);
  101. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  102. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  103. // 收集视图列表.
  104. TArray<const FViewInfo*> ViewList;
  105. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
  106. {
  107. ViewList.Add(&Views[ViewIndex]);
  108. }
  109. // 渲染自定义深度.
  110. if (bShouldRenderCustomDepth)
  111. {
  112. FRDGBuilder GraphBuilder(RHICmdList);
  113. FSceneTextureShaderParameters SceneTextures = CreateSceneTextureShaderParameters(GraphBuilder, Views[0].GetFeatureLevel(), ESceneTextureSetupMode::None);
  114. RenderCustomDepthPass(GraphBuilder, SceneTextures);
  115. GraphBuilder.Execute();
  116. }
  117. // 渲染深度PrePass.
  118. if (bIsFullPrepassEnabled)
  119. {
  120. // SDF和AO需要完整的PrePass深度.
  121. FRHIRenderPassInfo DepthPrePassRenderPassInfo(
  122. SceneContext.GetSceneDepthSurface(),
  123. EDepthStencilTargetActions::ClearDepthStencil_StoreDepthStencil);
  124. DepthPrePassRenderPassInfo.NumOcclusionQueries = ComputeNumOcclusionQueriesToBatch();
  125. DepthPrePassRenderPassInfo.bOcclusionQueries = DepthPrePassRenderPassInfo.NumOcclusionQueries != 0;
  126. RHICmdList.BeginRenderPass(DepthPrePassRenderPassInfo, TEXT("DepthPrepass"));
  127. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_MobilePrePass));
  128. // 渲染完整的深度PrePass.
  129. RenderPrePass(RHICmdList);
  130. // 提交遮挡剔除.
  131. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Occlusion));
  132. RenderOcclusion(RHICmdList);
  133. RHICmdList.EndRenderPass();
  134. // SDF阴影
  135. if (bRequiresDistanceFieldShadowingPass)
  136. {
  137. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderSDFShadowing);
  138. RenderSDFShadowing(RHICmdList);
  139. }
  140. // HZB.
  141. if (bShouldRenderHZB)
  142. {
  143. RenderHZB(RHICmdList, SceneContext.SceneDepthZ);
  144. }
  145. // AO.
  146. if (bRequiresAmbientOcclusionPass)
  147. {
  148. RenderAmbientOcclusion(RHICmdList, SceneContext.SceneDepthZ);
  149. }
  150. }
  151. FRHITexture* SceneColor = nullptr;
  152. // 延迟渲染.
  153. if (bDeferredShading)
  154. {
  155. SceneColor = RenderDeferred(RHICmdList, ViewList, SortedLightSet);
  156. }
  157. // 前向渲染.
  158. else
  159. {
  160. SceneColor = RenderForward(RHICmdList, ViewList);
  161. }
  162. // 渲染速度缓冲.
  163. if (bShouldRenderVelocities)
  164. {
  165. FRDGBuilder GraphBuilder(RHICmdList);
  166. FRDGTextureMSAA SceneDepthTexture = RegisterExternalTextureMSAA(GraphBuilder, SceneContext.SceneDepthZ);
  167. FRDGTextureRef VelocityTexture = TryRegisterExternalTexture(GraphBuilder, SceneContext.SceneVelocity);
  168. if (VelocityTexture != nullptr)
  169. {
  170. AddClearRenderTargetPass(GraphBuilder, VelocityTexture);
  171. }
  172. // 渲染可移动物体的速度缓冲.
  173. AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLMM_Velocity));
  174. RenderVelocities(GraphBuilder, SceneDepthTexture.Resolve, VelocityTexture, FSceneTextureShaderParameters(), EVelocityPass::Opaque, false);
  175. AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLMM_AfterVelocity));
  176. // 渲染透明物体的速度缓冲.
  177. AddSetCurrentStatPass(GraphBuilder, GET_STATID(STAT_CLMM_TranslucentVelocity));
  178. RenderVelocities(GraphBuilder, SceneDepthTexture.Resolve, VelocityTexture, GetSceneTextureShaderParameters(CreateMobileSceneTextureUniformBuffer(GraphBuilder, EMobileSceneTextureSetupMode::SceneColor)), EVelocityPass::Translucent, false);
  179. GraphBuilder.Execute();
  180. }
  181. // 处理场景渲染后的逻辑.
  182. {
  183. FRendererModule& RendererModule = static_cast<FRendererModule&>(GetRendererModule());
  184. FRDGBuilder GraphBuilder(RHICmdList);
  185. RendererModule.RenderPostOpaqueExtensions(GraphBuilder, Views, SceneContext);
  186. if (FXSystem && Views.IsValidIndex(0))
  187. {
  188. AddUntrackedAccessPass(GraphBuilder, [this](FRHICommandListImmediate& RHICmdList)
  189. {
  190. check(RHICmdList.IsOutsideRenderPass());
  191. FXSystem->PostRenderOpaque(
  192. RHICmdList,
  193. Views[0].ViewUniformBuffer,
  194. nullptr,
  195. nullptr,
  196. Views[0].AllowGPUParticleUpdate()
  197. );
  198. if (FGPUSortManager* GPUSortManager = FXSystem->GetGPUSortManager())
  199. {
  200. GPUSortManager->OnPostRenderOpaque(RHICmdList);
  201. }
  202. });
  203. }
  204. GraphBuilder.Execute();
  205. }
  206. // 刷新/提交命令缓冲.
  207. if (bSubmitOffscreenRendering)
  208. {
  209. RHICmdList.SubmitCommandsHint();
  210. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  211. }
  212. // 转换场景颜色成SRV, 以供后续步骤读取.
  213. if (!bGammaSpace || bRenderToSceneColor)
  214. {
  215. RHICmdList.Transition(FRHITransitionInfo(SceneColor, ERHIAccess::Unknown, ERHIAccess::SRVMask));
  216. }
  217. if (bDeferredShading)
  218. {
  219. // 释放场景渲染目标上的原始引用.
  220. SceneContext.AdjustGBufferRefCount(RHICmdList, -1);
  221. }
  222. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Post));
  223. // 处理虚拟纹理.
  224. if (bUseVirtualTexturing)
  225. {
  226. SCOPED_GPU_STAT(RHICmdList, VirtualTextureUpdate);
  227. // No pass after this should make VT page requests
  228. RHICmdList.EndUAVOverlap(SceneContext.VirtualTextureFeedbackUAV);
  229. RHICmdList.Transition(FRHITransitionInfo(SceneContext.VirtualTextureFeedbackUAV, ERHIAccess::UAVMask, ERHIAccess::SRVMask));
  230. TArray<FIntRect, TInlineAllocator<4>> ViewRects;
  231. ViewRects.AddUninitialized(Views.Num());
  232. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
  233. {
  234. ViewRects[ViewIndex] = Views[ViewIndex].ViewRect;
  235. }
  236. FVirtualTextureFeedbackBufferDesc Desc;
  237. Desc.Init2D(SceneContext.GetBufferSizeXY(), ViewRects, SceneContext.GetVirtualTextureFeedbackScale());
  238. SubmitVirtualTextureFeedbackBuffer(RHICmdList, SceneContext.VirtualTextureFeedback, Desc);
  239. }
  240. FMemMark Mark(FMemStack::Get());
  241. FRDGBuilder GraphBuilder(RHICmdList);
  242. FRDGTextureRef ViewFamilyTexture = TryCreateViewFamilyTexture(GraphBuilder, ViewFamily);
  243. // 解析场景
  244. if (ViewFamily.bResolveScene)
  245. {
  246. if (!bGammaSpace || bRenderToSceneColor)
  247. {
  248. // 完成每个视图的渲染或完整的立体声缓冲区(如果启用)
  249. {
  250. RDG_EVENT_SCOPE(GraphBuilder, "PostProcessing");
  251. SCOPE_CYCLE_COUNTER(STAT_FinishRenderViewTargetTime);
  252. TArray<TRDGUniformBufferRef<FMobileSceneTextureUniformParameters>, TInlineAllocator<1, SceneRenderingAllocator>> MobileSceneTexturesPerView;
  253. MobileSceneTexturesPerView.SetNumZeroed(Views.Num());
  254. const auto SetupMobileSceneTexturesPerView = [&]()
  255. {
  256. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
  257. {
  258. EMobileSceneTextureSetupMode SetupMode = EMobileSceneTextureSetupMode::SceneColor;
  259. if (Views[ViewIndex].bCustomDepthStencilValid)
  260. {
  261. SetupMode |= EMobileSceneTextureSetupMode::CustomDepth;
  262. }
  263. if (bShouldRenderVelocities)
  264. {
  265. SetupMode |= EMobileSceneTextureSetupMode::SceneVelocity;
  266. }
  267. MobileSceneTexturesPerView[ViewIndex] = CreateMobileSceneTextureUniformBuffer(GraphBuilder, SetupMode);
  268. }
  269. };
  270. SetupMobileSceneTexturesPerView();
  271. FMobilePostProcessingInputs PostProcessingInputs;
  272. PostProcessingInputs.ViewFamilyTexture = ViewFamilyTexture;
  273. // 渲染后处理效果.
  274. for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
  275. {
  276. RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);
  277. PostProcessingInputs.SceneTextures = MobileSceneTexturesPerView[ViewIndex];
  278. AddMobilePostProcessingPasses(GraphBuilder, Views[ViewIndex], PostProcessingInputs, NumMSAASamples > 1);
  279. }
  280. }
  281. }
  282. }
  283. GEngine->GetPostRenderDelegate().Broadcast();
  284. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_SceneEnd));
  285. if (bShouldRenderVelocities)
  286. {
  287. SceneContext.SceneVelocity.SafeRelease();
  288. }
  289. if (ViewFamily.bLateLatchingEnabled)
  290. {
  291. EndLateLatching(RHICmdList, Views[0]);
  292. }
  293. RenderFinish(GraphBuilder, ViewFamilyTexture);
  294. GraphBuilder.Execute();
  295. // 轮询遮挡剔除请求.
  296. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  297. FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  298. }

看过剖析虚幻渲染体系(04)- 延迟渲染管线篇章的同学应该都知道,移动端的场景渲染过程精简了很多很多步骤,相当于是PC端场景渲染器的一个子集。当然,为了适应移动端特有的GPU硬件架构,移动端的场景渲染也有区别于PC端的地方。后面会详细剖析。移动端场景的主要步骤和流程如下所示:

No

Yes

No

Yes

No

Yes

UpdateAllPrimitiveSceneInfos

PrepareViewRectsForRendering

InitViews

RenderSkyAtmosphereLookUpTables*

GatherAndSortLights

ComputeLightGrid

RenderShadowDepthMaps

RenderCustomDepthPass*

bDeferredShading2

RenderPrePass

RenderOcclusion

RenderSDFShadowing*

RenderHZB*

RenderAmbientOcclusion*

RenderForward

RenderDeferred

RenderVelocities*

AddMobilePostProcessingPasses*

RenderFinish

关于上面的流程图,有以下几点需要加以说明:

  • 流程图节点bDeferredShadingbDeferredShading2是同一个变量,这里区分开主要是为了防止mermaid语法绘图错误。
  • 带*的节点是有条件的,非必然执行的步骤。

UE4.26便加入了移动端的延迟渲染管线,所以上述代码中有前向渲染分支RenderForward和延迟渲染分支RenderDeferred,它们返回的都是渲染结果SceneColor。

移动端也支持了图元GPU场景、SDF阴影、AO、天空大气、虚拟纹理、遮挡剔除等渲染特性。

自UE4.26开始,渲染体系广泛地使用了RDG系统,移动端的场景渲染器也不例外。上述代码中总共声明了数个FRDGBuilder实例,用于计算光源格子,以及渲染天空大气LUT、自定义深度、速度缓冲、渲染后置事件、后处理等,它们都是相对独立的功能模块或渲染阶段。

12.3.2 RenderForward

RenderForward在移动端场景渲染器中负责前向渲染的分支,它的代码和解析如下:

  1. FRHITexture* FMobileSceneRenderer::RenderForward(FRHICommandListImmediate& RHICmdList, const TArrayView<const FViewInfo*> ViewList)
  2. {
  3. const FViewInfo& View = *ViewList[0];
  4. FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
  5. FRHITexture* SceneColor = nullptr;
  6. FRHITexture* SceneColorResolve = nullptr;
  7. FRHITexture* SceneDepth = nullptr;
  8. ERenderTargetActions ColorTargetAction = ERenderTargetActions::Clear_Store;
  9. EDepthStencilTargetActions DepthTargetAction = EDepthStencilTargetActions::ClearDepthStencil_DontStoreDepthStencil;
  10. // 是否启用移动端MSAA.
  11. bool bMobileMSAA = NumMSAASamples > 1 && SceneContext.GetSceneColorSurface()->GetNumSamples() > 1;
  12. // 是否启用移动端多试图模式.
  13. static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
  14. const bool bIsMultiViewApplication = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0);
  15. // gamma空间的渲染分支.
  16. if (bGammaSpace && !bRenderToSceneColor)
  17. {
  18. // 如果开启MSAA, 则从SceneContext获取渲染纹理(包含场景颜色和解析纹理)
  19. if (bMobileMSAA)
  20. {
  21. SceneColor = SceneContext.GetSceneColorSurface();
  22. SceneColorResolve = ViewFamily.RenderTarget->GetRenderTargetTexture();
  23. ColorTargetAction = ERenderTargetActions::Clear_Resolve;
  24. RHICmdList.Transition(FRHITransitionInfo(SceneColorResolve, ERHIAccess::Unknown, ERHIAccess::RTV | ERHIAccess::ResolveDst));
  25. }
  26. // 非MSAA,从视图家族获取渲染纹理.
  27. else
  28. {
  29. SceneColor = ViewFamily.RenderTarget->GetRenderTargetTexture();
  30. RHICmdList.Transition(FRHITransitionInfo(SceneColor, ERHIAccess::Unknown, ERHIAccess::RTV));
  31. }
  32. SceneDepth = SceneContext.GetSceneDepthSurface();
  33. }
  34. // 线性空间或渲染到场景纹理.
  35. else
  36. {
  37. SceneColor = SceneContext.GetSceneColorSurface();
  38. if (bMobileMSAA)
  39. {
  40. SceneColorResolve = SceneContext.GetSceneColorTexture();
  41. ColorTargetAction = ERenderTargetActions::Clear_Resolve;
  42. RHICmdList.Transition(FRHITransitionInfo(SceneColorResolve, ERHIAccess::Unknown, ERHIAccess::RTV | ERHIAccess::ResolveDst));
  43. }
  44. else
  45. {
  46. SceneColorResolve = nullptr;
  47. ColorTargetAction = ERenderTargetActions::Clear_Store;
  48. }
  49. SceneDepth = SceneContext.GetSceneDepthSurface();
  50. if (bRequiresMultiPass)
  51. {
  52. // store targets after opaque so translucency render pass can be restarted
  53. ColorTargetAction = ERenderTargetActions::Clear_Store;
  54. DepthTargetAction = EDepthStencilTargetActions::ClearDepthStencil_StoreDepthStencil;
  55. }
  56. if (bKeepDepthContent)
  57. {
  58. // store depth if post-processing/capture needs it
  59. DepthTargetAction = EDepthStencilTargetActions::ClearDepthStencil_StoreDepthStencil;
  60. }
  61. }
  62. // prepass的深度纹理状态.
  63. if (bIsFullPrepassEnabled)
  64. {
  65. ERenderTargetActions DepthTarget = MakeRenderTargetActions(ERenderTargetLoadAction::ELoad, GetStoreAction(GetDepthActions(DepthTargetAction)));
  66. ERenderTargetActions StencilTarget = MakeRenderTargetActions(ERenderTargetLoadAction::ELoad, GetStoreAction(GetStencilActions(DepthTargetAction)));
  67. DepthTargetAction = MakeDepthStencilTargetActions(DepthTarget, StencilTarget);
  68. }
  69. FRHITexture* ShadingRateTexture = nullptr;
  70. if (!View.bIsSceneCapture && !View.bIsReflectionCapture)
  71. {
  72. TRefCountPtr<IPooledRenderTarget> ShadingRateTarget = GVRSImageManager.GetMobileVariableRateShadingImage(ViewFamily);
  73. if (ShadingRateTarget.IsValid())
  74. {
  75. ShadingRateTexture = ShadingRateTarget->GetRenderTargetItem().ShaderResourceTexture;
  76. }
  77. }
  78. // 场景颜色渲染Pass信息.
  79. FRHIRenderPassInfo SceneColorRenderPassInfo(
  80. SceneColor,
  81. ColorTargetAction,
  82. SceneColorResolve,
  83. SceneDepth,
  84. DepthTargetAction,
  85. nullptr, // we never resolve scene depth on mobile
  86. ShadingRateTexture,
  87. VRSRB_Sum,
  88. FExclusiveDepthStencil::DepthWrite_StencilWrite
  89. );
  90. SceneColorRenderPassInfo.SubpassHint = ESubpassHint::DepthReadSubpass;
  91. if (!bIsFullPrepassEnabled)
  92. {
  93. SceneColorRenderPassInfo.NumOcclusionQueries = ComputeNumOcclusionQueriesToBatch();
  94. SceneColorRenderPassInfo.bOcclusionQueries = SceneColorRenderPassInfo.NumOcclusionQueries != 0;
  95. }
  96. // 如果场景颜色不是多视图,但应用程序是,需要渲染为单视图的多视图给着色器.
  97. SceneColorRenderPassInfo.MultiViewCount = View.bIsMobileMultiViewEnabled ? 2 : (bIsMultiViewApplication ? 1 : 0);
  98. // 开始渲染场景颜色.
  99. RHICmdList.BeginRenderPass(SceneColorRenderPassInfo, TEXT("SceneColorRendering"));
  100. if (GIsEditor && !View.bIsSceneCapture)
  101. {
  102. DrawClearQuad(RHICmdList, Views[0].BackgroundColor);
  103. }
  104. if (!bIsFullPrepassEnabled)
  105. {
  106. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_MobilePrePass));
  107. // 渲染深度pre-pass
  108. RenderPrePass(RHICmdList);
  109. }
  110. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Opaque));
  111. // 渲染BasePass: 不透明和masked物体.
  112. RenderMobileBasePass(RHICmdList, ViewList);
  113. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  114. //渲染调试模式.
  115. #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
  116. if (ViewFamily.UseDebugViewPS())
  117. {
  118. // Here we use the base pass depth result to get z culling for opaque and masque.
  119. // The color needs to be cleared at this point since shader complexity renders in additive.
  120. DrawClearQuad(RHICmdList, FLinearColor::Black);
  121. RenderMobileDebugView(RHICmdList, ViewList);
  122. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  123. }
  124. #endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
  125. const bool bAdrenoOcclusionMode = CVarMobileAdrenoOcclusionMode.GetValueOnRenderThread() != 0;
  126. if (!bIsFullPrepassEnabled)
  127. {
  128. // 遮挡剔除
  129. if (!bAdrenoOcclusionMode)
  130. {
  131. // 提交遮挡剔除
  132. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Occlusion));
  133. RenderOcclusion(RHICmdList);
  134. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  135. }
  136. }
  137. // 后置事件, 处理插件渲染.
  138. {
  139. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(ViewExtensionPostRenderBasePass);
  140. QUICK_SCOPE_CYCLE_COUNTER(STAT_FMobileSceneRenderer_ViewExtensionPostRenderBasePass);
  141. for (int32 ViewExt = 0; ViewExt < ViewFamily.ViewExtensions.Num(); ++ViewExt)
  142. {
  143. for (int32 ViewIndex = 0; ViewIndex < ViewFamily.Views.Num(); ++ViewIndex)
  144. {
  145. ViewFamily.ViewExtensions[ViewExt]->PostRenderBasePass_RenderThread(RHICmdList, Views[ViewIndex]);
  146. }
  147. }
  148. }
  149. // 如果需要渲染透明物体或像素投影的反射, 则需要拆分pass.
  150. if (bRequiresMultiPass || bRequiresPixelProjectedPlanarRelfectionPass)
  151. {
  152. RHICmdList.EndRenderPass();
  153. }
  154. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Translucency));
  155. // 如果需要, 则重新开启透明渲染通道.
  156. if (bRequiresMultiPass || bRequiresPixelProjectedPlanarRelfectionPass)
  157. {
  158. check(RHICmdList.IsOutsideRenderPass());
  159. // 如果当前硬件不支持读写相同的深度缓冲区,则复制场景深度.
  160. ConditionalResolveSceneDepth(RHICmdList, View);
  161. if (bRequiresPixelProjectedPlanarRelfectionPass)
  162. {
  163. const FPlanarReflectionSceneProxy* PlanarReflectionSceneProxy = Scene ? Scene->GetForwardPassGlobalPlanarReflection() : nullptr;
  164. RenderPixelProjectedReflection(RHICmdList, SceneContext, PlanarReflectionSceneProxy);
  165. FRHITransitionInfo TranslucentRenderPassTransitions[] = {
  166. FRHITransitionInfo(SceneColor, ERHIAccess::SRVMask, ERHIAccess::RTV),
  167. FRHITransitionInfo(SceneDepth, ERHIAccess::SRVMask, ERHIAccess::DSVWrite)
  168. };
  169. RHICmdList.Transition(MakeArrayView(TranslucentRenderPassTransitions, UE_ARRAY_COUNT(TranslucentRenderPassTransitions)));
  170. }
  171. DepthTargetAction = EDepthStencilTargetActions::LoadDepthStencil_DontStoreDepthStencil;
  172. FExclusiveDepthStencil::Type ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilRead;
  173. if (bModulatedShadowsInUse)
  174. {
  175. ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilWrite;
  176. }
  177. // 用于移动端像素投影反射的不透明网格必须将深度写入深度RT, 因为只渲染一次网格(如果质量水平低于或等于BestPerformance).
  178. if (IsMobilePixelProjectedReflectionEnabled(View.GetShaderPlatform())
  179. && GetMobilePixelProjectedReflectionQuality() == EMobilePixelProjectedReflectionQuality::BestPerformance)
  180. {
  181. ExclusiveDepthStencil = FExclusiveDepthStencil::DepthWrite_StencilWrite;
  182. }
  183. if (bKeepDepthContent && !bMobileMSAA)
  184. {
  185. DepthTargetAction = EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil;
  186. }
  187. #if PLATFORM_HOLOLENS
  188. if (bShouldRenderDepthToTranslucency)
  189. {
  190. ExclusiveDepthStencil = FExclusiveDepthStencil::DepthWrite_StencilWrite;
  191. }
  192. #endif
  193. // 透明物体渲染Pass.
  194. FRHIRenderPassInfo TranslucentRenderPassInfo(
  195. SceneColor,
  196. SceneColorResolve ? ERenderTargetActions::Load_Resolve : ERenderTargetActions::Load_Store,
  197. SceneColorResolve,
  198. SceneDepth,
  199. DepthTargetAction,
  200. nullptr,
  201. ShadingRateTexture,
  202. VRSRB_Sum,
  203. ExclusiveDepthStencil
  204. );
  205. TranslucentRenderPassInfo.NumOcclusionQueries = 0;
  206. TranslucentRenderPassInfo.bOcclusionQueries = false;
  207. TranslucentRenderPassInfo.SubpassHint = ESubpassHint::DepthReadSubpass;
  208. // 开始渲染半透明物体.
  209. RHICmdList.BeginRenderPass(TranslucentRenderPassInfo, TEXT("SceneColorTranslucencyRendering"));
  210. }
  211. // 场景深度是只读的,可以获取.
  212. RHICmdList.NextSubpass();
  213. if (!View.bIsPlanarReflection)
  214. {
  215. // 渲染贴花.
  216. if (ViewFamily.EngineShowFlags.Decals)
  217. {
  218. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderDecals);
  219. RenderDecals(RHICmdList);
  220. }
  221. // 渲染调制阴影投射.
  222. if (ViewFamily.EngineShowFlags.DynamicShadows)
  223. {
  224. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderShadowProjections);
  225. RenderModulatedShadowProjections(RHICmdList);
  226. }
  227. }
  228. // 绘制半透明.
  229. if (ViewFamily.EngineShowFlags.Translucency)
  230. {
  231. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderTranslucency);
  232. SCOPE_CYCLE_COUNTER(STAT_TranslucencyDrawTime);
  233. RenderTranslucency(RHICmdList, ViewList);
  234. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  235. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  236. }
  237. if (!bIsFullPrepassEnabled)
  238. {
  239. // Adreno遮挡剔除模式.
  240. if (bAdrenoOcclusionMode)
  241. {
  242. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Occlusion));
  243. // flush
  244. RHICmdList.SubmitCommandsHint();
  245. bSubmitOffscreenRendering = false; // submit once
  246. // Issue occlusion queries
  247. RenderOcclusion(RHICmdList);
  248. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  249. }
  250. }
  251. // 在MSAA被解析前预计算色调映射(只在iOS有效)
  252. if (!bGammaSpace)
  253. {
  254. PreTonemapMSAA(RHICmdList);
  255. }
  256. // 结束场景颜色渲染.
  257. RHICmdList.EndRenderPass();
  258. // 优化返回场景颜色的解析纹理(开启了MSAA才有).
  259. return SceneColorResolve ? SceneColorResolve : SceneColor;
  260. }

移动端前向渲染主要步骤跟PC端类似,依次渲染PrePass、BasePass、特殊渲染(贴花、AO、遮挡剔除等)、半透明物体。它们的流程图如下:

DrawClearQuad*

RenderPrePass*

RenderMobileBasePass

RenderOcclusion*

RenderDecals*

RenderModulatedShadowProjections*

RenderTranslucency*

PreTonemapMSAA*

其中遮挡剔除和GPU厂商相关,比如高通Adreno系列GPU芯片要求在Flush渲染指令和Switch FBO之间:

Render Opaque -> Render Translucent -> Flush -> Render Queries -> Switch FBO

那么UE也遵循了Adreno系列芯片的特殊要求,对其的遮挡剔除做了特殊的处理。

Adreno系列芯片支持TBDR架构的Bin和普通的Direct两种混合模式的渲染,会在遮挡查询时自动切换到Direct模式,以降低遮挡查询的开销。如果不在Flush渲染指令和Switch FBO之间提交查询,会卡住整个渲染管线,引发渲染性能下降。

MSAA由于天然硬件支持且效果和效率达到很好的平衡,是UE在移动端前向渲染的首选抗锯齿。因此,上述代码中出现了不少处理MSAA的逻辑,包含颜色和深度纹理及其资源状态。如果开启了MSAA,默认情况下是在RHICmdList.EndRenderPass()解析场景颜色(同时将芯片分块上的数据写回到系统显存中),由此获得抗锯齿的纹理。移动端的MSAA默认不开启,但可在以下界面中设置:

前向渲染支持Gamma空间和HDR(线性空间)两种颜色空间模式。如果是线性空间,则渲染后期需要色调映射等步骤。默认是HDR,可在项目配置中更改:

上述代码的bRequiresMultiPass标明了是否需要专用的渲染Pass绘制半透明物体,决定它的值由以下代码完成:

  1. // Engine\Source\Runtime\Renderer\Private\MobileShadingRenderer.cpp
  2. bool FMobileSceneRenderer::RequiresMultiPass(FRHICommandListImmediate& RHICmdList, const FViewInfo& View) const
  3. {
  4. // Vulkan uses subpasses
  5. if (IsVulkanPlatform(ShaderPlatform))
  6. {
  7. return false;
  8. }
  9. // All iOS support frame_buffer_fetch
  10. if (IsMetalMobilePlatform(ShaderPlatform))
  11. {
  12. return false;
  13. }
  14. if (IsMobileDeferredShadingEnabled(ShaderPlatform))
  15. {
  16. // TODO: add GL support
  17. return true;
  18. }
  19. // Some Androids support frame_buffer_fetch
  20. if (IsAndroidOpenGLESPlatform(ShaderPlatform) && (GSupportsShaderFramebufferFetch || GSupportsShaderDepthStencilFetch))
  21. {
  22. return false;
  23. }
  24. // Always render reflection capture in single pass
  25. if (View.bIsPlanarReflection || View.bIsSceneCapture)
  26. {
  27. return false;
  28. }
  29. // Always render LDR in single pass
  30. if (!IsMobileHDR())
  31. {
  32. return false;
  33. }
  34. // MSAA depth can't be sampled or resolved, unless we are on PC (no vulkan)
  35. if (NumMSAASamples > 1 && !IsSimulatedPlatform(ShaderPlatform))
  36. {
  37. return false;
  38. }
  39. return true;
  40. }

与之类似但意义不同的是bIsMultiViewApplication和bIsMobileMultiViewEnabled标记,标明是否开启多视图渲染以及多视图的个数。只用于VR,由控制台变量vr.MobileMultiView及图形API等因素决定。MultiView用于XR,用于优化渲染两次的情形,它存在Basic和Advanced两种模式:

用于优化VR等渲染的MultiView对比图。上:未采用MultiView模式的渲染,两个眼睛各自提交绘制指令;中:基础MultiView模式,复用提交指令,在GPU层复制多一份Command List;下:高级MultiView模式,可以复用DC、Command List、几何信息。

bKeepDepthContent标明是否要保留深度内容,决定它的代码:

  1. bKeepDepthContent =
  2. bRequiresMultiPass ||
  3. bForceDepthResolve ||
  4. bRequiresPixelProjectedPlanarRelfectionPass ||
  5. bSeparateTranslucencyActive ||
  6. Views[0].bIsReflectionCapture ||
  7. (bDeferredShading && bPostProcessUsesSceneDepth) ||
  8. bShouldRenderVelocities ||
  9. bIsFullPrepassEnabled;
  10. // 带MSAA的深度从不保留.
  11. bKeepDepthContent = (NumMSAASamples > 1 ? false : bKeepDepthContent);

上述代码还揭示了平面反射在移动端的一种特殊渲染方式:Pixel Projected Reflection(PPR)。它的实现原理类似于SSR,但需要的数据更少,只需要场景颜色、深度缓冲和反射区域。它的核心步骤:

  • 计算场景颜色的所有像素在反射平面的镜像位置。
  • 测试像素的反射是否在反射区域内。
    • 光线投射到镜像像素位置。
    • 测试交点是否在反射区域内。
  • 如果找到相交点,计算像素在屏幕的镜像位置。
  • 在交点处写入镜像像素的颜色。

  • 如果反射区域内的交点被其它物体遮挡,则剔除此位置的反射。

PPR效果一览。

PPR可以在工程配置中设置:

12.3.3 RenderDeferred

UE在4.26在移动端渲染管线增加了延迟渲染分支,并在4.27做了改进和优化。移动端是否开启延迟着色的特性由以下代码决定:

  1. // Engine\Source\Runtime\RenderCore\Private\RenderUtils.cpp
  2. bool IsMobileDeferredShadingEnabled(const FStaticShaderPlatform Platform)
  3. {
  4. // 禁用OpenGL的延迟着色.
  5. if (IsOpenGLPlatform(Platform))
  6. {
  7. // needs MRT framebuffer fetch or PLS
  8. return false;
  9. }
  10. // 控制台变量"r.Mobile.ShadingPath"要为1.
  11. static auto* MobileShadingPathCvar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.ShadingPath"));
  12. return MobileShadingPathCvar->GetValueOnAnyThread() == 1;
  13. }

简单地说就是非OpenGL图形API且控制台变量r.Mobile.ShadingPath设为1。

r.Mobile.ShadingPath不可在编辑器动态设置值,只能在项目工程根目录/Config/DefaultEngine.ini增加以下字段来开启:

[/Script/Engine.RendererSettings]

r.Mobile.ShadingPath=1

添加以上字段后,重启UE编辑器,等待shader编译完成即可预览移动端延迟着色效果。

以下是延迟渲染分支FMobileSceneRenderer::RenderDeferred的代码和解析:

  1. FRHITexture* FMobileSceneRenderer::RenderDeferred(FRHICommandListImmediate& RHICmdList, const TArrayView<const FViewInfo*> ViewList, const FSortedLightSetSceneInfo& SortedLightSet)
  2. {
  3. FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
  4. // 准备GBuffer.
  5. FRHITexture* ColorTargets[4] = {
  6. SceneContext.GetSceneColorSurface(),
  7. SceneContext.GetGBufferATexture().GetReference(),
  8. SceneContext.GetGBufferBTexture().GetReference(),
  9. SceneContext.GetGBufferCTexture().GetReference()
  10. };
  11. // RHI是否需要将GBuffer存储到GPU的系统内存中,并在单独的渲染通道中进行着色.
  12. ERenderTargetActions GBufferAction = bRequiresMultiPass ? ERenderTargetActions::Clear_Store : ERenderTargetActions::Clear_DontStore;
  13. EDepthStencilTargetActions DepthAction = bKeepDepthContent ? EDepthStencilTargetActions::ClearDepthStencil_StoreDepthStencil : EDepthStencilTargetActions::ClearDepthStencil_DontStoreDepthStencil;
  14. // RT的load/store动作.
  15. ERenderTargetActions ColorTargetsAction[4] = {ERenderTargetActions::Clear_Store, GBufferAction, GBufferAction, GBufferAction};
  16. if (bIsFullPrepassEnabled)
  17. {
  18. ERenderTargetActions DepthTarget = MakeRenderTargetActions(ERenderTargetLoadAction::ELoad, GetStoreAction(GetDepthActions(DepthAction)));
  19. ERenderTargetActions StencilTarget = MakeRenderTargetActions(ERenderTargetLoadAction::ELoad, GetStoreAction(GetStencilActions(DepthAction)));
  20. DepthAction = MakeDepthStencilTargetActions(DepthTarget, StencilTarget);
  21. }
  22. FRHIRenderPassInfo BasePassInfo = FRHIRenderPassInfo();
  23. int32 ColorTargetIndex = 0;
  24. for (; ColorTargetIndex < UE_ARRAY_COUNT(ColorTargets); ++ColorTargetIndex)
  25. {
  26. BasePassInfo.ColorRenderTargets[ColorTargetIndex].RenderTarget = ColorTargets[ColorTargetIndex];
  27. BasePassInfo.ColorRenderTargets[ColorTargetIndex].ResolveTarget = nullptr;
  28. BasePassInfo.ColorRenderTargets[ColorTargetIndex].ArraySlice = -1;
  29. BasePassInfo.ColorRenderTargets[ColorTargetIndex].MipIndex = 0;
  30. BasePassInfo.ColorRenderTargets[ColorTargetIndex].Action = ColorTargetsAction[ColorTargetIndex];
  31. }
  32. if (MobileRequiresSceneDepthAux(ShaderPlatform))
  33. {
  34. BasePassInfo.ColorRenderTargets[ColorTargetIndex].RenderTarget = SceneContext.SceneDepthAux->GetRenderTargetItem().ShaderResourceTexture.GetReference();
  35. BasePassInfo.ColorRenderTargets[ColorTargetIndex].ResolveTarget = nullptr;
  36. BasePassInfo.ColorRenderTargets[ColorTargetIndex].ArraySlice = -1;
  37. BasePassInfo.ColorRenderTargets[ColorTargetIndex].MipIndex = 0;
  38. BasePassInfo.ColorRenderTargets[ColorTargetIndex].Action = GBufferAction;
  39. ColorTargetIndex++;
  40. }
  41. BasePassInfo.DepthStencilRenderTarget.DepthStencilTarget = SceneContext.GetSceneDepthSurface();
  42. BasePassInfo.DepthStencilRenderTarget.ResolveTarget = nullptr;
  43. BasePassInfo.DepthStencilRenderTarget.Action = DepthAction;
  44. BasePassInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthWrite_StencilWrite;
  45. BasePassInfo.SubpassHint = ESubpassHint::DeferredShadingSubpass;
  46. if (!bIsFullPrepassEnabled)
  47. {
  48. BasePassInfo.NumOcclusionQueries = ComputeNumOcclusionQueriesToBatch();
  49. BasePassInfo.bOcclusionQueries = BasePassInfo.NumOcclusionQueries != 0;
  50. }
  51. BasePassInfo.ShadingRateTexture = nullptr;
  52. BasePassInfo.bIsMSAA = false;
  53. BasePassInfo.MultiViewCount = 0;
  54. RHICmdList.BeginRenderPass(BasePassInfo, TEXT("BasePassRendering"));
  55. if (GIsEditor && !Views[0].bIsSceneCapture)
  56. {
  57. DrawClearQuad(RHICmdList, Views[0].BackgroundColor);
  58. }
  59. // 深度PrePass
  60. if (!bIsFullPrepassEnabled)
  61. {
  62. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_MobilePrePass));
  63. // Depth pre-pass
  64. RenderPrePass(RHICmdList);
  65. }
  66. // BasePass: 不透明和镂空物体.
  67. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Opaque));
  68. RenderMobileBasePass(RHICmdList, ViewList);
  69. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  70. // 遮挡剔除.
  71. if (!bIsFullPrepassEnabled)
  72. {
  73. // Issue occlusion queries
  74. RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Occlusion));
  75. RenderOcclusion(RHICmdList);
  76. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  77. }
  78. // 非多Pass模式
  79. if (!bRequiresMultiPass)
  80. {
  81. // 下个子Pass: SSceneColor + GBuffer写入, SceneDepth只读.
  82. RHICmdList.NextSubpass();
  83. // 渲染贴花.
  84. if (ViewFamily.EngineShowFlags.Decals)
  85. {
  86. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderDecals);
  87. RenderDecals(RHICmdList);
  88. }
  89. // 下个子Pass: SceneColor写入, SceneDepth只读
  90. RHICmdList.NextSubpass();
  91. // 延迟光照着色.
  92. MobileDeferredShadingPass(RHICmdList, *Scene, ViewList, SortedLightSet);
  93. // 绘制半透明.
  94. if (ViewFamily.EngineShowFlags.Translucency)
  95. {
  96. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderTranslucency);
  97. SCOPE_CYCLE_COUNTER(STAT_TranslucencyDrawTime);
  98. RenderTranslucency(RHICmdList, ViewList);
  99. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  100. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  101. }
  102. // 结束渲染Pass.
  103. RHICmdList.EndRenderPass();
  104. }
  105. // 多Pass模式(PC设备模拟的移动端).
  106. else
  107. {
  108. // 结束子pass.
  109. RHICmdList.NextSubpass();
  110. RHICmdList.NextSubpass();
  111. RHICmdList.EndRenderPass();
  112. // SceneColor + GBuffer write, SceneDepth is read only
  113. {
  114. for (int32 Index = 0; Index < UE_ARRAY_COUNT(ColorTargets); ++Index)
  115. {
  116. BasePassInfo.ColorRenderTargets[Index].Action = ERenderTargetActions::Load_Store;
  117. }
  118. BasePassInfo.DepthStencilRenderTarget.Action = EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil;
  119. BasePassInfo.DepthStencilRenderTarget.ExclusiveDepthStencil = FExclusiveDepthStencil::DepthRead_StencilRead;
  120. BasePassInfo.SubpassHint = ESubpassHint::None;
  121. BasePassInfo.NumOcclusionQueries = 0;
  122. BasePassInfo.bOcclusionQueries = false;
  123. RHICmdList.BeginRenderPass(BasePassInfo, TEXT("AfterBasePass"));
  124. // 渲染贴花.
  125. if (ViewFamily.EngineShowFlags.Decals)
  126. {
  127. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderDecals);
  128. RenderDecals(RHICmdList);
  129. }
  130. RHICmdList.EndRenderPass();
  131. }
  132. // SceneColor write, SceneDepth is read only
  133. {
  134. FRHIRenderPassInfo ShadingPassInfo(
  135. SceneContext.GetSceneColorSurface(),
  136. ERenderTargetActions::Load_Store,
  137. nullptr,
  138. SceneContext.GetSceneDepthSurface(),
  139. EDepthStencilTargetActions::LoadDepthStencil_StoreDepthStencil,
  140. nullptr,
  141. nullptr,
  142. VRSRB_Passthrough,
  143. FExclusiveDepthStencil::DepthRead_StencilWrite
  144. );
  145. ShadingPassInfo.NumOcclusionQueries = 0;
  146. ShadingPassInfo.bOcclusionQueries = false;
  147. RHICmdList.BeginRenderPass(ShadingPassInfo, TEXT("MobileShadingPass"));
  148. // 延迟光照着色.
  149. MobileDeferredShadingPass(RHICmdList, *Scene, ViewList, SortedLightSet);
  150. // 绘制半透明.
  151. if (ViewFamily.EngineShowFlags.Translucency)
  152. {
  153. CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderTranslucency);
  154. SCOPE_CYCLE_COUNTER(STAT_TranslucencyDrawTime);
  155. RenderTranslucency(RHICmdList, ViewList);
  156. FRHICommandListExecutor::GetImmediateCommandList().PollOcclusionQueries();
  157. RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread);
  158. }
  159. RHICmdList.EndRenderPass();
  160. }
  161. }
  162. return ColorTargets[0];
  163. }

由上面可知,移动端的延迟渲染管线和PC比较类似,先渲染BasePass,获得GBuffer几何信息,再执行光照计算。它们的流程图如下:

DrawClearQuad*

RenderPrePass*

RenderMobileBasePass

RenderOcclusion*

RenderDecals*

MobileDeferredShadingPass*

RenderTranslucency*

PreTonemapMSAA*

当然,也有和PC不一样的地方,最明显的是移动端使用了适配TB(D)R架构的SubPass渲染,使得移动端在渲染PrePass深度、BasePass、光照计算时,让场景颜色、深度、GBuffer等信息一直在On-Chip的缓冲区中,提升渲染效率,降低设备能耗。

12.3.3.1 MobileDeferredShadingPass

延迟渲染光照的过程由MobileDeferredShadingPass担当:

  1. void MobileDeferredShadingPass(
  2. FRHICommandListImmediate& RHICmdList,
  3. const FScene& Scene,
  4. const TArrayView<const FViewInfo*> PassViews,
  5. const FSortedLightSetSceneInfo &SortedLightSet)
  6. {
  7. SCOPED_DRAW_EVENT(RHICmdList, MobileDeferredShading);
  8. const FViewInfo& View0 = *PassViews[0];
  9. FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
  10. // 创建Uniform Buffer.
  11. FUniformBufferRHIRef PassUniformBuffer = CreateMobileSceneTextureUniformBuffer(RHICmdList);
  12. FUniformBufferStaticBindings GlobalUniformBuffers(PassUniformBuffer);
  13. SCOPED_UNIFORM_BUFFER_GLOBAL_BINDINGS(RHICmdList, GlobalUniformBuffers);
  14. // 设置视口.
  15. RHICmdList.SetViewport(View0.ViewRect.Min.X, View0.ViewRect.Min.Y, 0.0f, View0.ViewRect.Max.X, View0.ViewRect.Max.Y, 1.0f);
  16. // 光照的默认材质.
  17. FCachedLightMaterial DefaultMaterial;
  18. DefaultMaterial.MaterialProxy = UMaterial::GetDefaultMaterial(MD_LightFunction)->GetRenderProxy();
  19. DefaultMaterial.Material = DefaultMaterial.MaterialProxy->GetMaterialNoFallback(ERHIFeatureLevel::ES3_1);
  20. check(DefaultMaterial.Material);
  21. // 绘制平行光.
  22. RenderDirectLight(RHICmdList, Scene, View0, DefaultMaterial);
  23. if (GMobileUseClusteredDeferredShading == 0)
  24. {
  25. // 渲染非分簇的简单光源.
  26. RenderSimpleLights(RHICmdList, Scene, PassViews, SortedLightSet, DefaultMaterial);
  27. }
  28. // 渲染非分簇的局部光源.
  29. int32 NumLights = SortedLightSet.SortedLights.Num();
  30. int32 StandardDeferredStart = SortedLightSet.SimpleLightsEnd;
  31. if (GMobileUseClusteredDeferredShading != 0)
  32. {
  33. StandardDeferredStart = SortedLightSet.ClusteredSupportedEnd;
  34. }
  35. // 渲染局部光源.
  36. for (int32 LightIdx = StandardDeferredStart; LightIdx < NumLights; ++LightIdx)
  37. {
  38. const FSortedLightSceneInfo& SortedLight = SortedLightSet.SortedLights[LightIdx];
  39. const FLightSceneInfo& LightSceneInfo = *SortedLight.LightSceneInfo;
  40. RenderLocalLight(RHICmdList, Scene, View0, LightSceneInfo, DefaultMaterial);
  41. }
  42. }

下面继续分析渲染不同类型光源的接口:

  1. // Engine\Source\Runtime\Renderer\Private\MobileDeferredShadingPass.cpp
  2. // 渲染平行光
  3. static void RenderDirectLight(FRHICommandListImmediate& RHICmdList, const FScene& Scene, const FViewInfo& View, const FCachedLightMaterial& DefaultLightMaterial)
  4. {
  5. FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);
  6. // 查找第一个平行光.
  7. FLightSceneInfo* DirectionalLight = nullptr;
  8. for (int32 ChannelIdx = 0; ChannelIdx < UE_ARRAY_COUNT(Scene.MobileDirectionalLights) && !DirectionalLight; ChannelIdx++)
  9. {
  10. DirectionalLight = Scene.MobileDirectionalLights[ChannelIdx];
  11. }
  12. // 渲染状态.
  13. FGraphicsPipelineStateInitializer GraphicsPSOInit;
  14. RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
  15. // 增加自发光到SceneColor.
  16. GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_One>::GetRHI();
  17. GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
  18. // 只绘制默认光照模型(MSM_DefaultLit)的像素.
  19. uint8 StencilRef = GET_STENCIL_MOBILE_SM_MASK(MSM_DefaultLit);
  20. GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<
  21. false, CF_Always,
  22. true, CF_Equal, SO_Keep, SO_Keep, SO_Keep,
  23. false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
  24. GET_STENCIL_MOBILE_SM_MASK(0x7), 0x00>::GetRHI(); // 4 bits for shading models
  25. // 处理VS.
  26. TShaderMapRef<FPostProcessVS> VertexShader(View.ShaderMap);
  27. const FMaterialRenderProxy* LightFunctionMaterialProxy = nullptr;
  28. if (View.Family->EngineShowFlags.LightFunctions && DirectionalLight)
  29. {
  30. LightFunctionMaterialProxy = DirectionalLight->Proxy->GetLightFunctionMaterial();
  31. }
  32. FMobileDirectLightFunctionPS::FPermutationDomain PermutationVector = FMobileDirectLightFunctionPS::BuildPermutationVector(View, DirectionalLight != nullptr);
  33. FCachedLightMaterial LightMaterial;
  34. TShaderRef<FMobileDirectLightFunctionPS> PixelShader;
  35. GetLightMaterial(DefaultLightMaterial, LightFunctionMaterialProxy, PermutationVector.ToDimensionValueId(), LightMaterial, PixelShader);
  36. GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI;
  37. GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
  38. GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
  39. GraphicsPSOInit.PrimitiveType = PT_TriangleList;
  40. SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
  41. // 处理PS.
  42. FMobileDirectLightFunctionPS::FParameters PassParameters;
  43. PassParameters.Forward = View.ForwardLightingResources->ForwardLightDataUniformBuffer;
  44. PassParameters.MobileDirectionalLight = Scene.UniformBuffers.MobileDirectionalLightUniformBuffers[1];
  45. PassParameters.ReflectionCaptureData = Scene.UniformBuffers.ReflectionCaptureUniformBuffer;
  46. FReflectionUniformParameters ReflectionUniformParameters;
  47. SetupReflectionUniformParameters(View, ReflectionUniformParameters);
  48. PassParameters.ReflectionsParameters = CreateUniformBufferImmediate(ReflectionUniformParameters, UniformBuffer_SingleDraw);
  49. PassParameters.LightFunctionParameters = FVector4(1.0f, 1.0f, 0.0f, 0.0f);
  50. if (DirectionalLight)
  51. {
  52. const bool bUseMovableLight = DirectionalLight && !DirectionalLight->Proxy->HasStaticShadowing();
  53. PassParameters.LightFunctionParameters2 = FVector(DirectionalLight->Proxy->GetLightFunctionFadeDistance(), DirectionalLight->Proxy->GetLightFunctionDisabledBrightness(), bUseMovableLight ? 1.0f : 0.0f);
  54. const FVector Scale = DirectionalLight->Proxy->GetLightFunctionScale();
  55. // Switch x and z so that z of the user specified scale affects the distance along the light direction
  56. const FVector InverseScale = FVector(1.f / Scale.Z, 1.f / Scale.Y, 1.f / Scale.X);
  57. PassParameters.WorldToLight = DirectionalLight->Proxy->GetWorldToLight() * FScaleMatrix(FVector(InverseScale));
  58. }
  59. FMobileDirectLightFunctionPS::SetParameters(RHICmdList, PixelShader, View, LightMaterial.MaterialProxy, *LightMaterial.Material, PassParameters);
  60. RHICmdList.SetStencilRef(StencilRef);
  61. const FIntPoint TargetSize = SceneContext.GetBufferSizeXY();
  62. // 用全屏幕的矩形绘制.
  63. DrawRectangle(
  64. RHICmdList,
  65. 0, 0,
  66. View.ViewRect.Width(), View.ViewRect.Height(),
  67. View.ViewRect.Min.X, View.ViewRect.Min.Y,
  68. View.ViewRect.Width(), View.ViewRect.Height(),
  69. FIntPoint(View.ViewRect.Width(), View.ViewRect.Height()),
  70. TargetSize,
  71. VertexShader);
  72. }
  73. // 渲染非分簇模式的简单光源.
  74. static void RenderSimpleLights(
  75. FRHICommandListImmediate& RHICmdList,
  76. const FScene& Scene,
  77. const TArrayView<const FViewInfo*> PassViews,
  78. const FSortedLightSetSceneInfo &SortedLightSet,
  79. const FCachedLightMaterial& DefaultMaterial)
  80. {
  81. const FSimpleLightArray& SimpleLights = SortedLightSet.SimpleLights;
  82. const int32 NumViews = PassViews.Num();
  83. const FViewInfo& View0 = *PassViews[0];
  84. // 处理VS.
  85. TShaderMapRef<TDeferredLightVS<true>> VertexShader(View0.ShaderMap);
  86. TShaderRef<FMobileRadialLightFunctionPS> PixelShaders[2];
  87. {
  88. const FMaterialShaderMap* MaterialShaderMap = DefaultMaterial.Material->GetRenderingThreadShaderMap();
  89. FMobileRadialLightFunctionPS::FPermutationDomain PermutationVector;
  90. PermutationVector.Set<FMobileRadialLightFunctionPS::FSpotLightDim>(false);
  91. PermutationVector.Set<FMobileRadialLightFunctionPS::FIESProfileDim>(false);
  92. PermutationVector.Set<FMobileRadialLightFunctionPS::FInverseSquaredDim>(false);
  93. PixelShaders[0] = MaterialShaderMap->GetShader<FMobileRadialLightFunctionPS>(PermutationVector);
  94. PermutationVector.Set<FMobileRadialLightFunctionPS::FInverseSquaredDim>(true);
  95. PixelShaders[1] = MaterialShaderMap->GetShader<FMobileRadialLightFunctionPS>(PermutationVector);
  96. }
  97. // 设置PSO.
  98. FGraphicsPipelineStateInitializer GraphicsPSOLight[2];
  99. {
  100. SetupSimpleLightPSO(RHICmdList, View0, VertexShader, PixelShaders[0], GraphicsPSOLight[0]);
  101. SetupSimpleLightPSO(RHICmdList, View0, VertexShader, PixelShaders[1], GraphicsPSOLight[1]);
  102. }
  103. // 设置模板缓冲.
  104. FGraphicsPipelineStateInitializer GraphicsPSOLightMask;
  105. {
  106. RHICmdList.ApplyCachedRenderTargets(GraphicsPSOLightMask);
  107. GraphicsPSOLightMask.PrimitiveType = PT_TriangleList;
  108. GraphicsPSOLightMask.BlendState = TStaticBlendStateWriteMask<CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE>::GetRHI();
  109. GraphicsPSOLightMask.RasterizerState = View0.bReverseCulling ? TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI();
  110. // set stencil to 1 where depth test fails
  111. GraphicsPSOLightMask.DepthStencilState = TStaticDepthStencilState<
  112. false, CF_DepthNearOrEqual,
  113. true, CF_Always, SO_Keep, SO_Replace, SO_Keep,
  114. false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
  115. 0x00, STENCIL_SANDBOX_MASK>::GetRHI();
  116. GraphicsPSOLightMask.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
  117. GraphicsPSOLightMask.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
  118. GraphicsPSOLightMask.BoundShaderState.PixelShaderRHI = nullptr;
  119. }
  120. // 遍历所有简单光源列表, 执行着色计算.
  121. for (int32 LightIndex = 0; LightIndex < SimpleLights.InstanceData.Num(); LightIndex++)
  122. {
  123. const FSimpleLightEntry& SimpleLight = SimpleLights.InstanceData[LightIndex];
  124. for (int32 ViewIndex = 0; ViewIndex < NumViews; ViewIndex++)
  125. {
  126. const FViewInfo& View = *PassViews[ViewIndex];
  127. const FSimpleLightPerViewEntry& SimpleLightPerViewData = SimpleLights.GetViewDependentData(LightIndex, ViewIndex, NumViews);
  128. const FSphere LightBounds(SimpleLightPerViewData.Position, SimpleLight.Radius);
  129. if (NumViews > 1)
  130. {
  131. // set viewports only we we have more than one
  132. // otherwise it is set at the start of the pass
  133. RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0.0f, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1.0f);
  134. }
  135. // 渲染光源遮罩.
  136. SetGraphicsPipelineState(RHICmdList, GraphicsPSOLightMask);
  137. VertexShader->SetSimpleLightParameters(RHICmdList, View, LightBounds);
  138. RHICmdList.SetStencilRef(1);
  139. StencilingGeometry::DrawSphere(RHICmdList);
  140. // 渲染光源.
  141. FMobileRadialLightFunctionPS::FParameters PassParameters;
  142. FDeferredLightUniformStruct DeferredLightUniformsValue;
  143. SetupSimpleDeferredLightParameters(SimpleLight, SimpleLightPerViewData, DeferredLightUniformsValue);
  144. PassParameters.DeferredLightUniforms = TUniformBufferRef<FDeferredLightUniformStruct>::CreateUniformBufferImmediate(DeferredLightUniformsValue, EUniformBufferUsage::UniformBuffer_SingleFrame);
  145. PassParameters.IESTexture = GWhiteTexture->TextureRHI;
  146. PassParameters.IESTextureSampler = GWhiteTexture->SamplerStateRHI;
  147. if (SimpleLight.Exponent == 0)
  148. {
  149. SetGraphicsPipelineState(RHICmdList, GraphicsPSOLight[1]);
  150. FMobileRadialLightFunctionPS::SetParameters(RHICmdList, PixelShaders[1], View, DefaultMaterial.MaterialProxy, *DefaultMaterial.Material, PassParameters);
  151. }
  152. else
  153. {
  154. SetGraphicsPipelineState(RHICmdList, GraphicsPSOLight[0]);
  155. FMobileRadialLightFunctionPS::SetParameters(RHICmdList, PixelShaders[0], View, DefaultMaterial.MaterialProxy, *DefaultMaterial.Material, PassParameters);
  156. }
  157. VertexShader->SetSimpleLightParameters(RHICmdList, View, LightBounds);
  158. // 只绘制默认光照模型(MSM_DefaultLit)的像素.
  159. uint8 StencilRef = GET_STENCIL_MOBILE_SM_MASK(MSM_DefaultLit);
  160. RHICmdList.SetStencilRef(StencilRef);
  161. // 用球体渲染光源(点光源和聚光灯), 以快速剔除光源影响之外的像素.
  162. StencilingGeometry::DrawSphere(RHICmdList);
  163. }
  164. }
  165. }
  166. // 渲染局部光源.
  167. static void RenderLocalLight(
  168. FRHICommandListImmediate& RHICmdList,
  169. const FScene& Scene,
  170. const FViewInfo& View,
  171. const FLightSceneInfo& LightSceneInfo,
  172. const FCachedLightMaterial& DefaultLightMaterial)
  173. {
  174. if (!LightSceneInfo.ShouldRenderLight(View))
  175. {
  176. return;
  177. }
  178. // 忽略非局部光源(光源和聚光灯之外的光源).
  179. const uint8 LightType = LightSceneInfo.Proxy->GetLightType();
  180. const bool bIsSpotLight = LightType == LightType_Spot;
  181. const bool bIsPointLight = LightType == LightType_Point;
  182. if (!bIsSpotLight && !bIsPointLight)
  183. {
  184. return;
  185. }
  186. // 绘制光源模板.
  187. if (GMobileUseLightStencilCulling != 0)
  188. {
  189. RenderLocalLight_StencilMask(RHICmdList, Scene, View, LightSceneInfo);
  190. }
  191. // 处理IES光照.
  192. bool bUseIESTexture = false;
  193. FTexture* IESTextureResource = GWhiteTexture;
  194. if (View.Family->EngineShowFlags.TexturedLightProfiles && LightSceneInfo.Proxy->GetIESTextureResource())
  195. {
  196. IESTextureResource = LightSceneInfo.Proxy->GetIESTextureResource();
  197. bUseIESTexture = true;
  198. }
  199. FGraphicsPipelineStateInitializer GraphicsPSOInit;
  200. RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
  201. GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_One, BO_Add, BF_One, BF_One>::GetRHI();
  202. GraphicsPSOInit.PrimitiveType = PT_TriangleList;
  203. const FSphere LightBounds = LightSceneInfo.Proxy->GetBoundingSphere();
  204. // 设置光源光栅化和深度状态.
  205. if (GMobileUseLightStencilCulling != 0)
  206. {
  207. SetLocalLightRasterizerAndDepthState_StencilMask(GraphicsPSOInit, View);
  208. }
  209. else
  210. {
  211. SetLocalLightRasterizerAndDepthState(GraphicsPSOInit, View, LightBounds);
  212. }
  213. // 设置VS
  214. TShaderMapRef<TDeferredLightVS<true>> VertexShader(View.ShaderMap);
  215. const FMaterialRenderProxy* LightFunctionMaterialProxy = nullptr;
  216. if (View.Family->EngineShowFlags.LightFunctions)
  217. {
  218. LightFunctionMaterialProxy = LightSceneInfo.Proxy->GetLightFunctionMaterial();
  219. }
  220. FMobileRadialLightFunctionPS::FPermutationDomain PermutationVector;
  221. PermutationVector.Set<FMobileRadialLightFunctionPS::FSpotLightDim>(bIsSpotLight);
  222. PermutationVector.Set<FMobileRadialLightFunctionPS::FInverseSquaredDim>(LightSceneInfo.Proxy->IsInverseSquared());
  223. PermutationVector.Set<FMobileRadialLightFunctionPS::FIESProfileDim>(bUseIESTexture);
  224. FCachedLightMaterial LightMaterial;
  225. TShaderRef<FMobileRadialLightFunctionPS> PixelShader;
  226. GetLightMaterial(DefaultLightMaterial, LightFunctionMaterialProxy, PermutationVector.ToDimensionValueId(), LightMaterial, PixelShader);
  227. GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
  228. GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
  229. GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
  230. SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
  231. VertexShader->SetParameters(RHICmdList, View, &LightSceneInfo);
  232. // 设置PS.
  233. FMobileRadialLightFunctionPS::FParameters PassParameters;
  234. PassParameters.DeferredLightUniforms = TUniformBufferRef<FDeferredLightUniformStruct>::CreateUniformBufferImmediate(GetDeferredLightParameters(View, LightSceneInfo), EUniformBufferUsage::UniformBuffer_SingleFrame);
  235. PassParameters.IESTexture = IESTextureResource->TextureRHI;
  236. PassParameters.IESTextureSampler = IESTextureResource->SamplerStateRHI;
  237. const float TanOuterAngle = bIsSpotLight ? FMath::Tan(LightSceneInfo.Proxy->GetOuterConeAngle()) : 1.0f;
  238. PassParameters.LightFunctionParameters = FVector4(TanOuterAngle, 1.0f /*ShadowFadeFraction*/, bIsSpotLight ? 1.0f : 0.0f, bIsPointLight ? 1.0f : 0.0f);
  239. PassParameters.LightFunctionParameters2 = FVector(LightSceneInfo.Proxy->GetLightFunctionFadeDistance(), LightSceneInfo.Proxy->GetLightFunctionDisabledBrightness(), 0.0f);
  240. const FVector Scale = LightSceneInfo.Proxy->GetLightFunctionScale();
  241. // Switch x and z so that z of the user specified scale affects the distance along the light direction
  242. const FVector InverseScale = FVector(1.f / Scale.Z, 1.f / Scale.Y, 1.f / Scale.X);
  243. PassParameters.WorldToLight = LightSceneInfo.Proxy->GetWorldToLight() * FScaleMatrix(FVector(InverseScale));
  244. FMobileRadialLightFunctionPS::SetParameters(RHICmdList, PixelShader, View, LightMaterial.MaterialProxy, *LightMaterial.Material, PassParameters);
  245. // 只绘制默认光照模型(MSM_DefaultLit)的像素.
  246. uint8 StencilRef = GET_STENCIL_MOBILE_SM_MASK(MSM_DefaultLit);
  247. RHICmdList.SetStencilRef(StencilRef);
  248. // 点光源用球体绘制.
  249. if (LightType == LightType_Point)
  250. {
  251. StencilingGeometry::DrawSphere(RHICmdList);
  252. }
  253. // 聚光灯用锥体绘制.
  254. else // LightType_Spot
  255. {
  256. StencilingGeometry::DrawCone(RHICmdList);
  257. }
  258. }

绘制光源时,按光源类型划分为三个步骤:平行光、非分簇简单光源、局部光源(点光源和聚光灯)。需要注意的是,移动端只支持默认光照模型(MSM_DefaultLit)的计算,其它高级光照模型(头发、次表面散射、清漆、眼睛、布料等)暂不支持。

绘制平行光时,最多只能绘制1个,采用的是全屏幕矩形绘制,支持若干级CSM阴影。

绘制非分簇简单光源时,无论是点光源还是聚光灯,都采用球体绘制,不支持阴影。

绘制局部光源时,会复杂许多,先绘制局部光源模板缓冲,再设置光栅化和深度状态,最后才绘制光源。其中点光源采用球体绘制,不支持阴影;聚光灯采用锥体绘制,可以支持阴影,默认情况下,聚光灯不支持动态光影计算,需要在工程配置中开启:

此外,是否开启模板剔除光源不相交的像素由GMobileUseLightStencilCulling决定,而GMobileUseLightStencilCulling又由r.Mobile.UseLightStencilCulling决定,默认为1(即开启状态)。渲染光源的模板缓冲代码如下:

  1. static void RenderLocalLight_StencilMask(FRHICommandListImmediate& RHICmdList, const FScene& Scene, const FViewInfo& View, const FLightSceneInfo& LightSceneInfo)
  2. {
  3. const uint8 LightType = LightSceneInfo.Proxy->GetLightType();
  4. FGraphicsPipelineStateInitializer GraphicsPSOInit;
  5. // 应用缓存好的RT(颜色/深度等).
  6. RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
  7. GraphicsPSOInit.PrimitiveType = PT_TriangleList;
  8. // 禁用所有RT的写操作.
  9. GraphicsPSOInit.BlendState = TStaticBlendStateWriteMask<CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE, CW_NONE>::GetRHI();
  10. GraphicsPSOInit.RasterizerState = View.bReverseCulling ? TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI() : TStaticRasterizerState<FM_Solid, CM_CW>::GetRHI();
  11. // 如果深度测试失败, 则写入模板缓冲值为1.
  12. GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<
  13. false, CF_DepthNearOrEqual,
  14. true, CF_Always, SO_Keep, SO_Replace, SO_Keep,
  15. false, CF_Always, SO_Keep, SO_Keep, SO_Keep,
  16. 0x00,
  17. // 注意只写入Pass专用的沙盒(SANBOX)位, 即模板缓冲的索引为0的位.
  18. STENCIL_SANDBOX_MASK>::GetRHI();
  19. // 绘制光源模板的VS是TDeferredLightVS.
  20. TShaderMapRef<TDeferredLightVS<true> > VertexShader(View.ShaderMap);
  21. GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
  22. GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
  23. // PS为空.
  24. GraphicsPSOInit.BoundShaderState.PixelShaderRHI = nullptr;
  25. SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
  26. VertexShader->SetParameters(RHICmdList, View, &LightSceneInfo);
  27. // 模板值为1.
  28. RHICmdList.SetStencilRef(1);
  29. // 根据不同光源用不同形状绘制.
  30. if (LightType == LightType_Point)
  31. {
  32. StencilingGeometry::DrawSphere(RHICmdList);
  33. }
  34. else // LightType_Spot
  35. {
  36. StencilingGeometry::DrawCone(RHICmdList);
  37. }
  38. }

每个局部光源首先绘制光源范围内的Mask,再计算通过了Stencil测试(Early-Z)的像素的光照。具体的剖析过程以下图的聚光灯为例:

上:场景中一盏等待渲染的聚光灯;中:利用模板Pass绘制出的模板Mask(白色区域),标记了屏幕空间中和聚光灯形状重叠且深度更近的像素 ;下:对有效像素进行光照计算后的效果。

对有效像素进行光照计算时,使用的DepthStencil状态如下:

翻译成文字就是,执行光照的像素必须在光源形状体之内,光源形状之外的像素会被剔除。模板Pass标记的是比光源形状深度更近的像素(光源形状体之外的像素),光源绘制Pass通过模板测试剔除模板Pass标记的像素,然后再通过深度测试找出在光源形状体内的像素,从而提升光照计算效率。

移动端的这种光源模板裁剪(Light Stencil Culling)技术和Siggraph2020的Unity演讲Deferred Shading in Unity URP提及的基于模板的光照计算相似(思想一致,但做法可能不完全一样)。该论文还提出了更加契合光源形状的几何体模拟:

以及对比了各种光源计算方法在PC和移动端的性能,下面是Mali GPU的对比图:

Mali Gpu在使用不同光照渲染技术的性能对比,可见在移动端,基于模板裁剪的光照算法要优于常规和分块算法。

值得一提的是,光源模板裁剪技术结合GPU的Early-Z技术,将极大提升光照渲染性能。而当前主流的移动端GPU都支持Early-Z技术,也为光源模板裁剪的应用奠定了基础。

UE目前实现的光源裁剪算法兴许还有改进的空间,比如背向光源的像素(下图红框所示)其实也是可以不计算的。(但如何快速有效地找到背向光源的像素又是一个问题)

12.3.3.2 MobileBasePassShader

本节主要阐述移动端BasePass涉及的shader,包括VS和PS。先看VS:

  1. // Engine\Shaders\Private\MobileBasePassVertexShader.usf
  2. (......)
  3. struct FMobileShadingBasePassVSToPS
  4. {
  5. FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
  6. FMobileBasePassInterpolantsVSToPS BasePassInterpolants;
  7. float4 Position : SV_POSITION;
  8. };
  9. #define FMobileShadingBasePassVSOutput FMobileShadingBasePassVSToPS
  10. #define VertexFactoryGetInterpolants VertexFactoryGetInterpolantsVSToPS
  11. // VS主入口.
  12. void Main(
  13. FVertexFactoryInput Input
  14. , out FMobileShadingBasePassVSOutput Output
  15. #if INSTANCED_STEREO
  16. , uint InstanceId : SV_InstanceID
  17. , out uint LayerIndex : SV_RenderTargetArrayIndex
  18. #elif MOBILE_MULTI_VIEW
  19. , in uint ViewId : SV_ViewID
  20. #endif
  21. )
  22. {
  23. // 立体视图模式.
  24. #if INSTANCED_STEREO
  25. const uint EyeIndex = GetEyeIndex(InstanceId);
  26. ResolvedView = ResolveView(EyeIndex);
  27. LayerIndex = EyeIndex;
  28. Output.BasePassInterpolants.MultiViewId = float(EyeIndex);
  29. // 多视图模式.
  30. #elif MOBILE_MULTI_VIEW
  31. #if COMPILER_GLSL_ES3_1
  32. const int MultiViewId = int(ViewId);
  33. ResolvedView = ResolveView(uint(MultiViewId));
  34. Output.BasePassInterpolants.MultiViewId = float(MultiViewId);
  35. #else
  36. ResolvedView = ResolveView(ViewId);
  37. Output.BasePassInterpolants.MultiViewId = float(ViewId);
  38. #endif
  39. #else
  40. ResolvedView = ResolveView();
  41. #endif
  42. // 初始化打包的插值数据.
  43. #if PACK_INTERPOLANTS
  44. float4 PackedInterps[NUM_VF_PACKED_INTERPOLANTS];
  45. UNROLL
  46. for(int i = 0; i < NUM_VF_PACKED_INTERPOLANTS; ++i)
  47. {
  48. PackedInterps[i] = 0;
  49. }
  50. #endif
  51. // 处理顶点工厂数据.
  52. FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
  53. float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
  54. float4 WorldPosition = WorldPositionExcludingWPO;
  55. // 获取材质的顶点数据, 处理坐标等.
  56. half3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
  57. FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);
  58. half3 WorldPositionOffset = GetMaterialWorldPositionOffset(VertexParameters);
  59. WorldPosition.xyz += WorldPositionOffset;
  60. float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
  61. Output.Position = mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip);
  62. Output.BasePassInterpolants.PixelPosition = WorldPosition;
  63. #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
  64. Output.BasePassInterpolants.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
  65. #endif
  66. // 裁剪面.
  67. #if USE_PS_CLIP_PLANE
  68. Output.BasePassInterpolants.OutClipDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPosition.xyz - ResolvedView.PreViewTranslation.xyz, 1));
  69. #endif
  70. // 顶点雾.
  71. #if USE_VERTEX_FOG
  72. float4 VertexFog = CalculateHeightFog(WorldPosition.xyz - ResolvedView.TranslatedWorldCameraOrigin);
  73. #if PROJECT_SUPPORT_SKY_ATMOSPHERE && MATERIAL_IS_SKY==0 // Do not apply aerial perpsective on sky materials
  74. if (ResolvedView.SkyAtmosphereApplyCameraAerialPerspectiveVolume > 0.0f)
  75. {
  76. const float OneOverPreExposure = USE_PREEXPOSURE ? ResolvedView.OneOverPreExposure : 1.0f;
  77. // Sample the aerial perspective (AP). It is also blended under the VertexFog parameter.
  78. VertexFog = GetAerialPerspectiveLuminanceTransmittanceWithFogOver(
  79. ResolvedView.RealTimeReflectionCapture, ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeSizeAndInvSize,
  80. Output.Position, WorldPosition.xyz*CM_TO_SKY_UNIT, ResolvedView.TranslatedWorldCameraOrigin*CM_TO_SKY_UNIT,
  81. View.CameraAerialPerspectiveVolume, View.CameraAerialPerspectiveVolumeSampler,
  82. ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthResolutionInv,
  83. ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthResolution,
  84. ResolvedView.SkyAtmosphereAerialPerspectiveStartDepthKm,
  85. ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthSliceLengthKm,
  86. ResolvedView.SkyAtmosphereCameraAerialPerspectiveVolumeDepthSliceLengthKmInv,
  87. OneOverPreExposure, VertexFog);
  88. }
  89. #endif
  90. #if PACK_INTERPOLANTS
  91. PackedInterps[0] = VertexFog;
  92. #else
  93. Output.BasePassInterpolants.VertexFog = VertexFog;
  94. #endif // PACK_INTERPOLANTS
  95. #endif // USE_VERTEX_FOG
  96. (......)
  97. // 获取待插值的数据.
  98. Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
  99. Output.BasePassInterpolants.PixelPosition.w = Output.Position.w;
  100. // 打包插值数据.
  101. #if PACK_INTERPOLANTS
  102. VertexFactoryPackInterpolants(Output.FactoryInterpolants, PackedInterps);
  103. #endif // PACK_INTERPOLANTS
  104. #if !OUTPUT_MOBILE_HDR && COMPILER_GLSL_ES3_1
  105. Output.Position.y *= -1;
  106. #endif
  107. }

以上可知,视图实例会根据立体绘制、多视图和普通模式不同而不同处理。支持顶点雾,但默认是关闭的,需要在工程配置内开启。

存在打包插值模式,为了压缩VS到PS之间的插值消耗和带宽。是否开启由宏PACK_INTERPOLANTS决定,它的定义如下:

  1. // Engine\Shaders\Private\MobileBasePassCommon.ush
  2. #define PACK_INTERPOLANTS (USE_VERTEX_FOG && NUM_VF_PACKED_INTERPOLANTS > 0 && (ES3_1_PROFILE))

也就是说,只有开启顶点雾、存在顶点工厂打包插值数据且是OpenGLES3.1着色平台才开启打包插值的特性。相比PC端的BasePass的VS,移动端的做了大量的简化,可以简单地认为只是PC端的一个很小的子集。下面继续分析PS:

  1. // Engine\Shaders\Private\MobileBasePassPixelShader.usf
  2. #include "Common.ush"
  3. // 各类宏定义.
  4. #define MobileSceneTextures MobileBasePass.SceneTextures
  5. #define EyeAdaptationStruct MobileBasePass
  6. (......)
  7. // 最接近被渲染对象的场景的预归一化捕获(完全粗糙材质不支持)
  8. #if !FULLY_ROUGH
  9. #if HQ_REFLECTIONS
  10. #define MAX_HQ_REFLECTIONS 3
  11. TextureCube ReflectionCubemap0;
  12. SamplerState ReflectionCubemapSampler0;
  13. TextureCube ReflectionCubemap1;
  14. SamplerState ReflectionCubemapSampler1;
  15. TextureCube ReflectionCubemap2;
  16. SamplerState ReflectionCubemapSampler2;
  17. // x,y,z - inverted average brightness for 0, 1, 2; w - sky cube texture max mips.
  18. float4 ReflectionAverageBrigtness;
  19. float4 ReflectanceMaxValueRGBM;
  20. float4 ReflectionPositionsAndRadii[MAX_HQ_REFLECTIONS];
  21. #if ALLOW_CUBE_REFLECTIONS
  22. float4x4 CaptureBoxTransformArray[MAX_HQ_REFLECTIONS];
  23. float4 CaptureBoxScalesArray[MAX_HQ_REFLECTIONS];
  24. #endif
  25. #endif
  26. #endif
  27. // 反射球/IBL等接口.
  28. half4 GetPlanarReflection(float3 WorldPosition, half3 WorldNormal, half Roughness);
  29. half MobileComputeMixingWeight(half IndirectIrradiance, half AverageBrightness, half Roughness);
  30. half3 GetLookupVectorForBoxCaptureMobile(half3 ReflectionVector, ...);
  31. half3 GetLookupVectorForSphereCaptureMobile(half3 ReflectionVector, ...);
  32. void GatherSpecularIBL(FMaterialPixelParameters MaterialParameters, ...);
  33. void BlendReflectionCaptures(FMaterialPixelParameters MaterialParameters, ...)
  34. half3 GetImageBasedReflectionLighting(FMaterialPixelParameters MaterialParameters, ...);
  35. // 其它接口.
  36. half3 FrameBufferBlendOp(half4 Source);
  37. bool UseCSM();
  38. void ApplyPixelDepthOffsetForMobileBasePass(inout FMaterialPixelParameters MaterialParameters, FPixelMaterialInputs PixelMaterialInputs, out float OutDepth);
  39. // 累积动态点光源.
  40. #if MAX_DYNAMIC_POINT_LIGHTS > 0
  41. void AccumulateLightingOfDynamicPointLight(
  42. FMaterialPixelParameters MaterialParameters,
  43. FMobileShadingModelContext ShadingModelContext,
  44. FGBufferData GBuffer,
  45. float4 LightPositionAndInvRadius,
  46. float4 LightColorAndFalloffExponent,
  47. float4 SpotLightDirectionAndSpecularScale,
  48. float4 SpotLightAnglesAndSoftTransitionScaleAndLightShadowType,
  49. #if SUPPORT_SPOTLIGHTS_SHADOW
  50. FPCFSamplerSettings Settings,
  51. float4 SpotLightShadowSharpenAndShadowFadeFraction,
  52. float4 SpotLightShadowmapMinMax,
  53. float4x4 SpotLightShadowWorldToShadowMatrix,
  54. #endif
  55. inout half3 Color)
  56. {
  57. uint LightShadowType = SpotLightAnglesAndSoftTransitionScaleAndLightShadowType.w;
  58. float FadedShadow = 1.0f;
  59. // 计算聚光灯阴影.
  60. #if SUPPORT_SPOTLIGHTS_SHADOW
  61. if ((LightShadowType & LightShadowType_Shadow) == LightShadowType_Shadow)
  62. {
  63. float4 HomogeneousShadowPosition = mul(float4(MaterialParameters.AbsoluteWorldPosition, 1), SpotLightShadowWorldToShadowMatrix);
  64. float2 ShadowUVs = HomogeneousShadowPosition.xy / HomogeneousShadowPosition.w;
  65. if (all(ShadowUVs >= SpotLightShadowmapMinMax.xy && ShadowUVs <= SpotLightShadowmapMinMax.zw))
  66. {
  67. // Clamp pixel depth in light space for shadowing opaque, because areas of the shadow depth buffer that weren't rendered to will have been cleared to 1
  68. // We want to force the shadow comparison to result in 'unshadowed' in that case, regardless of whether the pixel being shaded is in front or behind that plane
  69. float LightSpacePixelDepthForOpaque = min(HomogeneousShadowPosition.z, 0.99999f);
  70. Settings.SceneDepth = LightSpacePixelDepthForOpaque;
  71. Settings.TransitionScale = SpotLightAnglesAndSoftTransitionScaleAndLightShadowType.z;
  72. half Shadow = MobileShadowPCF(ShadowUVs, Settings);
  73. Shadow = saturate((Shadow - 0.5) * SpotLightShadowSharpenAndShadowFadeFraction.x + 0.5);
  74. FadedShadow = lerp(1.0f, Square(Shadow), SpotLightShadowSharpenAndShadowFadeFraction.y);
  75. }
  76. }
  77. #endif
  78. // 计算光照.
  79. if ((LightShadowType & ValidLightType) != 0)
  80. {
  81. float3 ToLight = LightPositionAndInvRadius.xyz - MaterialParameters.AbsoluteWorldPosition;
  82. float DistanceSqr = dot(ToLight, ToLight);
  83. float3 L = ToLight * rsqrt(DistanceSqr);
  84. half3 PointH = normalize(MaterialParameters.CameraVector + L);
  85. half PointNoL = max(0, dot(MaterialParameters.WorldNormal, L));
  86. half PointNoH = max(0, dot(MaterialParameters.WorldNormal, PointH));
  87. // 计算光源的衰减.
  88. float Attenuation;
  89. if (LightColorAndFalloffExponent.w == 0)
  90. {
  91. // Sphere falloff (technically just 1/d2 but this avoids inf)
  92. Attenuation = 1 / (DistanceSqr + 1);
  93. float LightRadiusMask = Square(saturate(1 - Square(DistanceSqr * (LightPositionAndInvRadius.w * LightPositionAndInvRadius.w))));
  94. Attenuation *= LightRadiusMask;
  95. }
  96. else
  97. {
  98. Attenuation = RadialAttenuation(ToLight * LightPositionAndInvRadius.w, LightColorAndFalloffExponent.w);
  99. }
  100. #if PROJECT_MOBILE_ENABLE_MOVABLE_SPOTLIGHTS
  101. if ((LightShadowType & LightShadowType_SpotLight) == LightShadowType_SpotLight)
  102. {
  103. Attenuation *= SpotAttenuation(L, -SpotLightDirectionAndSpecularScale.xyz, SpotLightAnglesAndSoftTransitionScaleAndLightShadowType.xy) * FadedShadow;
  104. }
  105. #endif
  106. // 累加光照结果.
  107. #if !FULLY_ROUGH
  108. FMobileDirectLighting Lighting = MobileIntegrateBxDF(ShadingModelContext, GBuffer, PointNoL, MaterialParameters.CameraVector, PointH, PointNoH);
  109. Color += min(65000.0, (Attenuation) * LightColorAndFalloffExponent.rgb * (1.0 / PI) * (Lighting.Diffuse + Lighting.Specular * SpotLightDirectionAndSpecularScale.w));
  110. #else
  111. Color += (Attenuation * PointNoL) * LightColorAndFalloffExponent.rgb * (1.0 / PI) * ShadingModelContext.DiffuseColor;
  112. #endif
  113. }
  114. }
  115. #endif
  116. (......)
  117. // 计算非直接光照.
  118. half ComputeIndirect(VTPageTableResult LightmapVTPageTableResult, FVertexFactoryInterpolantsVSToPS Interpolants, float3 DiffuseDir, FMobileShadingModelContext ShadingModelContext, out half IndirectIrradiance, out half3 Color)
  119. {
  120. //To keep IndirectLightingCache conherence with PC, initialize the IndirectIrradiance to zero.
  121. IndirectIrradiance = 0;
  122. Color = 0;
  123. // 非直接漫反射.
  124. #if LQ_TEXTURE_LIGHTMAP
  125. float2 LightmapUV0, LightmapUV1;
  126. uint LightmapDataIndex;
  127. GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
  128. half4 LightmapColor = GetLightMapColorLQ(LightmapVTPageTableResult, LightmapUV0, LightmapUV1, LightmapDataIndex, DiffuseDir);
  129. Color += LightmapColor.rgb * ShadingModelContext.DiffuseColor * View.IndirectLightingColorScale;
  130. IndirectIrradiance = LightmapColor.a;
  131. #elif CACHED_POINT_INDIRECT_LIGHTING
  132. #if MATERIALBLENDING_MASKED || MATERIALBLENDING_SOLID
  133. // 将法线应用到半透明物体.
  134. FThreeBandSHVectorRGB PointIndirectLighting;
  135. PointIndirectLighting.R.V0 = IndirectLightingCache.IndirectLightingSHCoefficients0[0];
  136. PointIndirectLighting.R.V1 = IndirectLightingCache.IndirectLightingSHCoefficients1[0];
  137. PointIndirectLighting.R.V2 = IndirectLightingCache.IndirectLightingSHCoefficients2[0];
  138. PointIndirectLighting.G.V0 = IndirectLightingCache.IndirectLightingSHCoefficients0[1];
  139. PointIndirectLighting.G.V1 = IndirectLightingCache.IndirectLightingSHCoefficients1[1];
  140. PointIndirectLighting.G.V2 = IndirectLightingCache.IndirectLightingSHCoefficients2[1];
  141. PointIndirectLighting.B.V0 = IndirectLightingCache.IndirectLightingSHCoefficients0[2];
  142. PointIndirectLighting.B.V1 = IndirectLightingCache.IndirectLightingSHCoefficients1[2];
  143. PointIndirectLighting.B.V2 = IndirectLightingCache.IndirectLightingSHCoefficients2[2];
  144. FThreeBandSHVector DiffuseTransferSH = CalcDiffuseTransferSH3(DiffuseDir, 1);
  145. // 计算加入了法线影响的漫反射光照.
  146. half3 DiffuseGI = max(half3(0, 0, 0), DotSH3(PointIndirectLighting, DiffuseTransferSH));
  147. IndirectIrradiance = Luminance(DiffuseGI);
  148. Color += ShadingModelContext.DiffuseColor * DiffuseGI * View.IndirectLightingColorScale;
  149. #else
  150. // 半透明使用无方向(Non-directional), 漫反射被打包在xyz, 已经在cpu端除了PI和SH漫反射.
  151. half3 PointIndirectLighting = IndirectLightingCache.IndirectLightingSHSingleCoefficient.rgb;
  152. half3 DiffuseGI = PointIndirectLighting;
  153. IndirectIrradiance = Luminance(DiffuseGI);
  154. Color += ShadingModelContext.DiffuseColor * DiffuseGI * View.IndirectLightingColorScale;
  155. #endif
  156. #endif
  157. return IndirectIrradiance;
  158. }
  159. // PS主入口.
  160. PIXELSHADER_EARLYDEPTHSTENCIL
  161. void Main(
  162. FVertexFactoryInterpolantsVSToPS Interpolants
  163. , FMobileBasePassInterpolantsVSToPS BasePassInterpolants
  164. , in float4 SvPosition : SV_Position
  165. OPTIONAL_IsFrontFace
  166. , out half4 OutColor : SV_Target0
  167. #if DEFERRED_SHADING_PATH
  168. , out half4 OutGBufferA : SV_Target1
  169. , out half4 OutGBufferB : SV_Target2
  170. , out half4 OutGBufferC : SV_Target3
  171. #endif
  172. #if USE_SCENE_DEPTH_AUX
  173. , out float OutSceneDepthAux : SV_Target4
  174. #endif
  175. #if OUTPUT_PIXEL_DEPTH_OFFSET
  176. , out float OutDepth : SV_Depth
  177. #endif
  178. )
  179. {
  180. #if MOBILE_MULTI_VIEW
  181. ResolvedView = ResolveView(uint(BasePassInterpolants.MultiViewId));
  182. #else
  183. ResolvedView = ResolveView();
  184. #endif
  185. #if USE_PS_CLIP_PLANE
  186. clip(BasePassInterpolants.OutClipDistance);
  187. #endif
  188. // 解压打包的插值数据.
  189. #if PACK_INTERPOLANTS
  190. float4 PackedInterpolants[NUM_VF_PACKED_INTERPOLANTS];
  191. VertexFactoryUnpackInterpolants(Interpolants, PackedInterpolants);
  192. #endif
  193. #if COMPILER_GLSL_ES3_1 && !OUTPUT_MOBILE_HDR && !MOBILE_EMULATION
  194. // LDR Mobile needs screen vertical flipped
  195. SvPosition.y = ResolvedView.BufferSizeAndInvSize.y - SvPosition.y - 1;
  196. #endif
  197. // 获取材质的像素属性.
  198. FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition);
  199. FPixelMaterialInputs PixelMaterialInputs;
  200. {
  201. float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
  202. float3 WorldPosition = BasePassInterpolants.PixelPosition.xyz;
  203. float3 WorldPositionExcludingWPO = BasePassInterpolants.PixelPosition.xyz;
  204. #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
  205. WorldPositionExcludingWPO = BasePassInterpolants.PixelPositionExcludingWPO;
  206. #endif
  207. CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, WorldPosition, WorldPositionExcludingWPO);
  208. #if FORCE_VERTEX_NORMAL
  209. // Quality level override of material's normal calculation, can be used to avoid normal map reads etc.
  210. MaterialParameters.WorldNormal = MaterialParameters.TangentToWorld[2];
  211. MaterialParameters.ReflectionVector = ReflectionAboutCustomWorldNormal(MaterialParameters, MaterialParameters.WorldNormal, false);
  212. #endif
  213. }
  214. // 像素深度偏移.
  215. #if OUTPUT_PIXEL_DEPTH_OFFSET
  216. ApplyPixelDepthOffsetForMobileBasePass(MaterialParameters, PixelMaterialInputs, OutDepth);
  217. #endif
  218. // Mask材质.
  219. #if !EARLY_Z_PASS_ONLY_MATERIAL_MASKING
  220. //Clip if the blend mode requires it.
  221. GetMaterialCoverageAndClipping(MaterialParameters, PixelMaterialInputs);
  222. #endif
  223. // 计算并缓存GBuffer数据, 防止后续多次采用纹理.
  224. FGBufferData GBuffer = (FGBufferData)0;
  225. GBuffer.WorldNormal = MaterialParameters.WorldNormal;
  226. GBuffer.BaseColor = GetMaterialBaseColor(PixelMaterialInputs);
  227. GBuffer.Metallic = GetMaterialMetallic(PixelMaterialInputs);
  228. GBuffer.Specular = GetMaterialSpecular(PixelMaterialInputs);
  229. GBuffer.Roughness = GetMaterialRoughness(PixelMaterialInputs);
  230. GBuffer.ShadingModelID = GetMaterialShadingModel(PixelMaterialInputs);
  231. half MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);
  232. // 应用AO.
  233. #if APPLY_AO
  234. half4 GatheredAmbientOcclusion = Texture2DSample(AmbientOcclusionTexture, AmbientOcclusionSampler, SvPositionToBufferUV(SvPosition));
  235. MaterialAO *= GatheredAmbientOcclusion.r;
  236. #endif
  237. GBuffer.GBufferAO = MaterialAO;
  238. // 由于IEEE 754 (FP16)可表示的最小标准值是2^-24 = 5.96e-8, 而后面的粗糙度涉及到1.0 / Roughness^4的计算, 所以为了防止除零错误, 需保证Roughness^4 >= 5.96e-8, 此处直接Clamp粗糙度到0.015625(0.015625^4 = 5.96e-8).
  239. // 另外, 为了匹配PC端的延迟渲染(粗糙度存储在8位的值), 因此也自动Clamp到1.0.
  240. GBuffer.Roughness = max(0.015625, GetMaterialRoughness(PixelMaterialInputs));
  241. // 初始化移动端着色模型上下文FMobileShadingModelContext.
  242. FMobileShadingModelContext ShadingModelContext = (FMobileShadingModelContext)0;
  243. ShadingModelContext.Opacity = GetMaterialOpacity(PixelMaterialInputs);
  244. // 薄层透明度物
  245. #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
  246. (......)
  247. #endif
  248. half3 Color = 0;
  249. // 自定义数据.
  250. half CustomData0 = GetMaterialCustomData0(MaterialParameters);
  251. half CustomData1 = GetMaterialCustomData1(MaterialParameters);
  252. InitShadingModelContext(ShadingModelContext, GBuffer, MaterialParameters.SvPosition, MaterialParameters.CameraVector, CustomData0, CustomData1);
  253. float3 DiffuseDir = MaterialParameters.WorldNormal;
  254. // 头发模型.
  255. #if MATERIAL_SHADINGMODEL_HAIR
  256. (......)
  257. #endif
  258. // 光照图虚拟纹理.
  259. VTPageTableResult LightmapVTPageTableResult = (VTPageTableResult)0.0f;
  260. #if LIGHTMAP_VT_ENABLED
  261. {
  262. float2 LightmapUV0, LightmapUV1;
  263. uint LightmapDataIndex;
  264. GetLightMapCoordinates(Interpolants, LightmapUV0, LightmapUV1, LightmapDataIndex);
  265. LightmapVTPageTableResult = LightmapGetVTSampleInfo(LightmapUV0, LightmapDataIndex, SvPosition.xy);
  266. }
  267. #endif
  268. #if LIGHTMAP_VT_ENABLED
  269. // This must occur after CalcMaterialParameters(), which is required to initialize the VT feedback mechanism
  270. // Lightmap request is always the first VT sample in the shader
  271. StoreVirtualTextureFeedback(MaterialParameters.VirtualTextureFeedback, 0, LightmapVTPageTableResult.PackedRequest);
  272. #endif
  273. // 计算非直接光.
  274. half IndirectIrradiance;
  275. half3 IndirectColor;
  276. ComputeIndirect(LightmapVTPageTableResult, Interpolants, DiffuseDir, ShadingModelContext, IndirectIrradiance, IndirectColor);
  277. Color += IndirectColor;
  278. // 预计算的阴影图.
  279. half Shadow = GetPrimaryPrecomputedShadowMask(LightmapVTPageTableResult, Interpolants).r;
  280. #if DEFERRED_SHADING_PATH
  281. float4 OutGBufferD;
  282. float4 OutGBufferE;
  283. float4 OutGBufferF;
  284. float4 OutGBufferVelocity = 0;
  285. GBuffer.IndirectIrradiance = IndirectIrradiance;
  286. GBuffer.PrecomputedShadowFactors.r = Shadow;
  287. // 编码GBuffer数据.
  288. EncodeGBuffer(GBuffer, OutGBufferA, OutGBufferB, OutGBufferC, OutGBufferD, OutGBufferE, OutGBufferF, OutGBufferVelocity);
  289. #else
  290. #if !MATERIAL_SHADINGMODEL_UNLIT
  291. // 天光.
  292. #if ENABLE_SKY_LIGHT
  293. half3 SkyDiffuseLighting = GetSkySHDiffuseSimple(MaterialParameters.WorldNormal);
  294. half3 DiffuseLookup = SkyDiffuseLighting * ResolvedView.SkyLightColor.rgb;
  295. IndirectIrradiance += Luminance(DiffuseLookup);
  296. #endif
  297. Color *= MaterialAO;
  298. IndirectIrradiance *= MaterialAO;
  299. float ShadowPositionZ = 0;
  300. #if DIRECTIONAL_LIGHT_CSM && !MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  301. // CSM阴影.
  302. if (UseCSM())
  303. {
  304. half ShadowMap = MobileDirectionalLightCSM(MaterialParameters.ScreenPosition.xy, MaterialParameters.ScreenPosition.w, ShadowPositionZ);
  305. #if ALLOW_STATIC_LIGHTING
  306. Shadow = min(ShadowMap, Shadow);
  307. #else
  308. Shadow = ShadowMap;
  309. #endif
  310. }
  311. #endif /* DIRECTIONAL_LIGHT_CSM */
  312. // 距离场阴影.
  313. #if APPLY_DISTANCE_FIELD
  314. if (ShadowPositionZ == 0)
  315. {
  316. Shadow = Texture2DSample(MobileBasePass.ScreenSpaceShadowMaskTexture, MobileBasePass.ScreenSpaceShadowMaskSampler, SvPositionToBufferUV(SvPosition)).x;
  317. }
  318. #endif
  319. half NoL = max(0, dot(MaterialParameters.WorldNormal, MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz));
  320. half3 H = normalize(MaterialParameters.CameraVector + MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz);
  321. half NoH = max(0, dot(MaterialParameters.WorldNormal, H));
  322. // 平行光 + IBL
  323. #if FULLY_ROUGH
  324. Color += (Shadow * NoL) * MobileDirectionalLight.DirectionalLightColor.rgb * ShadingModelContext.DiffuseColor;
  325. #else
  326. FMobileDirectLighting Lighting = MobileIntegrateBxDF(ShadingModelContext, GBuffer, NoL, MaterialParameters.CameraVector, H, NoH);
  327. // MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.z保存了平行光的SpecularScale.
  328. Color += (Shadow) * MobileDirectionalLight.DirectionalLightColor.rgb * (Lighting.Diffuse + Lighting.Specular * MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.z);
  329. // 头发着色.
  330. #if !(MATERIAL_SINGLE_SHADINGMODEL && MATERIAL_SHADINGMODEL_HAIR)
  331. (......)
  332. #endif
  333. #endif /* FULLY_ROUGH */
  334. // 局部光源, 最多4个.
  335. #if MAX_DYNAMIC_POINT_LIGHTS > 0 && !MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  336. if(NumDynamicPointLights > 0)
  337. {
  338. #if SUPPORT_SPOTLIGHTS_SHADOW
  339. FPCFSamplerSettings Settings;
  340. Settings.ShadowDepthTexture = DynamicSpotLightShadowTexture;
  341. Settings.ShadowDepthTextureSampler = DynamicSpotLightShadowSampler;
  342. Settings.ShadowBufferSize = DynamicSpotLightShadowBufferSize;
  343. Settings.bSubsurface = false;
  344. Settings.bTreatMaxDepthUnshadowed = false;
  345. Settings.DensityMulConstant = 0;
  346. Settings.ProjectionDepthBiasParameters = 0;
  347. #endif
  348. AccumulateLightingOfDynamicPointLight(MaterialParameters, ...);
  349. if (MAX_DYNAMIC_POINT_LIGHTS > 1 && NumDynamicPointLights > 1)
  350. {
  351. AccumulateLightingOfDynamicPointLight(MaterialParameters, ...);
  352. if (MAX_DYNAMIC_POINT_LIGHTS > 2 && NumDynamicPointLights > 2)
  353. {
  354. AccumulateLightingOfDynamicPointLight(MaterialParameters, ...);
  355. if (MAX_DYNAMIC_POINT_LIGHTS > 3 && NumDynamicPointLights > 3)
  356. {
  357. AccumulateLightingOfDynamicPointLight(MaterialParameters, ...);
  358. }
  359. }
  360. }
  361. }
  362. #endif
  363. // 天空光.
  364. #if ENABLE_SKY_LIGHT
  365. #if MATERIAL_TWOSIDED && LQ_TEXTURE_LIGHTMAP
  366. if (NoL == 0)
  367. {
  368. #endif
  369. #if MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  370. ShadingModelContext.WaterDiffuseIndirectLuminance += SkyDiffuseLighting;
  371. #endif
  372. Color += SkyDiffuseLighting * half3(ResolvedView.SkyLightColor.rgb) * ShadingModelContext.DiffuseColor * MaterialAO;
  373. #if MATERIAL_TWOSIDED && LQ_TEXTURE_LIGHTMAP
  374. }
  375. #endif
  376. #endif
  377. #endif /* !MATERIAL_SHADINGMODEL_UNLIT */
  378. #if MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  379. (......)
  380. #endif // MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  381. #endif// DEFERRED_SHADING_PATH
  382. // 处理顶点雾.
  383. half4 VertexFog = half4(0, 0, 0, 1);
  384. #if USE_VERTEX_FOG
  385. #if PACK_INTERPOLANTS
  386. VertexFog = PackedInterpolants[0];
  387. #else
  388. VertexFog = BasePassInterpolants.VertexFog;
  389. #endif
  390. #endif
  391. // 自发光.
  392. half3 Emissive = GetMaterialEmissive(PixelMaterialInputs);
  393. #if MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT
  394. Emissive *= TopMaterialCoverage;
  395. #endif
  396. Color += Emissive;
  397. #if !MATERIAL_SHADINGMODEL_UNLIT && MOBILE_EMULATION
  398. Color = lerp(Color, ShadingModelContext.DiffuseColor, ResolvedView.UnlitViewmodeMask);
  399. #endif
  400. // 组合雾颜色到输出颜色.
  401. #if MATERIALBLENDING_ALPHACOMPOSITE || MATERIAL_SHADINGMODEL_SINGLELAYERWATER
  402. OutColor = half4(Color * VertexFog.a + VertexFog.rgb * ShadingModelContext.Opacity, ShadingModelContext.Opacity);
  403. #elif MATERIALBLENDING_ALPHAHOLDOUT
  404. // not implemented for holdout
  405. OutColor = half4(Color * VertexFog.a + VertexFog.rgb * ShadingModelContext.Opacity, ShadingModelContext.Opacity);
  406. #elif MATERIALBLENDING_TRANSLUCENT
  407. OutColor = half4(Color * VertexFog.a + VertexFog.rgb, ShadingModelContext.Opacity);
  408. #elif MATERIALBLENDING_ADDITIVE
  409. OutColor = half4(Color * (VertexFog.a * ShadingModelContext.Opacity.x), 0.0f);
  410. #elif MATERIALBLENDING_MODULATE
  411. half3 FoggedColor = lerp(half3(1, 1, 1), Color, VertexFog.aaa * VertexFog.aaa);
  412. OutColor = half4(FoggedColor, ShadingModelContext.Opacity);
  413. #else
  414. OutColor.rgb = Color * VertexFog.a + VertexFog.rgb;
  415. #if !MATERIAL_USE_ALPHA_TO_COVERAGE
  416. // Scene color alpha is not used yet so we set it to 1
  417. OutColor.a = 1.0;
  418. #if OUTPUT_MOBILE_HDR
  419. // Store depth in FP16 alpha. This depth value can be fetched during translucency or sampled in post-processing
  420. OutColor.a = SvPosition.z;
  421. #endif
  422. #else
  423. half MaterialOpacityMask = GetMaterialMaskInputRaw(PixelMaterialInputs);
  424. OutColor.a = GetMaterialMask(PixelMaterialInputs) / max(abs(ddx(MaterialOpacityMask)) + abs(ddy(MaterialOpacityMask)), 0.0001f) + 0.5f;
  425. #endif
  426. #endif
  427. #if !MATERIALBLENDING_MODULATE && USE_PREEXPOSURE
  428. OutColor.rgb *= ResolvedView.PreExposure;
  429. #endif
  430. #if MATERIAL_IS_SKY
  431. OutColor.rgb = min(OutColor.rgb, Max10BitsFloat.xxx * 0.5f);
  432. #endif
  433. #if USE_SCENE_DEPTH_AUX
  434. OutSceneDepthAux = SvPosition.z;
  435. #endif
  436. // 处理颜色的alpha.
  437. #if USE_EDITOR_COMPOSITING && (MOBILE_EMULATION)
  438. // Editor primitive depth testing
  439. OutColor.a = 1.0;
  440. #if MATERIALBLENDING_MASKED
  441. // some material might have an opacity value
  442. OutColor.a = GetMaterialMaskInputRaw(PixelMaterialInputs);
  443. #endif
  444. clip(OutColor.a - GetMaterialOpacityMaskClipValue());
  445. #else
  446. #if OUTPUT_GAMMA_SPACE
  447. OutColor.rgb = sqrt(OutColor.rgb);
  448. #endif
  449. #endif
  450. #if NUM_VIRTUALTEXTURE_SAMPLES || LIGHTMAP_VT_ENABLED
  451. FinalizeVirtualTextureFeedback(
  452. MaterialParameters.VirtualTextureFeedback,
  453. MaterialParameters.SvPosition,
  454. ShadingModelContext.Opacity,
  455. View.FrameNumber,
  456. View.VTFeedbackBuffer
  457. );
  458. #endif
  459. }

移动端的BasePassPS的处理过程比较复杂,步骤繁多,主要有:解压插值数据,获取并计算材质属性,计算并缓村GBuffer,处理或调整GBuffer数据,计算前向渲染分支的光照(平行光、局部光),计算距离场、CSM等阴影,计算天空光,处理静态光照、非直接光和IBL,计算雾效,以及处理水体、头发、薄层透明度等特殊着色模型。

由于标准16位浮点数(FP16)可表示的最小值是1.0224=5.96⋅10−81.0224=5.96⋅10−8,而后续的光照计算涉及到粗糙度的4次方运算(1.0Roughness41.0Roughness4),为了防止除零错误,需要将粗糙度截取到0.0156250.015625(0.0156254=5.96⋅10−80.0156254=5.96⋅10−8)。

GBuffer.Roughness = max(0.015625, GetMaterialRoughness(PixelMaterialInputs));

这也警示我们在开发移动端的渲染特性时,需要格外注意和把控数据精度,否则在低端设备经常由于数据精度不足而出现各种奇葩的画面异常。

虽然上面的代码较多,但由很多宏控制着,实际渲染单个材质所需的代码可能只是其中的很小的一个子集。比如说,默认支持4个局部光源,但如果在工程配置(下图)中可以设为2或更少,则实际执行的光源指令少了很多。

如果是前向渲染分支,则GBuffer的很多处理将被忽略;如果是延迟渲染分支,则平行光、局部光源的计算将被忽略,由延迟渲染Pass的shader执行。

下面对重要接口EncodeGBuffer做剖析:

  1. void EncodeGBuffer(
  2. FGBufferData GBuffer,
  3. out float4 OutGBufferA,
  4. out float4 OutGBufferB,
  5. out float4 OutGBufferC,
  6. out float4 OutGBufferD,
  7. out float4 OutGBufferE,
  8. out float4 OutGBufferVelocity,
  9. float QuantizationBias = 0 // -0.5 to 0.5 random float. Used to bias quantization.
  10. )
  11. {
  12. if (GBuffer.ShadingModelID == SHADINGMODELID_UNLIT)
  13. {
  14. OutGBufferA = 0;
  15. SetGBufferForUnlit(OutGBufferB);
  16. OutGBufferC = 0;
  17. OutGBufferD = 0;
  18. OutGBufferE = 0;
  19. }
  20. else
  21. {
  22. // GBufferA: 八面体压缩后的法线, 预计算阴影因子, 逐物体数据.
  23. #if MOBILE_DEFERRED_SHADING
  24. OutGBufferA.rg = UnitVectorToOctahedron( normalize(GBuffer.WorldNormal) ) * 0.5f + 0.5f;
  25. OutGBufferA.b = GBuffer.PrecomputedShadowFactors.x;
  26. OutGBufferA.a = GBuffer.PerObjectGBufferData;
  27. #else
  28. (......)
  29. #endif
  30. // GBufferB: 金属度, 高光度, 粗糙度, 着色模型, 其它Mask.
  31. OutGBufferB.r = GBuffer.Metallic;
  32. OutGBufferB.g = GBuffer.Specular;
  33. OutGBufferB.b = GBuffer.Roughness;
  34. OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);
  35. // GBufferC: 基础色, AO或非直接光.
  36. OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor );
  37. #if ALLOW_STATIC_LIGHTING
  38. // No space for AO. Multiply IndirectIrradiance by AO instead of storing.
  39. OutGBufferC.a = EncodeIndirectIrradiance(GBuffer.IndirectIrradiance * GBuffer.GBufferAO) + QuantizationBias * (1.0 / 255.0);
  40. #else
  41. OutGBufferC.a = GBuffer.GBufferAO;
  42. #endif
  43. OutGBufferD = GBuffer.CustomData;
  44. OutGBufferE = GBuffer.PrecomputedShadowFactors;
  45. }
  46. #if WRITES_VELOCITY_TO_GBUFFER
  47. OutGBufferVelocity = GBuffer.Velocity;
  48. #else
  49. OutGBufferVelocity = 0;
  50. #endif
  51. }

在默认光照模型(DefaultLit)下,BasePass输出的结果有以下几种纹理:

12.3.3.3 MobileDeferredShading

移动端延迟光照的VS和PC端是一样的,都是DeferredLightVertexShaders.usf,但PS不一样,用的是MobileDeferredShading.usf。由于VS和PC一样,且没有特殊的操作,此处就忽略,如果有兴趣的同学可以看第五篇的小节5.5.3.1 DeferredLightVertexShader

下面直接分析PS代码:

  1. // Engine\Shaders\Private\MobileDeferredShading.usf
  2. (......)
  3. // 移动端光源数据结构体.
  4. struct FMobileLightData
  5. {
  6. float3 Position;
  7. float InvRadius;
  8. float3 Color;
  9. float FalloffExponent;
  10. float3 Direction;
  11. float2 SpotAngles;
  12. float SourceRadius;
  13. float SpecularScale;
  14. bool bInverseSquared;
  15. bool bSpotLight;
  16. };
  17. // 获取GBuffer数据.
  18. void FetchGBuffer(in float2 UV, out float4 GBufferA, out float4 GBufferB, out float4 GBufferC, out float4 GBufferD, out float SceneDepth)
  19. {
  20. // Vulkan的子pass获取数据.
  21. #if VULKAN_PROFILE
  22. GBufferA = VulkanSubpassFetch1();
  23. GBufferB = VulkanSubpassFetch2();
  24. GBufferC = VulkanSubpassFetch3();
  25. GBufferD = 0;
  26. SceneDepth = ConvertFromDeviceZ(VulkanSubpassDepthFetch());
  27. // Metal的子pass获取数据.
  28. #elif METAL_PROFILE
  29. GBufferA = SubpassFetchRGBA_1();
  30. GBufferB = SubpassFetchRGBA_2();
  31. GBufferC = SubpassFetchRGBA_3();
  32. GBufferD = 0;
  33. SceneDepth = ConvertFromDeviceZ(SubpassFetchR_4());
  34. // 其它平台(DX, OpenGL)的子pass获取数据.
  35. #else
  36. GBufferA = Texture2DSampleLevel(MobileSceneTextures.GBufferATexture, MobileSceneTextures.GBufferATextureSampler, UV, 0);
  37. GBufferB = Texture2DSampleLevel(MobileSceneTextures.GBufferBTexture, MobileSceneTextures.GBufferBTextureSampler, UV, 0);
  38. GBufferC = Texture2DSampleLevel(MobileSceneTextures.GBufferCTexture, MobileSceneTextures.GBufferCTextureSampler, UV, 0);
  39. GBufferD = 0;
  40. SceneDepth = ConvertFromDeviceZ(Texture2DSampleLevel(MobileSceneTextures.SceneDepthTexture, MobileSceneTextures.SceneDepthTextureSampler, UV, 0).r);
  41. #endif
  42. }
  43. // 解压GBuffer数据.
  44. FGBufferData DecodeGBufferMobile(
  45. float4 InGBufferA,
  46. float4 InGBufferB,
  47. float4 InGBufferC,
  48. float4 InGBufferD)
  49. {
  50. FGBufferData GBuffer;
  51. GBuffer.WorldNormal = OctahedronToUnitVector( InGBufferA.xy * 2.0f - 1.0f );
  52. GBuffer.PrecomputedShadowFactors = InGBufferA.z;
  53. GBuffer.PerObjectGBufferData = InGBufferA.a;
  54. GBuffer.Metallic = InGBufferB.r;
  55. GBuffer.Specular = InGBufferB.g;
  56. GBuffer.Roughness = max(0.015625, InGBufferB.b);
  57. // Note: must match GetShadingModelId standalone function logic
  58. // Also Note: SimpleElementPixelShader directly sets SV_Target2 ( GBufferB ) to indicate unlit.
  59. // An update there will be required if this layout changes.
  60. GBuffer.ShadingModelID = DecodeShadingModelId(InGBufferB.a);
  61. GBuffer.SelectiveOutputMask = DecodeSelectiveOutputMask(InGBufferB.a);
  62. GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb);
  63. #if ALLOW_STATIC_LIGHTING
  64. GBuffer.GBufferAO = 1;
  65. GBuffer.IndirectIrradiance = DecodeIndirectIrradiance(InGBufferC.a);
  66. #else
  67. GBuffer.GBufferAO = InGBufferC.a;
  68. GBuffer.IndirectIrradiance = 1;
  69. #endif
  70. GBuffer.CustomData = HasCustomGBufferData(GBuffer.ShadingModelID) ? InGBufferD : 0;
  71. return GBuffer;
  72. }
  73. // 直接光照.
  74. half3 GetDirectLighting(
  75. FMobileLightData LightData,
  76. FMobileShadingModelContext ShadingModelContext,
  77. FGBufferData GBuffer,
  78. float3 WorldPosition,
  79. half3 CameraVector)
  80. {
  81. half3 DirectLighting = 0;
  82. float3 ToLight = LightData.Position - WorldPosition;
  83. float DistanceSqr = dot(ToLight, ToLight);
  84. float3 L = ToLight * rsqrt(DistanceSqr);
  85. // 光源衰减.
  86. float Attenuation = 0.0;
  87. if (LightData.bInverseSquared)
  88. {
  89. // Sphere falloff (technically just 1/d2 but this avoids inf)
  90. Attenuation = 1.0f / (DistanceSqr + 1.0f);
  91. Attenuation *= Square(saturate(1 - Square(DistanceSqr * Square(LightData.InvRadius))));
  92. }
  93. else
  94. {
  95. Attenuation = RadialAttenuation(ToLight * LightData.InvRadius, LightData.FalloffExponent);
  96. }
  97. // 聚光灯衰减.
  98. if (LightData.bSpotLight)
  99. {
  100. Attenuation *= SpotAttenuation(L, -LightData.Direction, LightData.SpotAngles);
  101. }
  102. // 如果衰减不为0, 则计算直接光照.
  103. if (Attenuation > 0.0)
  104. {
  105. half3 H = normalize(CameraVector + L);
  106. half NoL = max(0.0, dot(GBuffer.WorldNormal, L));
  107. half NoH = max(0.0, dot(GBuffer.WorldNormal, H));
  108. FMobileDirectLighting Lighting = MobileIntegrateBxDF(ShadingModelContext, GBuffer, NoL, CameraVector, H, NoH);
  109. DirectLighting = (Lighting.Diffuse + Lighting.Specular * LightData.SpecularScale) * (LightData.Color * (1.0 / PI) * Attenuation);
  110. }
  111. return DirectLighting;
  112. }
  113. // 光照函数.
  114. half ComputeLightFunctionMultiplier(float3 WorldPosition);
  115. // 使用光网格添加局部光照, 不支持动态阴影, 因为需要逐光源阴影图.
  116. half3 GetLightGridLocalLighting(const FCulledLightsGridData InLightGridData, ...);
  117. // 平行光的PS主入口.
  118. void MobileDirectLightPS(
  119. noperspective float4 UVAndScreenPos : TEXCOORD0,
  120. float4 SvPosition : SV_POSITION,
  121. out half4 OutColor : SV_Target0)
  122. {
  123. // 恢复(读取)GBuffer数据.
  124. FGBufferData GBuffer = (FGBufferData)0;
  125. float SceneDepth = 0;
  126. {
  127. float4 GBufferA = 0;
  128. float4 GBufferB = 0;
  129. float4 GBufferC = 0;
  130. float4 GBufferD = 0;
  131. FetchGBuffer(UVAndScreenPos.xy, GBufferA, GBufferB, GBufferC, GBufferD, SceneDepth);
  132. GBuffer = DecodeGBufferMobile(GBufferA, GBufferB, GBufferC, GBufferD);
  133. }
  134. // 计算基础向量.
  135. float2 ScreenPos = UVAndScreenPos.zw;
  136. float3 WorldPosition = mul(float4(ScreenPos * SceneDepth, SceneDepth, 1), View.ScreenToWorld).xyz;
  137. half3 CameraVector = normalize(View.WorldCameraOrigin - WorldPosition);
  138. half NoV = max(0, dot(GBuffer.WorldNormal, CameraVector));
  139. half3 ReflectionVector = GBuffer.WorldNormal * (NoV * 2.0) - CameraVector;
  140. half3 Color = 0;
  141. // Check movable light param to determine if we should be using precomputed shadows
  142. half Shadow = LightFunctionParameters2.z > 0.0f ? 1.0f : GBuffer.PrecomputedShadowFactors.r;
  143. // CSM阴影.
  144. #if APPLY_CSM
  145. float ShadowPositionZ = 0;
  146. float4 ScreenPosition = SvPositionToScreenPosition(float4(SvPosition.xyz,SceneDepth));
  147. float ShadowMap = MobileDirectionalLightCSM(ScreenPosition.xy, SceneDepth, ShadowPositionZ);
  148. Shadow = min(ShadowMap, Shadow);
  149. #endif
  150. // 着色模型上下文.
  151. FMobileShadingModelContext ShadingModelContext = (FMobileShadingModelContext)0;
  152. {
  153. half DielectricSpecular = 0.08 * GBuffer.Specular;
  154. ShadingModelContext.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic; // 1 mad
  155. ShadingModelContext.SpecularColor = (DielectricSpecular - DielectricSpecular * GBuffer.Metallic) + GBuffer.BaseColor * GBuffer.Metallic; // 2 mad
  156. // 计算环境的BRDF.
  157. ShadingModelContext.SpecularColor = GetEnvBRDF(ShadingModelContext.SpecularColor, GBuffer.Roughness, NoV);
  158. }
  159. // 局部光源.
  160. float2 LocalPosition = SvPosition.xy - View.ViewRectMin.xy;
  161. uint GridIndex = ComputeLightGridCellIndex(uint2(LocalPosition.x, LocalPosition.y), SceneDepth);
  162. // 分簇光源
  163. #if USE_CLUSTERED
  164. {
  165. const uint EyeIndex = 0;
  166. const FCulledLightsGridData CulledLightGridData = GetCulledLightsGrid(GridIndex, EyeIndex);
  167. Color += GetLightGridLocalLighting(CulledLightGridData, ShadingModelContext, GBuffer, WorldPosition, CameraVector, EyeIndex, 0);
  168. }
  169. #endif
  170. // 计算平行光.
  171. half NoL = max(0, dot(GBuffer.WorldNormal, MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz));
  172. half3 H = normalize(CameraVector + MobileDirectionalLight.DirectionalLightDirectionAndShadowTransition.xyz);
  173. half NoH = max(0, dot(GBuffer.WorldNormal, H));
  174. FMobileDirectLighting Lighting;
  175. Lighting.Specular = ShadingModelContext.SpecularColor * CalcSpecular(GBuffer.Roughness, NoH);
  176. Lighting.Diffuse = ShadingModelContext.DiffuseColor;
  177. Color += (Shadow * NoL) * MobileDirectionalLight.DirectionalLightColor.rgb * (Lighting.Diffuse + Lighting.Specular * MobileDirectionalLight.DirectionalLightDistanceFadeMADAndSpecularScale.z);
  178. // 处理反射(IBL, 反射捕捉器).
  179. #if APPLY_REFLECTION
  180. uint NumCulledEntryIndex = (ForwardLightData.NumGridCells + GridIndex) * NUM_CULLED_LIGHTS_GRID_STRIDE;
  181. uint NumLocalReflectionCaptures = min(ForwardLightData.NumCulledLightsGrid[NumCulledEntryIndex + 0], ForwardLightData.NumReflectionCaptures);
  182. uint DataStartIndex = ForwardLightData.NumCulledLightsGrid[NumCulledEntryIndex + 1];
  183. float3 SpecularIBL = CompositeReflectionCapturesAndSkylight(
  184. 1.0f,
  185. WorldPosition,
  186. ReflectionVector,//RayDirection,
  187. GBuffer.Roughness,
  188. GBuffer.IndirectIrradiance,
  189. 1.0f,
  190. 0.0f,
  191. NumLocalReflectionCaptures,
  192. DataStartIndex,
  193. 0,
  194. true);
  195. Color += SpecularIBL * ShadingModelContext.SpecularColor;
  196. #elif APPLY_SKY_REFLECTION
  197. float SkyAverageBrightness = 1.0f;
  198. float3 SpecularIBL = GetSkyLightReflection(ReflectionVector, GBuffer.Roughness, SkyAverageBrightness);
  199. SpecularIBL *= ComputeMixingWeight(GBuffer.IndirectIrradiance, SkyAverageBrightness, GBuffer.Roughness);
  200. Color += SpecularIBL * ShadingModelContext.SpecularColor;
  201. #endif
  202. // 天空光漫反射.
  203. half3 SkyDiffuseLighting = GetSkySHDiffuseSimple(GBuffer.WorldNormal);
  204. Color+= SkyDiffuseLighting * half3(View.SkyLightColor.rgb) * ShadingModelContext.DiffuseColor * GBuffer.GBufferAO;
  205. half LightAttenuation = ComputeLightFunctionMultiplier(WorldPosition);
  206. #if USE_PREEXPOSURE
  207. // MobileHDR applies PreExposure in tonemapper
  208. LightAttenuation *= View.PreExposure;
  209. #endif
  210. OutColor.rgb = Color.rgb * LightAttenuation;
  211. OutColor.a = 1;
  212. }
  213. // 局部光源的PS主入口.
  214. void MobileRadialLightPS(
  215. float4 InScreenPosition : TEXCOORD0,
  216. float4 SVPos : SV_POSITION,
  217. out half4 OutColor : SV_Target0
  218. )
  219. {
  220. FGBufferData GBuffer = (FGBufferData)0;
  221. float SceneDepth = 0;
  222. {
  223. float2 ScreenUV = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
  224. float4 GBufferA = 0;
  225. float4 GBufferB = 0;
  226. float4 GBufferC = 0;
  227. float4 GBufferD = 0;
  228. FetchGBuffer(ScreenUV, GBufferA, GBufferB, GBufferC, GBufferD, SceneDepth);
  229. GBuffer = DecodeGBufferMobile(GBufferA, GBufferB, GBufferC, GBufferD);
  230. }
  231. // With a perspective projection, the clip space position is NDC * Clip.w
  232. // With an orthographic projection, clip space is the same as NDC
  233. float2 ClipPosition = InScreenPosition.xy / InScreenPosition.w * (View.ViewToClip[3][3] < 1.0f ? SceneDepth : 1.0f);
  234. float3 WorldPosition = mul(float4(ClipPosition, SceneDepth, 1), View.ScreenToWorld).xyz;
  235. half3 CameraVector = normalize(View.WorldCameraOrigin - WorldPosition);
  236. half NoV = max(0, dot(GBuffer.WorldNormal, CameraVector));
  237. // 组装光源数据结构体.
  238. FMobileLightData LightData = (FMobileLightData)0;
  239. {
  240. LightData.Position = DeferredLightUniforms.Position;
  241. LightData.InvRadius = DeferredLightUniforms.InvRadius;
  242. LightData.Color = DeferredLightUniforms.Color;
  243. LightData.FalloffExponent = DeferredLightUniforms.FalloffExponent;
  244. LightData.Direction = DeferredLightUniforms.Direction;
  245. LightData.SpotAngles = DeferredLightUniforms.SpotAngles;
  246. LightData.SpecularScale = 1.0;
  247. LightData.bInverseSquared = INVERSE_SQUARED_FALLOFF;
  248. LightData.bSpotLight = IS_SPOT_LIGHT;
  249. }
  250. FMobileShadingModelContext ShadingModelContext = (FMobileShadingModelContext)0;
  251. {
  252. half DielectricSpecular = 0.08 * GBuffer.Specular;
  253. ShadingModelContext.DiffuseColor = GBuffer.BaseColor - GBuffer.BaseColor * GBuffer.Metallic; // 1 mad
  254. ShadingModelContext.SpecularColor = (DielectricSpecular - DielectricSpecular * GBuffer.Metallic) + GBuffer.BaseColor * GBuffer.Metallic; // 2 mad
  255. // 计算环境BRDF.
  256. ShadingModelContext.SpecularColor = GetEnvBRDF(ShadingModelContext.SpecularColor, GBuffer.Roughness, NoV);
  257. }
  258. // 计算直接光.
  259. half3 Color = GetDirectLighting(LightData, ShadingModelContext, GBuffer, WorldPosition, CameraVector);
  260. // IES, 光照函数.
  261. half LightAttenuation = ComputeLightProfileMultiplier(WorldPosition, DeferredLightUniforms.Position, -DeferredLightUniforms.Direction, DeferredLightUniforms.Tangent);
  262. LightAttenuation*= ComputeLightFunctionMultiplier(WorldPosition);
  263. #if USE_PREEXPOSURE
  264. // MobileHDR applies PreExposure in tonemapper
  265. LightAttenuation*= View.PreExposure;
  266. #endif
  267. OutColor.rgb = Color * LightAttenuation;
  268. OutColor.a = 1;
  269. }

以上可知,平行光和局部光源的PS是不同的入口,主要是因为两者的区别较大,平行光直接在主入口计算光照,附带计算了反射(IBL、捕捉器)、天空光漫反射;而局部光源会构建一个光源结构体,进入直接光计算函数,最后处理局部光源特有的IES和光照函数。

另外,获取GBuffer时,采用了SubPass特有的读取模式,不同的着色平台有所不同:

  1. // Vulkan
  2. [[vk::input_attachment_index(1)]]
  3. SubpassInput<float4> GENERATED_SubpassFetchAttachment0;
  4. #define VulkanSubpassFetch0() GENERATED_SubpassFetchAttachment0.SubpassLoad()
  5. // Metal
  6. Texture2D<float4> gl_LastFragDataRGBA_1;
  7. #define SubpassFetchRGBA_1() gl_LastFragDataRGBA_1.Load(uint3(0, 0, 0), 0)
  8. // DX / OpenGL
  9. Texture2DSampleLevel(GBufferATexture, GBufferATextureSampler, UV, 0);

团队招员

博主所在的团队正在用UE4开发一种全新的沉浸式体验产品,急需各路豪士一同加入,共谋宏图大业。目前急招以下职位:

  • UE逻辑开发。
  • UE引擎程序。
  • UE图形渲染。
  • TA(技术向、美术向)。

要求:对技术有热情,扎实的技术基础,良好的沟通和合作能力,有UE使用经验或移动端开发经验者更佳。

有意向或想了解更多的请添加博主微信:81079389(注明博客园求职),或者发简历到博主邮箱:81079389#qq.com(#换成@)。

静待各路英雄豪杰相会。

特别说明

  • Part 1结束,Part 2的内容有:
    • 移动端渲染技术
    • 移动端优化技巧
  • 感谢所有参考文献的作者,部分图片来自参考文献和网络,侵删。
  • 本系列文章为笔者原创,只发表在博客园上,欢迎分享本文链接,但未经同意,不允许转载
  • 系列文章,未完待续,完整目录请戳内容纲目
  • 系列文章,未完待续,完整目录请戳内容纲目
  • 系列文章,未完待续,完整目录请戳内容纲目

参考文献

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/108599
推荐阅读
  

闽ICP备14008679号