Shader优化

介绍一些优化Shader的方法。

一 LOD/可控的SubShader加载

Shader LOD的动态加载与释放:不同级别的Shader LOD,动态加载当前需要使用的Shader LOD(subshader),减少shaderlab内存占用(需要修改引擎源码)。

二 运行时压缩

如果有条件利用时间换空间的话。

默认的runtime的shader创建过程
在transfer中会将实际的shader code进行解压,并在之后parse的过程中创建的subprogram中进行保存 (programCode 字段)。

压缩(LZ4HC)保存在subprograme内的shader code,在compile到gpu时再解压(Unity默认情况下保存的是已经解压过的shader code)。

节约内存,但是可能warmup或直接compile gpu program时的时间开销增大。

三 变体相关

shader_feature 和 multi_complie 是两个很相似的预编译指令,声明Keyword(宏标识符)。在Shader代码中控制开启宏或关闭宏时物体的渲染过程。最终编译的时候也是根据这些不同的宏来编译生成多种组合形式的Shader源码。其中每一种组合就是这个Shader的一个变体(Variant)。

优化思路大致是四个方面:控制总量,剔除冗余,精准加载,加载策略

1 Keyword关键字

Global Keyword关键字限制

早期版本256,不能满足更复杂项目的需求。
在2020.3,数量限制是384。
建议升级到2021.2以上,来规避这个Keyword上限约束。

Unity can use up to 4,294,967,294 global shader keywords. Individual shaders and compute shaders can use up to 65,534 local shader keywords.

Keyword越多、变体越多,其内存占用越大。可以使用shader_feature代替multi_compile。

2 剔除

上文提到,特别是URP管线出现之后,Keywords数量快速增长,编译后的变体数量更是指数级增长,所以无效变体的剔除是打包过程中必须的一步操作。

如图ShaderLab总内存控制在30M左右没有问题,但是PBR_SceneCommon Shader很⼤,可以分析变体关键字,去除不⽤的变体。

可以参考:Stripping scriptable shader variants | Unity Blog

3 Warmup

未做Warmup的Shader,第一次渲染时有CreateGPUProgram的开销。

(1) 常驻的Shader

一些通用的基础的Shader,配置在Always include预加载Shader列表,再调用Shader.WarmupAllShaders。

WarmupAllshader不影响Shader的加载,是提前将所有已加载的Shader都做一次快速渲染/初始化,防止这部分开销(CreateGPUProgram)分散出现在运行时一些总开销较高的时候。

(2) 动态加载的Shader

通过创建SVC,加载,再ShaderVariantCollection.WarmUp。

4 加载优化

(1) Dynamic shader variant loading
对于Shader变体较多的shader,Unity新引入了Dynamic shader variant loading功能。
Dynamic Shader Variant Loading

(2) 可控的Shader变体加载
在运行时,动态收集特定时间段内的shader变体使用情况。保存为SVC,方便开发者对shader的变体使用进行控制

四 ALU计算优化

精简算法

  • 移动平台最好禁用Standard材质
  • 特别是全屏绘制的物体需要尽量简化Shader,比如空气墙。

降低浮点数精度

  • 在移动平台下,GPU可在执⾏⼀个float操作的同等周期内,执⾏2个half操作。尤其是屏占⽐较⾼、半透明物件所⽤的shader,优化的收益较⾼。
  • 通过Xcode性能分析工具可以得到有效提示

复杂函数

  • Transcendental mathematical functions (such as pow, exp, log, cos, sin, tan) are quite resource-intensive, so avoid using them where possible on low-end hardware. Consider using lookup textures as an alternative to complex math calculations if applicable.
  • Avoid writing your own operations (such as normalize, dot, inversesqrt). Unity’s built-in options ensure that the driver can generate much better code.
  • Remember that the Alpha Test (discard) operation often makes your fragment shader slower.

分支循环

  • 为了合批⽽添加if/else,如果实际效果不明显,建议使⽤Shader变体来解决

其他

  • 移除Material中没有使用到的属性
  • 禁止一些不必要的操作或用替代方案,比如GrabPass
  • 适配中低端手机考虑减少纹理采样次数

五 Editor Project Settings

Built-in管线

URP管线

参考