1.背景
复杂游戏会有很多shader,不进行管理的话,会引发不少麻烦。
2.shader来源
(1)艺术家。
艺术家会制作大量的Shader Graph,每个Shader Graph最后都会变成一段Shader代码。
而艺术家的想象力并不受控,所以会生成海量的Shader。
(2)程序员自身
(1)在游戏中,各种限定条件非常多,比如材质是在点光源下、在面光源下、全局光照下,还是在球面调和函数下?材质是单面渲染,还是双面渲染?材质有没有透明度等等?总而言之,围绕材质就会有各种变化。
(2)如果为每种变化组合都单独实现一个Shader,这种Shader的数量会非常庞大。
3.解决方案~Uber Shader 和 Variants
(1)Uber Shader是一个完整的Shader,用宏定义对各种情况进行分支处理,每一种宏定义就代表了函数的一种可能的分支。
(2)而对于GPU来说,分支会极大降低GPU的运行效率。因为如果一个函数中有分支语句,就会导致函数的执行时间不一致,而GPU采取的是SIMT架构,GPU期望一批指令在不同计算核心上的计算时间是一致的。
(3)因此,将所有这些分支编译成大量的Shader。这些Shader也叫做变体( Variants)或组合(Permutation)
比如有165个手写Shader模板作为Uber Shader,而这些模板一共生成了7万多个Shader的变体。
4.优点
(1)出现Bug后,只需要对某一类Shader算法的实现进行修改
(2)如果不使用Uber Shader,需要依次修改所有可能的组合,在这个过程中很容易出现错误。
5.针对shader语言多样性的处理
(1)Shader的跨平台编译功能
Shader语言的多样性,比如GLSL、HLSL、WGSL,以及苹果自己的Metal。
(2)开源的SPIRV第三方库,它能够帮助我们编译各种Shader,包括PS(Playstation)的PSSL。
(3)从事引擎开发、编写Shader时,建议一开始就尝试将Shader编译成各种平台的版本,比如Vulkan、Metal等。
(4)这样可以避免需要针对不同平台再单独写一套Shader的工作,因为Shader的调试很麻烦。这样就可以对Shader进行管理了。