[官方培训] 22-UE资产优化 | Epic 肖月
- 目标:面向TA / 美术,不影响视觉的前提下,优化运行和编辑效率
- 引擎:UE5.1 延迟管线(不涉及GamePlay、游戏交互逻辑、前向渲染)
- 平台:支持 Namite/Lumen 等影视级特性的主流 PC,Windows 10 with DX12
一. 性能查看
1.1 整体指标
- 在编辑器控制台命令行中,输入
Stat FPS
,可以看到当前场景运行的 FPS 帧率,以及可以换算过来的实际生成每帧画面具体用了多少 ms 毫秒
- 在做具体的性能优化前,需关闭项目设置中的 Smooth Frame Rate,以防在场景区域的优化检查时,掩盖一些问题
- 使用
Stat Unit
,对帧率和耗时做进一步拆解
1.2 瓶颈定位
- 根据
Stat Unit
的拆解:
- Game:CPU 的游戏线程,如场景中 Actor 物体位置变换、用户逻辑交互、动画及物理相关计算内容
- Draw:渲染线程,CPU 为 GPU 做绘制前的数据准备
- GPU:显卡实际绘制的消耗
- 三个线程有大部分的并行内容,根据线程耗时对比(哪部分的消耗和最终 Frame 消耗最接近),定位瓶颈
- 使用
Stat UnitGraph
,在一定的时间窗里绘制几个线程的消耗变化,方便在场景中变化视角,排查问题
- 假设瓶颈在渲染线程:
- 通过
Stat SceneRendering
查看实际消耗
- InitViews:物体可见性剔除时的消耗
- GatherRayTracingWorldInstances:收集 Lumen 场景光线追踪的消耗
- Mesh draw calls:CPU 生成的模型 DrawCalls 数量
- Decals/Lights in scene:场景中贴花/灯光数量
- 通过
Stat InitViews
查看剔除消耗
- View Visibility:判断物体是否在视窗内的计算消耗
- Occlusion Cull:判断物体是否被前后遮挡的计算消耗
- Processed Primitives:场景中被处理的 Primitive 总数
- Frustum Culled Primitives:被视锥剔除的 Primitive 数量
- Occluded Primitives:视锥剔除后,预计算可视性 + 软件遮挡剔除 + HZB剔除总数量
- 当硬件显存不高时,通过
Stat RHI
查看各 buffer 在显存中的占用,对应缩小项目纹理大小或灯光阴影质量
- Texture Memory 2D:包含材质中的纹理、顶点 Buffer 的存储量等
- Render Target Memory 2D:包含渲染视图的 GBuffer、阴影贴图等
- DrawPrimitive calls:相较于
Stat SceneRendering
中的 Mesh draw calls 还包括:贴花、阴影、后处理提交的 DrawCall 数量等 - DrawCall 是发送给 GPU 的渲染批次,一个 DrawCall 内是一组属性相同的多边形,渲染时通过同样的处理过程,不同平台上合适的 Drawcall 指标也大不相同
- DrawCall 是性能优化的一大重点(比控制面数更重要)
- 假设瓶颈在 GPU 线程:
- 通过
Stat GPU
查看 GPU 绘制各个 Pass 的消耗
- Basepass:生成屏幕的 GBuffer 消耗
- Prepass:深度信息的预绘制
- Shadow Depth:阴影贴图的绘制消耗
- Lights:灯光的光照计算(Lumen 各部分消耗等)
- 快捷键
Ctrl + Shift + ,
(也可以通过ProfileGPU
命令) 打开 GPU Visualizer,进一步查看 GPU 线程各环节消耗情况
1.3 引擎 Scalability 设置
- 当引擎渲染质量开的过高时,编辑时会有卡顿现象,在编辑器调整 Scalabiity 等级,使画面质量快速降级,提高帧率,例如:
- 当 Lumen 消耗过高时,对积压、反射降级
- 开放场景中,物体三角面过多时,降级 View Distance,高强度剔除远处的物体
- 内存有限时,降低纹理标准,以缩小 Streaming 纹理池尺寸(引擎根据纹理池上限,选择实际可行的精度 Mip 加载)
二. 分模块优化
2.1 模型
- Culling 剔除:只提交实际可见的 primitive 进行渲染绘制,前后遮挡与视窗外影响不大的模型尽可能的剔除
- 非 Nanite 物体:逐物体 Component 组件 / Instance 实例剔除
- 通过
Stat InitViews
查看性能消耗(如:View Visibility、Occlusion Cull 等实际剔除的计算量过大,可以考虑把模型做一定的合并)
- 通过
r.VisualizeOccludedPrimitives 1
查看被遮挡物,距离剔除的计算在遮挡剔除之前,大场景优化,按视觉重点设置不同物体的剔除距离(如:场景中的小道具、地平线的小植被等,都可以通过距离剔除提前的优化掉)
- Nanite 物体:逐 Cluster 剔除(效率效果更高)
- 剔除单位细化到模型内部相邻三角面组成的 Cluster,且剔除工作大部分是在 GPU 上高速完成
- 在 Nanite Visualization > Overview 视口,查看整体
- 在 Nanite Visualization > Clusters 视口,查看 Clusters 剔除(不同颜色的 Clusters 是在提交渲染前做剔除的最小单位,随着相机视角变化,三角面的 Cluster 被重新计算,从而大大减少实际进行光栅化绘制的三角面)
- Drawcall:合理化场景模型的拆分,单个模型通过优化材质数量减少 DrawCall
- DrawCall 的划分:
- 场景中,不同的 Object 物体、Component 组件等都会被划分为不同的 DrawCall
- 模型内,不同的 LOD 层级、材质等也被划分为不同的 DrawCall
- ISM 植被类静态网格体的 Instance 实例,HISM 植被按全场景的种类和 LOD 拆分(效率更高)
- 大场景模型的拆分建议:
- 非 Nanite 模型:
- Culling 剔除效果和 Drawcall 数量间平衡(模型过于细碎,导致 Drawcall 数量过多,影响性能;模型合并的过大,又容易剔不除小物件,造成渲染资源浪费)
- PC 平台不要提早优化,从模块化的模组物件做起,后期看情况快捷合并
- 多用 Instance 实例(以黑客帝国的项目为例,整个工程单独的模型数量不多,通过程序化拼接场景,即使是百万级别的 Instance,对 Drawcall 的负担也不会太重)
- Nanite 模型:
- (开启了 Nanite,模型的 DrawCall 划分就会高效很多)同类材质可高效合批
- 主要从加载效率、内存占用等方面考虑模型拆分(以黑客帝国的项目为例,横跨整个场景的立交桥,柱子、护栏等可用模组拼接,有弧度的桥面按 WorldPartition 的加载精度进行切割,一次只加载一个小方格内的模型)
- 单个资产优化:
- 不论是否为 Nanite 模型,不同材质会被分为不同的 DrawCall,在美术层面,需要控制模型的材质插槽数量,或在远处低精度 LOD 上对材质做近一步简化与合并
- Quad Overdraw:基于屏幕占比控制三角形密度,减少 Overdraw
- 对性能的影响主要体现在 GPU 上,当模型的三角面密度超出像素精度过多(如:模型远离相机时,屏幕的占比缩小,一个像素内会包含多个三角形),造成光栅化过程的浪费
- 引擎有自动 LOD 生成功能,及自动设置方案:
- 通过 Level of Detail Coloration -> Mesh LOD Coloration 视口,debug 当前场景中模型的 LOD 显示层级(无 LOD 设置则场景显示为灰色)
- 设置 LOD 后,模型会随相机视角变化,自动选取合适的 LOD
- 通过 Optimization Viewmodes -> Quad Overdraw 视口,定位场景中 Overdraw 较严重的区域(如:实例中的地面、房屋等绿色区域的三角面密度就偏高了)
- 在单个模型编辑器中,设置 LOD 数量,点击应用就可以自动生成多级 LOD
- 默认情况下,每级 LOD 会减少一半面数,但也可以根据具体的 LOD 层级,进一步调整减面
- 更简单的设置方案:分部件制定标准,在引擎目录的 Base Engine.ini 配置文件中设置,在模型编辑器中划分为不同的 LOD 组
- 在引擎内批量编辑 LOD 组
- 模型优化:
- 按模型重要性、精度、类别等,合理设置 LOD(佷依赖美术经验)
- 甚至可能需要对 LOD 重新建模
- 模型开启 Nanite 提高性能,能用尽用(不再以物体为级别切换 LOD,无视觉跳变,更细致的三角形密度控制)
- 原则上,模型拓扑越均匀,Nanite 剔除处理效率越高(具体效果需要视模型外观而定)
- 在多层 Mesh 堆叠的情况下,Nanite 处理效率并不高,会造成大量 Overdraw,场景搭建时应尽量规避(Mesh 堆叠同样也会降低场景中 Lumen 光线追踪效率)
- 通过 Nanite Visualization -> Overdraw 视口,定位不合理的模型堆叠区域
- 模型内部减少穿插,组装场景前,使用 DCC 软件批量检查模型穿插,或在引擎内合并修复穿插
- 顶点数:减少不必要的 hard edge/光滑组,模型 UV 不宜切的过碎(从磁盘/内存占用角度,模型顶点数越少,顶点属性越少,存储效率越高,光滑组的拆分,由于多了法线存储从而增加了顶点数)
- 属性数:减少 UV sets,非必要不生成 Lightmap UV
- UE5.1 版本的 Nanite 支持:
- 支持带 WPO 顶点动画的模型(相较于普通静态模型,仍有较大开销),开启 WPO 优化模式
r.OptimizedWPO
,建议仅在必要时开启,并按相机距离设置,在材质内部简化 WPO 计算逻辑(如:把远处的植被风动动画,烘焙到 VAT 顶点动画贴图上,把复杂的逻辑计算简化到只做一次贴图采样)
- 支持 Masked 材质,但Masked 材质的性能消耗比 WPO 更大些(如:堡垒之夜项目中的树叶的表现,使用更高面模型替代 Masked 材质,详见:Bringing Nanite to Fortnite Battle Royale in Chapter 4)
2.2 材质与贴图
- 材质纹理的消耗主要在GPU和内存上,通过 Optimization Viewmodes -> Shader Complexity 视口,衡量材质复杂度(红色及白色区域需要重点关注)
- 半透明材质造成的开销居多(如:大量半透明粒子贴片叠加到相机前,虽然这些材质本身的计算并不复杂,但由于半透物体需多 Pass 进行渲染,材质的消耗迭代增长)
- 材质指令数过高,同样影响材质复杂度,通过材质编辑器中的 Stats 查看材质用到的指令数
- 指令数不仅包含当前材质编辑器中用到的所有节点,还包含不同光照模型、不同复杂度的光照计算产生的指令数(毛发、眼睛、次表面材质等角色相关的指令数会偏高),(主流 PC 端)指令数的参考:
- 300+ 正常
- 500+ 优化
- 1000+ 尽量减少
- 优化材质指令数的建议:
- 避免使用一个复杂材质覆盖所有需求,合理规划好母材质,用 StaticSwitch 节点做材质分支
- 注意不同节点的指令数消耗不同
- 对于远景材质或光泽度不高的材质,可用 fully rough 简化,节省指令数
- 简化多 Pass 的半透明材质,粒子尽量使用 Unlit 无光照模式
- 纹理贴图除了本身的材质指令消耗,还需要等待 Steaming 进来的时间,开销较大,优化建议:
- Pack 通道减少材质的贴图采样数(如:Substance 等材质制作软件都提供了 Pack 不同通道到少量贴图输出的格式)
- 压缩贴图可以大大减少显存的占用
- 极个别不能用压缩格式的贴图(如:VAT 顶点动画),要注意控制精度
2.3 灯光与阴影
- 直接动态光的优化方向(静态烘焙光照,运行时开销较小,暂不展开):
- 光照计算复杂度:
- 通过 Optimization Viewmodes -> Light Complexity 模式检查光照计算复杂度
- 不同灯光类型的开销不同,Directional Light 定向光源更耗性能
- 设置 Spot Light 聚光灯和 Point Light 点光源的 Max Draw Distance,提前剔除
- 控制动态灯光的影响范围(如:修改 Point Light 点光源的Attenuuation Radius 衰减半径、修改 Spot Light 聚光灯的 Cone Angle 锥角等控制灯光影响范围),减少重叠区域
- 阴影质量:
- 阴影质量的情况,主要观察 GPU 上,ShadowDepths 的消耗
- 原理:直接光阴影的主要绘制方式是从灯光方向来绘制场景的深度,计算光照时,在屏幕空间通过深度的比较,判断是否在阴影中
- 因此,阴影开销的优化主要在于:
- 控制绘制范围,减少投射阴影的模型数量(范围内所有物体都需要光栅化输出深度,如果影响的物体过多,会影响 DrawCall),关闭不需要投影模型的阴影绘制
- 控制阴影贴图精度开销,不同类型阴影开销:Point Light 点光源 > Directional Light 定向光源 > Spot Light 聚光灯
- VSM 虚拟阴影
- VSM 虚拟阴影,一般与 Nanite 协作,支持高效缓存 Cached Page
- 通过 Virtual Shadow Map -> Cached Page 模式,查看缓存(引擎默认开启静态物体与动态物体分开缓存的模式,绿色区域为静态物体不需要每帧重新绘制,灯光变化会更新缓存并显示为红色,动态物体会每帧更新,蓝色区域表示只有动态缓存更新)
- 通过 Virtual Shadow Map -> Virtual Page 模式,查看不同相机视角下,自适应变化以配置阴影精度(相较于旧的级联阴影,VSM 虚拟阴影的锯齿情况好很多)
r.Shadow.Virtual.MaxPhysicalPages 4096
设置 VSM 虚拟阴影的纹理池上限(内存限制较高时,可以适量改小数值,但数值过小,会导致部分阴影纹理无法绘制)
- 通过 Virtual Shadow Map -> Shadow Mask 模式,查看阴影贴图
r.Shadow.Virtual.ResoulutionLodBiasLocal
设置局部光的 LOD 精度偏移r.Shadow.Virtual.ResoulutionLodBiasDirectional
设置方向光的 LOD 精度偏移(数值越大,阴影贴图精度下降,阴影质量越差)
- 使用 SMRT 阴影贴图光线追踪,实现半影效果:修改 Directional Light 定向光源的 Source Angle
r.Shadow.Virtual.SMRT.RayCountLocal
设置局部光的光线数量r.Shadow.Virtual.SMRT.RayCountDirectional
设置方向光的光线数量(默认是 16,数值越低,性能越好)
r.Shadow.Virtual.SMRT.SamplesPerRayLocal
设置局部光的逐光线采样数量r.Shadow.Virtual.SMRT.SamplesPerRayDirectional
设置方向光的逐光线采样数量(采样数越高,阴影越柔和,越耗性能)
- 间接动态光 Lumen 的优化方向:
- 为了更好的捕捉间接光,资产制作注意项:
- 通过 Lumen -> Lumen Scene 模式,查看场景间接光,确保 Lumen Scene 模式下的物体有足够高的精度,及正确的材质表现
- 如果光线追踪的精度设置为:软件光线追踪,需要关注 Distance Fields 的精度
- 通过 Visualize -> Mesh DistanceFields 模式,查看网格体距离场
- 例如:这个灯泡模型,在 Mesh DistanceFields 模式下,由于精度不足漏掉了部分(如果旁边有强反射,则会被显示残缺)
- 可以在模型编辑器中修改 Distance Field Resolution Scale 距离场分辨率尺寸,以提高精度
- 如果光线追踪的精度设置为:硬件光线追踪,画面质量更高,追踪的是模型三角面,如果使用 Nanite 模型,则实际追踪的是面数较低的 Fallback Mesh(一般情况下没啥问题,但当 Fallback Mesh 减面过多,强反射区域,则也会被显示残缺)
- Lumen Scene 材质
- 物体表面的材质光照信息,主要靠 Mesh Card 捕捉,通过
r.Lumen.Visualize.CardPlacement 1
命令查看场景中的 Mesh Card
- 通过 Lumen -> Surface Cache 模式,查看捕捉效果(黄色、紫色区域为没被捕捉到的,不会渲染到 Lumen Scene 中)
- 若要减少上述未被捕捉的黄色、紫色区域,可以提高后处理盒子中的 Lumen Scene Detail 参数
- 但对于结构较复杂的模型,可以在模型编辑器中提高 Max Lumen Mesh Cards,完整覆盖表面(Card 过高影响性能)
- 对于凹面/空洞过多的极端模型,仅单纯提高插片数可能效果不佳,需要进行离线模型拆分
- Lumen 光追效率:
- 优化重建光追加速结构的开销
- 实时光追依赖引擎在场景内搭建的加速结构,SkeletalMesh 带动画的角色、破碎、毛发、布料、Niagara粒子等,都需要每帧重建加速结构,因此需要重点控制这类物体的面数
- 在 SkeletalMesh 静态物体上的 WPO 顶点动画,需要限制开启区域,如:
r.RayTracing.Geometry.StaticMeshes.WPO.CullingRadius(5000)
按相机距离开启/关闭
- 化对视窗外物体的追踪
- 正确的间接光照需要屏幕外的物体,Lumen 会追踪视窗外的物体,需要限制追踪范围,剔除不必要的物体以提高效率:
r.RayTracing.Culling
r.RayTracing.Culling.Radius 10000
(100米)r.RayTracing.Culling.Angle 1
(5度)
- 设置光追组整体剔除
- 以黑客帝国项目为例,把小模块搭建的整个建筑设置为一个光追组,超过设置范围时,模型被整体剔除,提高计算
- 简化光线击中表面时的材质计算
- 使用 RayTracingQualitySwitch 材质节点,拆分
- Normal:屏幕空间 Gbuffer 中的计算逻辑
- RayTraced:Lumen Scene 中光追打到物体表面的材质计算
2.4 特效
- 特效的大部分优化都是在 Effect Type 上分类别进行设置
- Niagara 特效优化方向:
- 控制场景特效实例数
- 按距离进行相机剔除
- 设置 System 个数上限
- 利用 Niagara Debugger 工具检查特效性能,为特效的解算线程或渲染线程设置预算上限
- 控制粒子数量
- 分平台控制(在 Effect Type 上,分 Scalability 等级进行设置,并在编辑器中切换)
- 按距离缩放,设置 Spawnrate 发射量为 Scalability Distance Based Float 按相机距离减少,相当于给特效加 LOD
- 控制计算复杂度 Validation Rule
- 在 Effect Type 上,分等级禁用复杂 Module 模块或者 Render(如:在低等级上,禁用 Light Render)
- 针对复杂的 GPU 模拟,可以设置 Simulation Stage 迭代次数的预算上限
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!