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) 和禁用( 这是一个隐含的、不需要声明的状态) 因此, 它的标准写法就是简单地列出启用这个功能时所需的关键字; 禁用状态被认为是自然而然的默认情况, 不需要额外声明