作者:幸福抉择2502901973 | 来源:互联网 | 2023-09-01 18:41
multi_complie和shader_feature编译指令往往用于正式游戏项目的优化一、关键字与Shader变体multi_complie的用法:#pra
multi_complie 和 shader_feature 编译指令往往用于正式游戏项目的优化
一、关键字与Shader变体
multi_complie 的用法:
#pragma multi_compile NAMEA, NAMEB, NAMEC, …,
参考代码:
Shader "Jaihk662/ShaderVariantTest"
{Properties{_MainTex("Texture", 2D) = "white" {}//枚举宏,注意在预编译指令中,宏要大写[KeywordEnum(R,G,B)] _Color("Color", float) = 0}SubShader{Tags { "RenderType" = "Opaque" }LOD 200Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile _COLOR_R _COLOR_G _COLOR_B#pragma multi_compile __ DB_ON#include "UnityCG.cginc"struct v2f{float4 vertex: SV_POSITION;float2 uv: TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_ST;v2f vert(appdata_base v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;}fixed4 frag(v2f i): SV_Target{#if DB_ONreturn fixed4(1, 1, 1, 1);#elif _COLOR_Rreturn fixed4(1, 0, 0, 1);#elif _COLOR_Greturn fixed4(0, 1, 0, 1);#elif _COLOR_Breturn fixed4(0, 0, 1, 1);#elsefixed4 color = tex2D(_MainTex, i.uv);return color;#endif}ENDCG}}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestShader: MonoBehaviour
{public Material mat;void Update(){if (Input.GetKey(KeyCode.Q))mat.EnableKeyword("DB_ON");if (Input.GetKey(KeyCode.W))mat.DisableKeyword("DB_ON");}
}
其中 NAMEA 为枚举名,一个非空的枚举名为一个全局的关键字(Keyword)
关于关键字(Keyword):
- 指定的第一个 keyword 默认生效
- 对于一条预编译指令中的内容,当然同时有且只有一个 keyword 生效
- 在脚本中使用 material.EnableKeyword、Shader.EnableKeyword 或 CommandBuffer.EnableShaderKeyword 来指定激活哪个关键字,需要注意的是后两个方法是全局的,当然了 Keyword 不特殊声明也是全局的
- 同 ③ 可以使用 .DisableKeyword 方法取消激活,如果没有被激活的回到 ①
- 除此之外,也可以在 Properties 中定义对应的 Enum 或者 Toggle,并在材质面板中设定好 keyword,就如下图
这样做的目的是什么?对于 #pragma multi_compile _COLOR_R _COLOR_G _COLOR_B 指令,对应的 Shader 会被编译成三个变体(Variant):一是只包含 _COLOR_R 模块代码的变体 A;二是只包含 _COLOR_G 模块代码的变体 B,三是只包含 _COLOR_B 模块代码的变体 C,这就相当于是省掉了着色器中的 if 语句,直接将所有分支全部展出来
当然了,如果一个 shader 中有多个这样的预编译指令,那么生成的变体数量会是累乘的,就像上面的示例代码就会有 2 x 3 = 6 个变体
二、特殊的双下划线与关键字限制
上面的代码中有下面这么一段,那么对于这第二行,__ 是什么东西呢?
#pragma multi_compile _COLOR_R _COLOR_G _COLOR_B
#pragma multi_compile __ DB_ON
#pragma shader_feature FANCY_STUFF
其实它代表着当前这一行预编译指令中,所有的关键字都不生效,可以理解为“空”,它不属于关键字,对于 #pragma multi_compile __ DB_ON,其 Shader 仍会编译成两个变体:一是不包含 DB_ON 模块代码的变体;二是包含 DB_ON 模块代码的变体,当然默认为__,即不包含 DB_ON 的变体生效
- 全局的 Keyword 只能有256个!因此可以尽量使用 __,这样可以省掉一个关键字
- 对于只有一个关键字的情况,它相当于是省略了前面的 __,是一种简便写法
局部的关键字:
同样能使用 #pragma multi_complie_local 的方法声明只在对应 shader 内部生效的关键字(keyword),对于局部的关键字(keyword):
- local keyword 仍有数量限制,每个 Shader 最多只能包含64个 local Keyword
- 只能使用局部方法 material.EnableKeyword 来指定激活
- 如果同时声明了全局和局部 keyword,局部的优先级高
三、multi_complie && shader_feature
multi_complie 和 shader_feature 作用完全一样,唯一的区别是:如果使用 shader_feature,build 项目时没有用到的变体就会被删除不会打出来。这也意味着在非编辑器中运行代码 Material.EnableKeyword("B") 可能会不起作用,因为没有 Material 在使用变体 B,变体 B 没有被 build 出来,运行时也找不到变体 B,而在编辑器环境下,multi_complie 和 shader_feature 没有区别
一般来讲,对于 Properties 中定义对的 Enum 或者 Toggle 关键字,使用 shader_feature,而对于在脚本中设置的关键字,使用 multi_complie,这个很好理解
如果偏要使用 shader_feature,但又避免不了用到所有的变体,也可以通过把对应 Shader 加入到 “always included shaders” 中,这样它所有的变体都会被直接打包到游戏
参考文档:
- https://docs.unity3d.com/cn/current/Manual/SL-MultipleProgramVariants.html
- https://blogs.unity3d.com/cn/2018/05/14/stripping-scriptable-shader-variants/
- https://docs.unity3d.com/cn/current/Manual/OptimizingShaderLoadTime.html