Unity shader中的#if和#ifdef

在研究宏定义的时候发现,如果不先理解#if和#ifdef宏定义的判断方法,去看宏定义的时候会乱七八糟的。网络资源此部分都讲的不好,因此先整理本篇梳理一下#if和#ifdef等。

一.#if defined (A)定义

使用一个宏定义的方式如下(本文只讨论if相关的用法,multi_compile等用法会在下篇讨论):

//在Pass内定义两个宏定义A B
            //使用空格分开AB,可以自定义命名,可以多个
            #pragma multi_compile A B
            fixed4 frag (v2f i) : SV_Target
            {
                #if defined (A) //如果定义了A则返回蓝色
                    return fixed4(0.0,0.0,1.0,1.0);//蓝
                #elif defined (B)//如果定义了B则返回红色
                    return fixed4(1.0,0.0,0.0,1.0);//红
                #else//如果都未定义则返回绿色
                    return fixed4(0.0,1.0,0.0,1.0);//绿
                #endif
            }

上述代码在Unity编辑器中会显示为蓝色,创建了一个最简单的宏定义,表达了如果定义(defined)了A则为蓝,如果定义了B则为红,否则为绿。逻辑很清晰,但唯一的问题是这里的(defined)即定义,到底什么是定义,怎样算定义了,怎样又不算定义。

- (defined)定义

Unity中定义的方法有如下几种:
1.和上述代码一样,使用#pragma multi_compile 等语法定义,此时会默认定义第一个,后面的都不会定义,因此如下代码输出为绿色:

//输出绿色
            #pragma multi_compile A B
            fixed4 frag (v2f i) : SV_Target
            {
                #if defined (B)
                    return fixed4(0.0,0.0,1.0,1.0);//蓝
                #elif defined (B)
                    return fixed4(1.0,0.0,0.0,1.0);//红
                #else
                    return fixed4(0.0,1.0,0.0,1.0);//绿
                #endif
            }

2.使用编辑器的debug模式,在材质球界面定义

1.修改属性面板为Debug模式
2.增加Shader Keywords,使用空格分开

这里要注意,在debug中增加关键字的前提是使用#pragma multi_compile A B描述了关键字,下面代码展示了两种不同的情况

//如果代码未描述关键字AB
            //在面板写啥都没用
            //输出绿色
            //#pragma multi_compile A B
            fixed4 frag (v2f i) : SV_Target
            {
                    #if defined (A)
                        return fixed4(0.0,0.0,1.0,1.0);//蓝
                    #elif defined (B)
                        return fixed4(1.0,0.0,0.0,1.0);//红
                    #else
                        return fixed4(0.0,1.0,0.0,1.0);//绿
                    #endif
            }

            //代码描述了关键字
            //且面板上仅写了B
            //输出红色
            #pragma multi_compile A B
            fixed4 frag (v2f i) : SV_Target
            {
                #if defined (A)
                    return fixed4(0.0,0.0,1.0,1.0);//蓝
                #elif defined (B)
                    return fixed4(1.0,0.0,0.0,1.0);//红
                #else
                    return fixed4(0.0,1.0,0.0,1.0);//绿
                #endif
            }

当然了,这种Debug模式并不是常规做法,因此不作深究。
3.在属性面板中描述定义

Properties
            {
                //在属性面板中描述定义了_TEST_A _TEST_B
                //此处的名字必须按这种前后分割的规则与下面对应
                //_TEST所有字母必须大写
                //KeywordEnum(括号内的可以小写但下面必须全部大写)
                [KeywordEnum(A,B)] _TEST("TEST", Float) = 0
            }
            ...
            //根据属性面板选择输出颜色
            #pragma multi_compile _TEST_A _TEST_B
            fixed4 frag (v2f i) : SV_Target
            {
                #if defined(_TEST_A)
                    return fixed4(0.0,0.0,1.0,1.0);//蓝
                #elif defined(_TEST_B)
                    return fixed4(1.0,0.0,0.0,1.0);//红
                #else
                    return fixed4(0.0,1.0,0.0,1.0);//绿
                #endif
            }
image.png
image.png

这种方法是我们自定义宏定义的时候最常用的方法,也是实际项目中使用最多的方法,手动去修改某个材质球的选项。
4.Material.EnableKeyword()、Material.DisableKeyword()、Shader.EnableKeyword()、Shader.DisableKeyword()
在C#代码中脚本来修改某个关键字的开关,尝试了以下代码并运行获得了以下结果:

void Start()
    {
        //根据材质关键字,这种调用方式仅在运行状态下有效,退出运行后恢复原状
        GetComponent<MeshRenderer>().material.EnableKeyword("B");
        //全局设置的着色器关键字,使用下面这种方法可以全局设置开启B,即便退出运行后也保持生效
        Shader.EnableKeyword("B");
    }

如果在编辑器下使用 Shader.EnableKeyword也是可以的,脚本注意要放到Editor文件夹下呀,每次执行完成后要点击一下Game界面刷新显示:

using UnityEngine;
using UnityEditor;

public class EditorHelp : Editor
{
    [MenuItem("Tools/EnableKeyword B")]
    public static void EnableKeywordB()
    {
        Shader.EnableKeyword("B");
    }

    [MenuItem("Tools/DisableKeyword B")]
    public static void DisableKeywordB()
    {
        Shader.DisableKeyword("B");
    }
}

由于Material.EnableKeyword写起来比较麻烦,项目中也不实用,暂时不测编辑器下使用情况了。
这种方法比较适合写编辑器工具的时候用。

二.#ifdef A

原理上#ifdef A 是#if defined (A)的简写,但这篇问题中提到如果用#ifdef A的话使用Shader.EnableKeyword来开关的时候会不起作用,这里尝试了一下Unity2021版本中仍然有此问题,使用#if defined (A)则没有此问题。
实际上#ifdef是一种较旧的写法,后续官方建议使用#if defined (A)这种写法。

三.#ifndef A

同上#ifndef A 是#if !defined (A)的简写,即#if defined (A)取反。

四.#if A

之前讲的#if defined (A)表示的是是否定义了A从而决定是否要在编译阶段控制代码中哪一部分会被编译,而#if A与之功能相同,但多了表达式的处理,如#if A>1,实际使用时情况如下:

//如按照以下写法
            //属性面板选B的时候输出蓝色,此时_TEST_A=0,_TEST_B=1
            //属性面板选A的时候输出红色,此时_TEST_A=1,_TEST_B=0
            #pragma multi_compile _TEST_A _TEST_B
            fixed4 frag (v2f i) : SV_Target
            {
                #if _TEST_A<1
                    return fixed4(0.0,0.0,1.0,1.0);//蓝
                #elif _TEST_B<1
                    return fixed4(1.0,0.0,0.0,1.0);//红
                #else
                    return fixed4(0.0,1.0,0.0,1.0);//绿
                #endif
            }

五.总结

综上所述,处理枚举总体上只建议使用#if defined (A)或#if !defined (A)的语法,其他语法均不太实用。而#if主要用在普通的宏定义上,不适合用在此处枚举宏定义使用。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值