生成多种着色器程序扩展
通常,保留大部分的着色代码是很方便的,但也允许产生稍微不同的着色“变体”。这通常被称为“mega shaders”或“uber shaders”,并通过为每个案例编译不同的预处理程序指令来编译shader代码。
在Unity中,可以使用指令 #pragma multi_compile
或者 #pragma shader_feature 来编译着色器程序片段。这个东西对表面着色器也起作用。
在运行时,通过材质球的关键字(Material.EnableKeyword and DisableKeyword) 或者全局着色器关键字(Shader.EnableKeyword and DisableKeyword)来选择合适的 着色器变体.
多重编译如何工作
编译指令长这样:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
这个将会产生两个着色器变体。一个变体中定义了FANCY_STUFF_OFF,另一个定义了FANCY_STUFF_ON。在运行时,其中一个会被材质球或者全局着色器关键字激活。如果两个关键字都是被禁止的,那么就会使用第一个。
当然,这里可以不止两个关键字,如下面,可以有4个:
#pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
当任意一个名字的字符全部是下划线,那么一个着色器变体也会生成,但是没有任何预定义宏被包含在内。这通常用于着色器的特性,来避免用完两个关键字。比如,西面的指令会产生两个变体,第一个什么都没定义,第二个定义了一个 交FOO_ON的宏。
#pragma multi_compile __ FOO_ON
shader_feature 与multi_compile的差异
这两个指令很相似,唯一的差别是由 shader_feature定义出的,没有被使用的变体不会被打包到游戏中。所以 shader_feature 适用于产生那些定义在材质球上的关键字控制的着色器变体,而multi_compile则更适合产生全局关键字控制的着色器变体。
除此以外,shader_feature有一个简写特性:
#pragma shader_feature FANCY_STUFF
上面这段代码 等同于#pragma shader_feature _ FANCY_STUFF,它会产生两个变体,一个啥都没有定义,一个定义了一个FANCY_STUFF。
结合几行multi_compile
使用好几行multi_compile指令是可以的,然后shader就会被编译成这几行定义的所有可能组合个数的变体:
#pragma multi_compile A B C #pragma multi_compile D E
看上面这行代码,那么第一行会产生三个变体,第二行产生两个,然后总的还有6个(A+D, B+D, C+D, A+E, B+E, C+E)
最简单的理解方式就是认为每一行multi_compile指令控制一个shader特性。要记住的是这样的话shader的变体数量将增加的很快。比如,10行multi_compile指令,每一行都只有两个特性,结果会产生1024个变体。
关键字限制
当使用shader变体时,记住在unity中关键字最多只有256个。而且在内部已经用了60个了。而且,在一个项目中,关键字都是全局使用的,所以记得不要超标了。
内置multi_compile缩写
针对于编译多个shader变体,这里有好些个缩写符号。他们大多数情况下用来处理光照,阴影和
光线映射。
multi_compile_fwdbase
:编译所有Pass类型为ForwardBase 的shader变体。这些变体会处理不同的光线映射类型和主平行光是否有阴影。multi_compile_fwdadd
:编译所有Pass类型为ForwardAdd 的shader变体。这些编译变体用来处理平行光、聚光和点光类型,并且他们的变体会烘焙贴图。multi_compile_fwdadd_fullshadows
:与上面的一致,但是还包含了光源实时阴影的处理。multi_compile_fog
:扩展了几个变体来处理不同的雾类型
大部分内置的multi_compile缩写都会生成许多的shader变体。如果你清晰的知道有些东西你是不需要的,那么你可以使用指令 #pragma skip_variants来跳过一些变体。
#pragma multi_compile_fwdadd // will make all variants containing // "POINT" or "POINT_COOKIE" be skipped #pragma skip_variants POINT POINT_COOKIE
着色器硬件变体
一个常见的使用shader变体的原因是为了创建可靠的,简化的shader,并且这些shader还可以在高端或者低端的硬件设备上高效的运行,同时只有一个目标平台,比如说 OpenGL ES。无敌了提供一个特别的,专为不同级别能力的硬件优化的shader变体,你可以使用shader的硬件变体。
要生成shader的硬件变体,那么需要添加指令:#pragma hardware_tier_variants renderer,这里面的renderer是一个可用的硬件平台。使用这个#pragma 每个shader都会生成3个shader变体,不需要任何的关键字。每一个变体都会有以下其中之一被定义:
UNITY_HARDWARE_TIER1 UNITY_HARDWARE_TIER2 UNITY_HARDWARE_TIER3
你可以使用这些关键字来对更高或者更低的硬件编写额外的回调或者特性。在编辑器模式下你可以使用图形模拟菜单来测试任意一个档位。
为了保持这些变体尽量小,只有一个变体的shader会被加载到玩家处。除此之外,任何其它相同的shader-比如你只对TIER1做了特别优化版本,但是其它版本都一样,那么就不会增加额外的存储空间。
在加载时,Unity会检查当前在使用的GPU并且自动的获取一个 硬件等级值;如果GPU没有被自动获取到,那么默认就会是最高等级。当然,你可以覆写等级值: Shader.globalShaderHardwareTier,但是这个必须要在你希望的任何更改着色器加载之前完成。一旦shader被加载,他们会自动选择他们的变体,这个时候这个值就没卵用了。一个好的做法是,加载一个预加载的场景,然后再加载主场景。
注意,这些shader硬件层级不会受玩家设置的图像质量的影响,它们只取决于当前运行的GPU是个什么等级。
平台shader设置
除了为不同的硬件层调整您的着色器代码之外,您可能还想调整unity内部的定义(比如,你可能想要强制在手机上开启阴影接受)。你可以通过这个API来找到一些细节:UnityEditor.Rendering.PlatformShaderSettings ,这里面提供了一系列 可以覆写每个层级的特性。使用UnityEditor.Rendering.EditorGraphicsSettings.SetShaderSettingsForPlatform 来为每个平台,每个硬件等级进行shader调整。
注意如果 PlatformShaderSettings为不同硬件层级设置了不同的属性,那么即便#pragma hardware_tier_variants没有使用,也会生成许多shader变体。