1.变体简介
2.为什么需要变体
3.变体是如何产生的
4.变体带来的麻烦
5.multi_compile和shader_feature
1.变体简介
比如我们开了一家餐厅, 你有一本万能的菜单(Shader源代码), 上面包含了所有可能的菜式; 但是顾客每次来点餐时, 不可能将整本菜单都做一遍, 他们会根据今天有没有优惠(是否启用雾效), 是不是会员(是否启用阴影), 想吃主食还是甜点(使用哪个渲染管线)来勾选一些选项(Shader Keywords)a.万能的菜单(Shader源码)包含了所有可能的代码, 比如做牛排的步骤, 做冰淇淋的步骤, 会员专属摆盘的步骤b.最终给顾客的定制菜单(Shader Variant, 着色器变体)根据顾客勾选的选项(Keywords), 从万能的菜单里实际抄录下来的那一小部分菜式的集合; 它是一个最终编译好的, 独立无二, 只包含当前所需功能的完整Shader程序c.选项(Shader Keywords)比如_ENABLE_FOG_ON(启用雾效), _LIGHT_TYPE_SPOT(点光源)等, 这些是生成不同变体的条件
Shader变体就是同一个Shader源代码, 根据不同的功能开关(Keywords)组合, 编译出的多个不同版本的, 实实在在的Shader程序
2.为什么需要变体
1).性能如果在Shader里写if(_USE_FOG) {...}, GPU在执行时依然会判断这个条件, 会有性能损耗; 而变体就是编译的时候就决定了代码的去留, 运行时没有任何判断开销2).灵活性游戏要运行在不同性能的设备上, 可以为低端机编译一个去掉高级效果的变体, 为高端机保留所有效果; 一份Shader源码适配多种配置
3.变体是如何产生的
变体通过#pragma multi_compile和#pragma shader_feature两个指令

a.multi_compile不管你的场景用不用这些关键字, 引擎都会关键字生成变体; 适合必须存在的功能, 比如光源类型b.shader_feature只有在Material上真正启用了某个关键字或其他变体依赖时, 才会生成对应的变体; 适合可选的功能, 比如是否启用积雪


4.变体带来的麻烦
a.编译时间变长ShaderLab编译器需要为每一种组合编译一次, 1024个变体……你自己想象b.构建体积巨大每个变体都会被打包到最终的游戏里(如果使用了的话), 导致游戏安装包(APK/IPA)变得非常大c.内存占用高即使你没使用, 有些被强制编译的变体(multi_compile)也会被加载到内存中; multi_compile变体即使未被使用也会占用内存, 主要是因为Unity的资源加载机制为了确保运行时的稳定性和效率, 倾向于将一个Shader的所有已编译变体作为一个整体来加载和管理
5.multi_compile和shader_feature
1).multi_compile它的设计目的是强制列出所有可能性, __代表一个你必须明确指出的, 有效的全局默认状态; 它告诉你: 看, 即使你什么都不开, 我也会为你生成一个专门的变体; 所以它的语法是 __ A B, 非常直白2).shader_feature它的设计初衷是一个可选的、开关式的功能, 它的思维模式是:- 我有一个功能, 比如积雪- 这个功能有两种状态: 启用(对应 _KEYWORD_C)和禁用(这是一个隐含的、不需要声明的状态)因此, 它的标准写法就是简单地列出启用这个功能时所需的关键字; 禁用状态被认为是自然而然的默认情况, 不需要额外声明

