BlueRoseNote/03-UnrealEngine/性能优化/UE5优化方法与实践笔记.md

487 lines
40 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: UE5优化方法与实践笔记
date: 2022-09-26 10:06:37
excerpt:
tags:
rating: ⭐⭐
---
## 前言
- 视频推荐:
- https://www.youtube.com/watch?v=ZRaeiVAM4LI
- 其他:
- https://zhuanlan.zhihu.com/p/629225258
- Tomlooman的优化建议:https://www.tomlooman.com/wp-content/uploads/2022/11/Unreal-Engine-Game-Optimization-on-a-Budget.pdf
GPU Visualizer工具显示命令 **ProfileGPU**
CPU优化方法:https://www.unrealengine.com/en-US/blog/how-to-improve-game-thread-cpu-performance
### Sample Workflow
1. Run `Stat unit`
- shows the render thread is taking 50 ms.
2. Then run `stat scenerendering`
- shows that 25 ms is in the RenderViewFamily
- leaving us with 25 ms that the render commands are taking up
3. Finally, run `stat sceneupdate`
- we see that there is 25 ms in AddLight RT
- we see that it is being called 10 times a frame
4. We then need to go look at who is calling AddLight via a break point. And see why adding a specific light or lights is so slow. Usually it is the case that a specific light being added in a way that is doing more work than actually needs to be done. (e.g. attaching / reattaching it)
# 优化前需做
- r.vsync 0
- t.maxfps 0
- SmoothFrameRate=False (Project Settings)
- Lighting Built & MapCheck Errors fixed.
- Packaged Game build
- 使用Standalone进行优化。
## 优化笔记
r.Lumen.DiffuseIndirect.MeshSDF.RadiusThreshold
让物体包围球的半径大于上述两个决定的数值的时候才参与mesh sdf的软追踪。
某些情况下,创建移除 WPO/PDO 的材质 LOD 可能不切实际,但这些转换的最终效果在远处很小。用于`r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange`设置一个距离(以厘米为单位),超过该距离将忽略这些材质的缓存失效。
为了在这些情况下更好地优化,可以使用启用可选的**单独静态缓存**`r.Shadow.Virtual.Cache.StaticSeparate 1`模式。此模式将物理页面池的大小加倍,以便可以将给定页面中的静态几何与动态几何分开缓存。即使动态几何移动了,也不需要重新绘制静态几何。
### Nanite
r.Nanite.AllowWPODistanceDisable
### 非纳米变形和树叶
可以使用骨骼动画变形的几何体,或使用世界位置偏移或像素深度偏移的材质总是使缓存页面每帧无效。根据定义,这些案例也必须是非 Nanite——这更昂贵——因此确保谨慎使用这些功能并控制边界是极其重要的。
在某些情况下,例如草地,有时是树叶,仅使用[接触阴影](https://docs.unrealengine.com/5.0/en-US/contact-shadows-in-unreal-engine)就足以替代高分辨率阴影贴图。在前景中需要高细节阴影的情况下,请考虑以下内容以帮助降低性能成本:
- 非 Nanite 对象仍然遵守常规阴影 CPU 剔除设置,例如`r.Shadow.RadiusThreshold`. 使用这些来帮助控制将这些对象渲染到虚拟阴影贴图中的成本。
- 在有很多树叶的场景中,强烈建议使用 禁用粗糙页面中的非 Nanite 对象`r.Shadow.Virtual.NonNanite.IncludeInCoarsePages 0`。或者,考虑在不需要时 [完全禁用粗糙页面。](https://docs.unrealengine.com/5.0/en-US/virtual-shadow-maps-in-unreal-engine#coarsepages)
- 在效果不再明显的距离使用网格 LOD 切换到不使用 WPO/PDO 的材料。在某些情况下,可以关闭远处这些对象的动态阴影投射,并完全依赖屏幕空间接触阴影。
对于平行光,还有其他可用选项:
- 距离场阴影接管非 Nanite 几何体,超出由光的级联阴影贴图部分设置的**动态阴影距离可移动光距离。**为远处的非 Nanite 切换到距离场阴影可以显着提高性能,因为该几何体不具有 Nanite 提供的细粒度 LOD 缩放。
- 在某些情况下,创建移除 WPO/PDO 的材质 LOD 可能不切实际,但这些转换的最终效果在远处很小。用于`r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange`设置一个距离(以厘米为单位),超过该距离将忽略这些材质的缓存失效。
如果运动很明显,这可能会导致阴影“模糊”,并且对象会错误地自我形成阴影,但通常这些伪影是性能和便利性的合理折衷。
### 普通植被
普通植被最快最有效的优化方式是做好LOD减少culling distance.除此之外,还有两个因素对性能也有一定的影响。
- foliage actor的范围
foliage actor的默认tile的大小是256如果一个tile下有大量的植被而且植被密度非常浓密的时候这里不仅会引起分区流送引起cluster tree构建的卡顿也会导致gpu下prepass和basspass消耗过高因为默认的剔除盒子太大导致prepass和basspass存在大量的vs的overdraw.
- cluster tree遮挡剔除盒子大小
cluste tree的剔除盒大小决定了这一块instance是否绘制过大的盒子会引起prepass和basspass的vs阶段的overdraw过小的盒子导致存在大量的遮挡剔除查询导致render线程耗时过高而且也会产生过多的drawcall。引擎对于cluster tree的控制参数都是全局的有一些component适用也一些则不合适。过密的component须要提高盒子的粒度过于稀疏而且面数不高的可以减少盒子数。因为如果植被特别影响性能的时候可以修改引擎支持让这些参数跟着component走。引起cluster tree盒子的因素有点多这里主要聊三个。
foliage.MaxOcclusionQueriesPerComponent
控制每个component的最多盒子数
foliage.MinOcclusionQueriesPerComponent
控制每个component的最少盒子数
foliage.MinInstancesPerOcclusionQuery
控制每个盒子最小包含的实例数
如果这个盒子粒度控制好在植被比较多的情况下可以在prepass和basspass省下2MS的预算。
### 贴图优化
具体参看Managing the Texture Streaming Poolhttps://www.youtube.com/watch?v=uk3W8Zhahdg
大致步骤:
- 使用Tools - Audit - Statistics查看贴图占用显存以及场景中的使用数量。
- 通过ViewMode - OptimizationViewModes - RequiredTextureResolution 查看贴图在场景中所需要的占用比例是否合适。
- 使用批量编辑功能修改Texture资产的MaximumTextureSize到指定Mipmap层级的分辨率即可。
另一个种方式就是使用SVT。
## 游戏的优化指标
目标性能30帧 约30ms
2000-3000是合理drawcall次数5000较高10000有问题
### 优化建议
1. 远景或者不太容易见到的灯光不产生阴影
2. Spotlight不要超大开角可以降低阴影分辨率衰减半径不要设置过大只需照亮需要照亮的主题即可如果不投射阴影可以改成点光源。
3. 灯光需要减少级联阴影的使用量,在远景处尽可能得使用距离场阴影
1. 方向光降低DynamicShadowDistanceMoveableLightNum Dynamic Shadow Cascades的值。适当调整DistanceFieldShadowDistance与DistanceFieldTraceDistance。
2. 其他光:降低**Max Draw Distance**,减少渲染范围;控制**Attenuation Radius**、**Cone Angle**减少叠加。具体可以使用**ViewModel - Optimization ViewModes - Light Complexity**来进行检查。
3. 其他光降低ShadowResolutionScale并且调整亮度让玩家看不出阴影质量较低。
4. 对于**非Nanite物体**的阴影,可以使用**r.Shadow.RadiusThreshold**来减少投射阴影的物体数量。
5. VSM
1. 局部光16k8级Mipmap
2. 方向光16k 17级Clipmap
3. 纹理池上限:`r.Shadow.Virtual.MaxPhysicalPages (4096)`
4. Lod精度偏移
1. `r.Shadow.Virtual.ResolutionBiasDirectional`
2. `r.Shadow.Virtual.ResolutionBiasLocal`
5. 减少SMRT的采样次数来提高软阴影的性能。
4. Lumen
1. 距离场的设置![[Lumen_StaticMeshDistanceFieldSettings.png|1000]]
5. Nanite
1. UE5.1针对树等植被采用了直接的建模式树叶而非之前的Mased+卡片的方式实现此时再配合StaticMesh中NaniteSettings的**Preserve Area**。拉远了树木上的树叶就不会消失了。
1. <iframe height=450 width=800 src="https://cdn2.unrealengine.com/p-area-off-opt-0c8757e8275f.mp4" frameborder=0 allowfullscreen> </iframe>
2. <iframe height=450 width=800 src="https://cdn2.unrealengine.com/p-area-on-opt-1s-89bb46d92142.mp4" frameborder=0 allowfullscreen> </iframe>
2. UE5.1支持了WorldPositionOffset但这个效果在远距离下并不明显需要根据距离优化掉。可以通过**View Mode - Optimization View Mode - Evaluate World Position Offset**来查看。
6. 控制RayTracing渲染功能的距离与范围
1. 按角度与距离剔除
1. r.Raytracing.Culling 1
2. r.Raytracing.Culling.Radius 10000 100米
3. r.Raytracing.Culling.Radius 1 5度
2. 设置光追组
1. StaticMeshComponent - RayTracing - Advanced - Raytracing Group Id /Culling Priority
7. 针对粒子特效可以调节Cut Off的裁剪系数Spawn TimeSpawn Rate等等可以在保证效果的同时来降低粒子数量的参数
1. https://www.youtube.com/watch?v=_T-BTiMF7XA
2. https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/Niagara/EmitterReference/RenderModules/
8. 尽可能使用贴花
9. 模型优化
1. 给模型生成LOD以及HLOD
1. Level Of Detail Coloration->Mesh LODs(可以**用红绿蓝三种颜色来**检验不同屏幕尺寸的LOD变化
2. 对于远处会闪烁的模型需要关闭LOD的`自动计算LOD距离`并且手动制作LOD。
2. 模型转换为Nanite后可以使用Nanite Tools来检查Nanite转换是否正确
3. 如果是**大量OverDraw Nanite模型**,可以使用**UE内置建模工具**的**Merge功能**将模型进行合并。
10. 使用剔除功能减少渲染的图元数
1. 使用`Stat InitViews`检查当前渲染图元数,以及之后优化完之后的图元数。**View Visibility**、**Occlusion Cull**为剔除消耗,**Processed**、**Frustum Culled**、**Occluded**剔除前后图元变化。
2. 给模型的LOD选项中设置**MinDrawDistance、DesiredmaxDrawDistance、CurrentMaxDrawDistance让模型在较远处被剔除**,适合一些细小的装饰类模型。
3. 使用剔除用Volumn。
11. 材质、蓝图等等,如果可以创建实例就使用实例
12. 综合优化视频
1. Adjusting Your Content to Perform on Target Hardware https://www.youtube.com/watch?v=Ln8PCZfO18Y
13. 贴图优化
1. 调整贴图的LOD Bias
2. 使用rdTexTools 插件优化贴图
14. 使用MergeActor工具将模型转化成Instance或者将细碎的模型组合成一个模型。
15. 调整材质减少材质复杂度以及使用的材质指令量。EPIC给出的建议300左右正常、**500+优化**、**1000+尽量减少**。
16. HLOD 估计适合组合拼装的房子 https://www.youtube.com/watch?v=WhcxGbKWdbI
1. 知乎介绍文章https://zhuanlan.zhihu.com/p/77509062
2. HLOD需要在世界设置中的LODSystem中打开。
17. 场景模型剔除 推荐视频https://www.youtube.com/watch?v=6WtE3CoFMXU
1. Show-Visualize-Advanced-CameraFrustums可以查看摄像机范围外的物体与遮挡物体。
2. Volumn
1. CullDistanceVolumn 根据对象距摄像机的距离及其尺寸对对象进行剔除即不绘制到屏幕上。当对象小到可被视为不重要时即可不绘制对象从而优化场景。尺寸是按照边界框的最长边计算的而剔除距离是根据与该尺寸最接近的距离计算的。
2. HierarchicalLODVolumn
3. MeshMergeCullingVolumn与HLOD有关
4. PrecomputedVisibilityVolumn这类体积会保存Actor的可视性以了解它们在场景中的位置。应仅将这些体积放置在玩家可以到达的区域。视频是把玩家可以到的地方直接放满了
3. Debug方法
1. FreezeRenderingFoliage.Freeze、FX.FreezeGPUSimulation、FX.FreezeParticleSimulation
2. r.VisualizeOccludedPrimitives1
3. ToggleDebugCamera
18. UE5 OpenWorld WorldComposition Datalayer 里面有附带大世界的HLOD 以及Nanite生成方法以及对应的CommanLethttps://www.youtube.com/watch?v=ZxJ5DG8Ytog
# 场景
在蓝图中放置多个&多层StaticMeshComponent结构在移动时会爆卡。可以在子Component中勾选***Use Attach Parent Bound***即可避免多层级的BoundingBox更新而导致卡顿。
![[UE5 MergeActor使用笔记]]
### DLSS与动态分辨率
DLSS 在5.03上没有效果动态分辨率不支持PC。
https://docs.unrealengine.com/5.0/en-US/dynamic-resolution-in-unreal-engine/
### 蓝图的方法
![900](https://docs.unrealengine.com/5.0/Images/designing-visuals-rendering-and-graphics/rendering-optimization/dynamic-resolution/DynamicResBlueprint.webp)
### c++的方法
```
GEngine->GetDynamicResolutionStatus()->SetEnabled(true);
```
将 _SetEnabled_ 设置为 **false** 可将其禁用。
>在实际启用或禁用动态分辨率时,游戏线程逻辑掌握最终程序控制权限,所以如果你是用蓝图在运行时启动它,这会优先于代码设置。要将游戏用户设置恢复到初始状态,请使用以下命令行:
```
GEngine->GameUserSettings->ApplyNonResolutionSettings();
```
### 命令行
你可以使用 **运算模式Operation Mode** 设置如何在游戏中覆盖和使用动态分辨率设置在游戏中覆盖它和使用它的方式。为了控制这种模式在项目所对应平台Xbox One、PlayStation 4等的平台配置描述或设备描述你可以使用下列控制台命令
```
r.DynamicRes.OperationMode
```
使用下列数值之一来设置运算模式如何针对项目的平台工作:
- **1** 是根据游戏用户设置状态在C++或蓝图中设置)启用动态分辨率。
- **2** 是无论游戏用户设置状态如何都启用动态分辨率。
启用动态分辨率后,下列控制台变量会设置屏幕百分比的最大值和最小值,以及在降低分辨率之前任何给定帧的最大预算。如果你不设置,这些变量都有默认值:
| 控制台变量 | 默认值 | 描述 |
| -------------------------------- | ------ | ------------------------------------------ |
| r.DynamicRes.MinScreenPercentage | 50 | 设置要使用的最小屏幕百分比。 |
| r.DynamicRes.MaxScreenPercentage | 100 | 设置用于分配渲染目标的最大主要屏幕百分比。 |
| r.DynamicRes.FrameTimeBudget | 33.3 | 设置帧预算(以毫秒为单位)。 |
你可以使用Unreal Engine中的"设备描述Device Profiles"窗口设置和管理配置文件。可以通过"文件File"菜单选择 **编辑Edit> Developer Tools开发者工具> Device Profiles设备描述** 来访问此窗口。
### 暂停和恢复动态分辨率
有时你可能需要为项目启用动态分辨率,但你又不想对主大厅之类的区域启用。动态分辨率可以随运作模式暂停和恢复。下列控制台变量可用于设置动态分辨率的运算模式:
```
r.DynamicRes.OperationMode
```
| 数值 | 描述 |
| ---- | ------------------------------------------ |
| 0 | 禁用(默认) |
| 1 | 根据GameUserSettings中使用的设置启用。 |
| 2 | 无论GameUserSettings中的设置如何都会启用。 |
## DLSS 与 FSR
UE5使用DLSS时需要关闭TAA并且调整[[ScreenPercentage与描边宽度问题解决]]
### DLSS
- 下载地址https://developer.nvidia.com/rtx/dlss/get-started#ue-version
### FSR
- 使用方法与参数详解https://zhuanlan.zhihu.com/p/437537928
- 下载地址https://gpuopen.com/learn/ue-fsr2/
# 内存数据查看
- stat llm查看**虚拟内存数据**。需要在启动方式里添加`-llm`才会有数据。
- MemoryInsight需要在启动方式里添加`-trace=memory`之后才能在UnrealInsight中查看。
# 材质优化 MIN/MAX DRAW DISTANCE
使用DistanceCullFade节点将过远的部分给Cull掉。
![[Unreal-Engine-Game-Optimization-on-a-Budget_MINMAX DRAW DISTANCE.png]]
# FREEZERENDERING
冻结渲染以此查看剔除情况。
- FreezeRendering + ; (semi-colon) to fly with DebugCamera
- Verify occlusion is working as expected
- pause (Freeze Game Thread)
# LIGHT CULLING (Stationary & Movable)
- Automatic ScreenSize culling not strict enough
- MinScreenRadiusForLights (0.03)
- Cull earlier case-by-case
- MaxDrawDistance
- MaxDistanceFadeRange
- Profiling
- Show > LightComplexity (Alt+7)
- Show > StationaryLightOverlap
- ToggleLight
# LEVEL STREAMING
关卡流调试方法
- Streaming Volumes vs. Manual Load/Unload
- Camera Location based (caution: third person view and cinematic shots)
- Cannot combine both on a specific sublevel, can mix within the game
- Profiling
- stat levels
- Loadtimes.dumpreport (+ loadtimes.reset)
- Unreal Insight
- UnrealInsight相关标签Look for level load & “GC” bookmarks
- UnrealInsight相关追踪内容loadtime,file categories
# Animation
## ANIMATION: FAST PATH
- Allow Fast Path by moving Computations out of AnimGraph (into EventGraph)
- Use WarnAboutBlueprintUsage to get warnings in AnimGraph
- Profiling
- stat anim
## ANIMATION: QUICK WINS
- **Update Rate Optimization (URO) for distant SkelMeshes**根据距离调整骨骼物体的更新频率该选项位于SkeletalMeshComponent。
- VisibilityBasedAnimTickOption (DefaultEngine.ini)
- OnlyTickPoseWhenRendered
- AlwaysTickPoseAndRefreshBones
-
- More Bools!
- bRenderAsStatic UE5.2不存在该选项。
- bPauseAnims该选项位于SkeletalMeshComponent。
- bNoSkeletonUpdate该选项位于SkeletalMeshComponent。
# DrawCall优化相关
使用**Stat DrawCount** 可以参看当前视角所有Pass所有DrawCall。
使用**Stat SceneRendering** 可以查看Mesh Draw Call。
# Shadow优化
原文:https://zhuanlan.zhihu.com/p/644620609
## 阴影
阴影主要聊一下平行光的两个解决方案,一个传统的[CSM](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=CSM&zhida_source=entity)一个是ue5配合nanite的[VSM](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=VSM&zhida_source=entity).传统的CSM效果最好没有噪点也没有BUG但是走了Nanite的话,CSM跟nanite配合是水士不服。UE5主推的VSM性能最好但是bug多有噪点但是又没办法跟距离场配合开启了VSM平行光的阴影只有VSM这一种真是又爱又恨。
### csm
CSM的实行网上教程很多这里主要聊优化手段。
- CSM缓存
可以通过r.Shadow.CSMCaching修改为1开启csm缓存.它会为静态和动态的对像分别启用一张深度图动态的深度图每帧都会重绘但是可以通过r.Shadow.CacheWPOPrimitives调整让WPO的也强制缓存。当两帧平行光没有变化而且两帧的CSM重叠超过一个阈值的时候CSM会复用上一帧的深度图这个阈值通过r.Shadow.CSMScrollingOverlapAreaThrottle调整默认是0.75。这个CSM缓存在低分辨率下的时候也个好优化对于高分辨率深度图失效的时候会重现申请申请这个RT是个不少的消耗这一点要注意。
- CSM层遮挡剔除
CSM有好多层如果前面有个建筑物很高完全档住人你的视线这里CSM远处的层级照样绘制深度这个是没有必要的可以通过遮挡剔除优化掉UE也做了这个优化。过能r.Shadow.OcclusionCullCascadedShadowMaps修改让不在视野范围的深度图不参与计算这个是个不错的优化最好开启。
- 调整屏幕占比factor
对于一些特别远的屏幕占比很低的物体没必要去生成深度可以通过修改r.Shadow.RadiusThreshold生成深度图的对像的半径阈值。
- 强制使用LOD
可以通过r.ForceLODShadow调用生成阴影深度的LOD这个是个大优化。
- 调整分辨率
这个优化是最好的直接把分辨率降低消耗直接变低。r.Shadow.MaxCSMResolution可以通过此参数调整csm每层的分辨率。
- 逐帧更新
因为csm有很多层对于远处的层没必要每帧都更新可以在csm缓存的代码里修改一下就可以做到远处的层级逐帧更新这个优化可以把CSM的性能提高了好多。
- 代理阴影
对于非nanite的网格体有强制使用LOD可以降低深度图对像的面数但是对于nanite前面的手段是不生效的。对于nanite来说走的是自已的[raster](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=raster&zhida_source=entity)。但是很多时候nanite光栅化出来的面数非常巨大引起nanite切换lod失效的因素非常多。所以对于那些远处又或者自投影细节比较少的网格体可以使用代理nanite阴影去解决。修改引擎支持proxyshadow component只设置影响深度pass其它pass都不绘制可以解决nanite在csm情况下LOD失效的时候减面优化性能。
上面的手段基本都是对于非nanite的有效对于nanite的上面的优化手段基本都是失效的nanite的网格全是gpu driver的有自已的渲染管线。
### vsm
VSM的原理就是一张大VT不同clipmap范围的VT页分辨率不一样同时动静分离在平行光不变的情况下静态页面复用只重绘动态的页面同时对于动态的页面缓存,非nanite的网格体也可以缓存,可以通过r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange调整数值让离自已一定范围内的非nanite才失效超过强行缓存。对于平行光不停在变情况的情况下所有页面都在失效这时可以关闭缓存r.shadow.vsm.cache反而可以提高一点性能节省一些显存因为不分动静页也不会分析哪些而页面失效。
目前VSM的问题比较多主要集中在两个方面bug多和噪点问题bug比如页面分析出错应该失效的页面不失效大范围WPO导致超出cluster bound box导致深度图被clamp在一个范围内同一个物体的深度在不同clipmap下的页面不一至 ,导致一些奇奇怪怪的阴影错乱问题, 噪点问题是算法决定了,smrt判断是否是软阴影的一个算法当一个像素发送射线过多会导致性能不行过低噪点严重。总体来说VSM还不能完全线上使用。
VSM配合nanite使用性能是非常好的下面讲一下VSM的一些优化手段主要是降分辨率。
- clipmap范围与页面密度
可以通过调用r.Shadow.Virtual.Clipmap.FistLevel和LastLevel来调clipmap的覆盖范围在不同的clipmap有同的页面覆盖面积。这里不仅可以优化性能也可以优化显存。
- 分辨率bias
在clipmap范围不变的情况下可以通过调整每个页面的覆盖范围通过调整r.Shadow.Virtual.ResolutionLodBiasDirectional来修改平行光的页面覆盖面积以达到降分辨率的处理
- 非nanite物体wpo强制缓存
调整r.Shadow.Virtual.Cache.MaxMaterialPositionInvalidationRange数值让在数值范围内的WPO强制使用缓存。
### 距离场阴影
距离场阴影的原理是把屏幕切tile在cs里找到每个tile对应有可能overlap的距离场然后每个tile每个像素追踪距离场就可以知道像素是否在阴影下。距离场阴影性能非常好。对于远处的物体阴影 都建议开距离场阴影。但是在VSM开启的情况下Nanite物体距离场是失效的。
对于点光的阴影可以通过在远距离时使用距离场阴影近距离影时使用shadowmap。
这里提示一点,在室内或者完全不受平行光的环境下,可以把平行光的阴影关掉,用距离场去代替。
### 接触阴影
接触阴影主要是屏幕空间下的通过hzb追踪得来的阴影他会有效果表现下比较差但是对于植被这种特别适合当场景大量存在存被的情况下可以开启接触阴影。
### 点光源
对于点光在GPU端分两种情况直接阴影和间接阴影。有三种阴影选择深度图接触阴影距离场接触阴影效果不好深度图精度最高支持动态物体但是很影响性能距离场性能最好但是不支持动态物体。对于远一点的可以用距离场代替随着距离变近切换到shadowmap,把光源交由逻辑层管理。
如果一个光源间接光照强度大于0此光源就会在[lumen scene](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=lumen+scene&zhida_source=entity)生效在lumen scene里不仅要计算光照也要计算阴影包括离屏阴影性能消耗不低无论使用shadowmap还是距离场这里交由脚本层管理过远的把间接光照强度调成0不参与lumen scene.
## [RVT](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=RVT&zhida_source=entity)
RVT的使用教程知乎非常多这里聊一下性能优化点。对于贴图混合层特别多的时候用RVT可以优化非常大的性能特别是地形但是RVT有时候也会突然消耗非常高可达3MS以上下面各个聊。
### produce与upload
当VT不存在或者MIP级别不匹配的时候引擎会从磁盘svt或者通过drawcall(rvt)生成对应的tile,这个过程叫produce,当生成后就会upload到gpu中更新indirect table和vt图集这个过种叫upload.
### produce
svt的produce不影响性能主要影响性的是地形的rvt的produce,因为地形的图层太多了这里会通过调用地形的材质drawcall生成对应的vt tile.这里性能消耗是不低的可以通过r.VT.MaxTilesProducedPerFrame调整数量注意这个参数是包括svt的tile数量。
### upload
一般upload不会产生GPU的消耗除非你的upload数量非常高。可以通过r.VT.MaxUploadsPerFrame调整每帧的upload数量编辑下通过r.VT.MaxUploadsPerFrameInEditor调整。
### VT池问题
当VT池不足的时候vt池会不停的切换mip以降显存这里GPU的VT消耗就会起来。可以通过命令r.VT.Residency.Show查看哪些VT池不足也可以通过r.VT.Residency.Notify当对应的池不足切mip的时候屏幕下会有打印warning找到对应的池在配置文件里增大显存。
### 地形
地形用RVT去减少采样是个非常大的优化另外RVT支持离线烘倍可以把低的mip离线烘倍减少切mip的时候的draw call发生以优化性能。通过RVT Volumn下的虚拟纹理构建把低的MIP烘倍出来。
## Nanite
nanite是UE5最稳定性能最好的新技术,对于实现原理没有想像中这么复杂可以去看一下知乎大佬丛越系列的文章讲的非常详细这里聊一下nanite遇到的性能问题。
### WPO
WPO非常影响性能当cluster剔除后光栅化后出来的面数也不低全开WPO会让vibility buffer生成性能翻倍这里可以使用世界位置偏移禁用距离调整超过距离后WPO禁用
### Mask
当有mask材质的时候没办法通过HZB做剔除只能先光栅化出来才能通过alphatest来决定去流overdraw非常严重对于nanite的材质最好禁用mask材质。使用mask材质性能非常低之前有测试过有mask性能直接减半。
### LOD减面失效问题
nanite的减面是要依赖平滑法线组去决定三角面是否能合并如果一个模形导出的时候没有平滑法线组UE会有warning这个要特别注意如果没有平滑法线组面数非常爆裂。
### Drawcall问题
nanite的bass pass是通过在屏幕空间切tile每个tile有要用到drawcall的材质CPU端是不知道此时GPU端哪些材质是否draw,所以nanite的draw call是在CPU端把加载进来的所有材质都发起drawcall每个drawcall是一个quad,没有在屏幕的时候在vs阶段就culling掉.正常来说一个culling掉的drawcall是基本不耗时的但是在一些老显卡20系,一个culling掉的drawcall也会有小耗时如果材质多的话这些culling掉的drawcall是个不小的消耗。
### 破面问题
引起破面的问题主要有下面几个因素,但是几个因素引起的表现不太一样,下面一个一个聊。
### 实例数
nanite在raster的时候会限制BVH node节点数量可以通过r.Nanite.MaxNodes去调整当实例数超过上限的时候场景每帧会随机掉面。
### cluster数
这里有两个类型cluster一个是candicate cluster一个是visiable cluster分别通过r.Nanite.MaxCandidateClusters和r.Nanite.MaxVisibleClusters当场景cluster数量足的时候场景会随机掉失cluster整个画面在闪烁。
对于实例数和cluster数的统计可以通过nanitestats去看到当前的数据可能数据非常接近上限的时候就要调整这里要注意一点当使用VSM的时候如果在VSM的view这些数量也超过了会导致深度生成每帧都不一样阴影会错乱.
### nanite流送池显存不足
nanite是通过每帧的feedback去让CPU发起流送的当流送池显存不够的时候就是流送低的cluster tree这个时候整个场景的面数非常低而且会破面。
### cluster id上限
这个没找到原因当上述所有条件都足够的情况下仍然会出现随机破面画面闪烁的问题。当场景存在大量的nanite而且每个nanite实例都是分离的最后退化一个cluster的时候这个cluster仍然很多这个时候也会随机破面目前怀疑是跟 cluster id上限的问题。主要出现在植被非常多的时候。
## Lumen
UE5默认lumen是把效果质量都是拉满的如果默认使用lumen的参数性能很炸。对于lumen反而bug比较少主要问题是在集中在性能问题。lumen对场景的效果的提升确实有的但是很多时候质量拉满和质量很低的情况下画面其实看不出有多少区别所以在我看来,lumen要用但不须要很高的质量基本于这个观点出现lumen就有很多地方可以优化如果当某个地方须要很高lumen质量的时候可以在拉一个新的post process volume提高lumen的质量。lumen整体分成三个部分lighting,probe gather,反射下面集中围绕这三个聊一下优能优化的点。另外想了解lumen的实现可以参考丛越大佬的文章写得非常详细。
### Lighting速度
lumen首先通地在lumen的direct ligting再加上radiosity的得到每一个patch的lighting最终combine lighting成indirect lighting.在接下的阶段final gather中放入probe去trace得到final lighting.这里有两个参数比较影响性能。
- direct lighting速度
可以通过修改r.LumenScene.DirectLighting.UpdateFactor参数和LumenSceneLightingUpdateSpeed两个参数控制更新速度前者是分母后者是分子这两个参数共同决定了当前帧分配更新图集大小。此图集通过PriorityHistogram中收集优先级的surface cache的tile放入到更新图集从而达到分帧更新。
- final gather速度
总体流程跟上面类似通过r.Lumen.ScreenProbeGather.RadianceCache.NumProbesToTraceBudget和LumenFinalGatherLightingUpdateSpeed参数总同决定了final gather阶段最大的trace probe数量在SelectMaxPriorityBucketCS中选择优化级大的而且小于上述两个参数共同决定的最大上限probe数去trace.
### 追踪距离
可以通过r.Lumen.TraceDistanceScale和LumenMaxTraceDistance去决定在lumen下距离场的最大追踪距离。这两个参数是影响整个lumen须要距离场cone trace追踪的所有pass,包括final gather的radian cache trace,radiosity的trace,reflect的trace,减少追踪距离可以优化性能但是会带来一些不想要的后果比如反射不到lumen scene阴影漏光导致场景有些地方会有从亮到暗的过程泄露天光等。这里可以在不同区域拉不同的pp盒子在一些野外把追踪距离设置小一点提高性能在一些封闭的地方或者须要反射的地方可以把追踪距离设置大点。
### 离屏阴影
lumen scene的lighting不仅要算colour也要计算阴影它不仅是当前屏幕的是包括整个surface cache的对于离屏阴影UE默认使用mesh object sdf去追踪如果场景存在大量物体每个物体都有[mesh sdf](https://zhida.zhihu.com/search?content_id=231379699&content_type=Article&match_order=1&q=mesh+sdf&zhida_source=entity)这样子每个surface cache tile对应的可能对交的mesh sdf非常多每个一像素对各个mesh sdf追踪是个很大的性能问题而且对于离屏的阴影大部分是不须要mesh sdf去追踪野外大场景如果一个mesh太小就算lumen scene 露光也看不出来对于室内存在小的sdf漏光找美术去调整就好。
能过r.LumenScene.DirectLighting.OffscreenShadowing.TraceMeshSDFs设置为0关闭离屏的mesh sdf trace,使用global sdf去trace.
### Mesh SDF
mesh sdf是lumen的核心就我看来目前lumen的消耗有80%来源于mesh sdf用到mesh sdf的地方非常多。可以通过LumenSceneDetail和r.Lumen.DiffuseIndirect.MeshSDF.RadiusThreshold共同控制让物体包围球的半径大于上述两个决定的数值的时候才参与mesh sdf的软追踪。这个优化非常大特别场景存在大量的小物体的时候比如植被这些小物体对于lumen scene的贡献非常低当然除了反射。为了解决反射这种情况我们并没有使用上面的参数控制小物体距离场生效与否我们直接把小物体的距离场全都默认关闭这样子对于须要反射的地方小物体手动开启距离场。
另外调整LumenSceneDetail不仅会影响mesh sdf的culling也会导致mesh card的丢失这个参数会引够mesh card小于某个分辨率的时候也会culling掉在surceface cache视图可以看到很多黄色图块黄色的代表culling掉紫色代表没有surface cache这会引起光照的异常黑影
### TraceProbe数量
在final gather阶段是非常耗时的最直观的影响因素就是trace射线的数量它是由八面体probe的密度每个probe trace的射线数量共同决定。probe的密度通过r.Lumen.ScreenProbeGather.DownsampleFactor和LumenFinalGatherQuality共同决定八面体每个面的trace线射数量由r.Lumen.ScreenProbeGather.TracingOctahedronResolution和LumenFinalGatherQuality共同决定。这个参数数值可以在不同的情况调整不同的数值在野外调到最低在室内或者封闭的地方相应调高。
### 反射分辨率
这个非常影响性能这个目前没找到优化方案最快的解决方式是降低反射buffer的分辨率须要高精度的反射比如达到镜面反射的情况把pp盒的lumen反射质量调高其它情况默认调到中档质量低质量是没有反射。
lumen默认管方都是质量拉满的可以调优的地方非常多另外,lumen非常吃显存对于显存部分的有机会在显存篇幅里介绍。
### 异步Lumen
lumen在5.1后支持了异步lumen这个是依赖GPU Async Compute,这个开启后在我们项目Lumen能节省1~2ms的消耗。
- 异步Light Probe Gather
首先要支持GSupportsEfficientAsyncCompute,AMD默认支持N卡默认关闭可以通过命名行ForceAsyncCompute强制打开。
另外通过r.LumenScene.DirectLighting.ReuseShadowMaps关闭lumen scene下的复用阴影的shadow map,使用距离场阴影代替注意这会导致动态骨骼在lumen scene没有阴影而且距离场阴影是很难跟shadowmap的阴影一致的这会导致两种方式下final lighting会不一样。r.Lumen.DiffuseIndirect.AsyncCompute打开支持异步light probe gather,通过以上三个,异步Light Probe Gather就支持了。
- 异步Reflect
在r.LumenScene.DirectLighting.ReuseShadowMaps关闭下异步反射可以通过r.Lumen.Reflections.AsyncCompute打开了。
- 异步Lighting
可以通过r.LumenScene.Lighting.AsyncCompute打开异步lighting.
通过上面三个把异步lumen全部打开的情况下在已经经过极致优化的lumen下还可以优化到1到2MS把lumen在GPU的耗时压得很低而且不失效果。
## 植被问题
植被有两个方向可以走nanite与普通植被各有利弊。
### nanite植被
当植被开启nanite后性能可以提高一倍以上就算面数非常高不可视的面都在nanite culling介段剔除掉。nanite植被有两个问题非常影响性能,wpo和maskwpo前面nanite有提到可以超过一定的距离禁用。对于mask材质建议走实体模型。另外当植被数量到达一定程度nanite cluster会随机丢失造成画面闪烁。
### 普通植被
普通植被最快最有效的优化方式是做好LOD减少culling distance.除此之外,还有两个因素对性能也有一定的影响。
- foliage actor的范围
foliage actor的默认tile的大小是256如果一个tile下有大量的植被而且植被密度非常浓密的时候这里不仅会引起分区流送引起cluster tree构建的卡顿也会导致gpu下prepass和basspass消耗过高因为默认的剔除盒子太大导致prepass和basspass存在大量的vs的overdraw.
- cluster tree遮挡剔除盒子大小
cluste tree的剔除盒大小决定了这一块instance是否绘制过大的盒子会引起prepass和basspass的vs阶段的overdraw过小的盒子导致存在大量的遮挡剔除查询导致render线程耗时过高而且也会产生过多的drawcall。引擎对于cluster tree的控制参数都是全局的有一些component适用也一些则不合适。过密的component须要提高盒子的粒度过于稀疏而且面数不高的可以减少盒子数。因为如果植被特别影响性能的时候可以修改引擎支持让这些参数跟着component走。引起cluster tree盒子的因素有点多这里主要聊三个。
- foliage.MaxOcclusionQueriesPerComponent
控制每个component的最多盒子数
- foliage.MinOcclusionQueriesPerComponent
控制每个component的最少盒子数
- foliage.MinInstancesPerOcclusionQuery
控制每个盒子最小包含的实例数
如果这个盒子粒度控制好在植被比较多的情况下可以在prepass和basspass省下2MS的预算。
## SingleLayerWater
如果水材质的效果不是用面表达的是用法线贴图去做的话可以把材质的mesh换成一个quad,高面的plane,在singlelayerwater里非常耗时。
## 直接光照
### cluster deffer lighting
推荐知乎大佬张亚坤多光源渲染的版本答案Cluster Shading的文章对于cluster deferred lighting有个介绍先简单说一下cluster deferred流程。在gather light介段把视锥体切豆腐在cs中把光源注入到格子中然后在cluster deferring shading的发起一个quad的drawcall,每个像素找到对应的grid,从grid中找到对应的light,叠加shading.cluster shading性能肯定是非常好的优化地方不多只与采样的buffer大小有关。
UE5默认是关闭cluster deffered shading的可以通过r.UseClusteredDeferredShading打开。
引起不能cluster shading的原因有很多如产生阴影有light function,使用light channel,平行光rect光等。在非cluster shading与unbatch light之间还有一些light主要是非shadow,非light function的,主要是rect等,每一个发起一个drawcall,这里消耗不低,能减少就减少一下,把生效距离设置短一点。
### unbatch light
对于unbatch light主要是lumen scene light和正常的直接光照light,这里就不说了光照lighting,shadow mask生成有距离场阴影的也在这里追踪建议就是交由逻辑层管理根据不同策略动态开关。