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的变体使用进行控制

Editor Project Settings

Built-in管线

URP管线

ALU计算优化

  • 精简算法,特别是全屏绘制的物体需要尽量简化Shader,比如空气墙。
  • 降低浮点数精度:在移动平台下,GPU可在执⾏⼀个float操作的同等周期内,执⾏2个half操作。尤其是屏占⽐较⾼、半透明物件所⽤的shader,优化的收益较⾼。
  • 禁止一些不必要的操作或用替代方案,比如GrabPass。
  • 减少纹理采样次数也是适配中低端手机的一种策略。
  • 移除Material中没有使用到的属性。

参考