在研究宏定义的时候发现,如果不先理解#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模式,在材质球界面定义
![](https://i-blog.csdnimg.cn/blog_migrate/7d2d783c0eca9a0664f8aa7b6b7f7760.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1a726b2d65d6a169bf685397184241e9.png)
这里要注意,在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
}
![](https://i-blog.csdnimg.cn/blog_migrate/34df34692ea586a4c3d665d0b63e637a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9e0f316a4bcdcf322da6fa8eb81ebd80.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主要用在普通的宏定义上,不适合用在此处枚举宏定义使用。