UnityShader30:预编译指令 multi_complie 和 shader_feature

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 200

        Pass
        {
            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_ON
                    return fixed4(1, 1, 1, 1);
                #elif _COLOR_R
                    return fixed4(1, 0, 0, 1);
                #elif _COLOR_G
                    return fixed4(0, 1, 0, 1);
                #elif _COLOR_B
                    return fixed4(0, 0, 1, 1);
                #else
                    fixed4 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)

  1. 指定的第一个 keyword 默认生效
  2. 对于一条预编译指令中的内容,当然同时有且只有一个 keyword 生效
  3. 在脚本中使用 material.EnableKeyword、Shader.EnableKeyword 或 CommandBuffer.EnableShaderKeyword 来指定激活哪个关键字,需要注意的是后两个方法是全局的,当然了 Keyword 不特殊声明也是全局的
  4. 同 ③ 可以使用 .DisableKeyword 方法取消激活,如果没有被激活的回到 ①
  5. 除此之外,也可以在 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 个变体

二、未定义宏(NoKeyword)与关键字限制

上面的代码中有下面这么一段,那么对于这第二行,__ 是什么东西呢?

#pragma multi_compile _COLOR_R _COLOR_G _COLOR_B
#pragma multi_compile __ DB_ON
#pragma shader_feature FANCY_STUFF

其实它代表着当前这一行预编译指令中,所有的关键字都不生效,可以理解为“空”,即未定义宏(NoKeyword),对于 #pragma multi_compile __ DB_ON,其 Shader 仍会编译成两个变体:一是不包含 DB_ON 模块代码的变体;二是包含 DB_ON 模块代码的变体,当然默认为__,即不包含 DB_ON 的变体生效

  • 全局的 Keyword 只能有256个!因此可以尽量使用 __,这样可以省掉一个关键字
  • 对于只有一个关键字的情况,如过是 #pragma shader_feature A 这种,它相当于是省略了前面的 __,是一种简便写法,但是 #pragma multi_compile A 并不是!它是一种错误的写法,相当于是 shader 此时只对应 A 这个变体,但无论如何,永远不建议这么写

局部的关键字:

同样能使用 #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 项目时和 shader 在同一个 AB 包中没有任何 Material 用到的变体就不会打出来。这也意味着在非编辑器中运行代码 Material.EnableKeyword("B") 可能会不起作用,因为没有 Material 在使用变体 B,变体 B 没有被 build 出来,运行时也找不到变体 B,而在编辑器环境下,multi_complie 和 shader_feature 没有区别

shader_feature 的作用就是避免没有被使用的冗余变体(ShadeVariant)被生成

一般来讲,对于 Properties 中定义对的 Enum 或者 Toggle 关键字,使用 #shader_feature,而对于在脚本中设置的关键字,使用 #multi_complie,这个很好理解,又或者可以这么说:

  • #multi_compile 往往同时适用于大部分 shader,与 shader 自身所带的属性无关
  • 而 shader_feature 定义的宏多用于针对 shader 自身的属性

如果偏要使用 shader_feature,但又避免不了用到所有的变体,也可以通过把对应 Shader 加入到 “always included shaders” 中,这样它所有的变体都会被直接打包到游戏,又或者放在 Assets/Resources 文件夹,不走 AB 加载

当然这两种方法都不推荐,如果不想 Material 和 shader 在一个 AB 包里的话,又要生成足够的变体,还有一种靠谱的方法就是 SVC,基于篇幅这个就不介绍了

参考文档:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值