Unity shader中的#if和#ifdef

本文详细解析了Unity中的#if和#ifdef宏定义,包括它们的判断逻辑、定义与检测方法,以及在不同场景下的使用技巧,如ShaderKeywords、属性面板设置和C#脚本控制。重点介绍了ifdefined(A)的推荐使用和ifndefA的简写形式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在研究宏定义的时候发现,如果不先理解#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主要用在普通的宏定义上,不适合用在此处枚举宏定义使用。

<think>好的,我现在需要回答用户关于在Unity Shader中如何实现透视正交投影切换的问题。首先,用户提到的引用内容中提到了Projection Mode参数,这可能是一个关键点。 我记得在Unity中,摄像机的投影模式可以通过脚本来切换,但Shader中可能需要访问这个参数。可能需要使用Unity内置的变量,比如UNITY_MATRIX_P,它代表投影矩阵。当摄像机的投影模式改变时,这个矩阵也会相应变化。 接下来,用户需要在Shader中根据当前的投影模式调整渲染逻辑。比如,在顶点着色器中,可能需要不同的变换处理。或者可能需要通过条件判断来应用不同的计算方式。但Unity的着色器语言是否支持直接读取摄像机的投影模式呢? 查看引用[2],里面提到“Projection Mode相机当前的投影模式。0:透视投影。1:正射投影。”这可能意味着在Shader中可以通过某个变量来获取这个模式值。比如,Unity可能会将当前投影模式传递给Shader,比如使用unity_OrthoParams。这个参数的某个分量可能表示是否是正交投影。 然后,我需要验证一下,如何在Shader中检测当前是正交还是透视投影。查阅资料,发现unity_OrthoParams的x分量表示正交投影下的宽度,而如果摄像机是透视模式,这个值可能为0。或者可能有其他变量,比如_ProjectionParams,其中某个位表示投影类型。 此外,可能需要在Shader中使用#ifdef来判断,但可能更直接的是在运行时通过变量判断。比如,使用UNITY_MATRIX_P的第3行第4列元素是否为0,因为正交投影矩阵的该位置通常是0,而透视投影则为1。 不过这些方法可能需要进一步验证。例如,透视投影矩阵的最后一个元素([3][3])通常是0,而正交的是1?或者反过来?需要确认投影矩阵的结构。 假设正确的方式是通过检查UNITY_MATRIX_P的元素,那么在Shader中可以这样写: ``` bool isOrtho = UNITY_MATRIX_P[3][3] == 1.0; ``` 如果是正交投影,那么这个值会是1,否则为0。这可能是一个常用的方法。 接下来,用户可能需要根据不同的投影模式调整着色器的某些计算,比如深度值处理或者顶点变换。例如,在正交投影下,顶点的深度处理可能与透视不同,需要线性深度而不是透视校正的。 然后,用户可能需要在Shader中编写条件分支,根据isOrtho的值来选择不同的计算路径。但要注意,条件分支在Shader中可能影响性能,但如果是基于uniform变量,可能不会有太大问题。 最后,可能需要提供一个示例代码,展示如何在顶点或片段着色器中切换处理逻辑。例如,在顶点着色器中调整位置计算,或者在片段着色器中处理深度或光照。 总结步骤应该是:获取投影模式,根据模式调整着色器中的计算,并确保语法正确,使用正确的内置变量矩阵。</think>在Unity Shader中实现透视与正交投影的切换,核心在于检测当前相机的投影模式,并调整顶点变换或参数计算。以下是具体实现方法: --- ### **1. 检测投影模式** Unity的投影矩阵`UNITY_MATRIX_P`在不同模式下结构不同,可通过其元素判断: ```glsl bool isOrtho = (UNITY_MATRIX_P[3][3] == 1.0); ``` - **透视投影**:`UNITY_MATRIX_P[3][3] = 0` - **正交投影**:`UNITY_MATRIX_P[3][3] = 1`[^2] --- ### **2. 调整顶点变换** 在顶点着色器中,根据投影模式选择计算逻辑: ```glsl v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); // 默认变换 // 自定义处理(可选) if (isOrtho) { // 正交投影下的额外调整,如缩放或偏移 o.pos.xy *= _OrthoScale; } else { // 透视投影的特定计算 } return o; } ``` --- ### **3. 处理深度或光照** 在片段着色器中,可针对投影模式调整光照模型: ```glsl fixed4 frag(v2f i) : SV_Target { float depth = Linear01Depth(i.depth); // 统一深度采样 if (isOrtho) { // 正交投影下的线性深度处理 depth = (i.linearDepth - _OrthoNear) / (_OrthoFar - _OrthoNear); } // 其他光照计算... } ``` --- ### **4. 完整示例代码** ```glsl Shader "Custom/ProjectionSwitch" { Properties { _OrthoScale ("Orthographic Scale", Range(0.1, 2)) = 1.0 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; float linearDepth : TEXCOORD0; }; float _OrthoScale; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); bool isOrtho = (UNITY_MATRIX_P[3][3] == 1.0); if (isOrtho) { o.pos.xy *= _OrthoScale; // 正交模式下的缩放 } o.linearDepth = o.pos.z / o.pos.w; // 记录线性深度 return o; } fixed4 frag(v2f i) : SV_Target { bool isOrtho = (UNITY_MATRIX_P[3][3] == 1.0); float depth = Linear01Depth(i.linearDepth); if (isOrtho) { // 正交投影下的颜色调整示例 return fixed4(0.5, 0.8, 1.0, 1.0) * (1 - depth); } else { return fixed4(1.0, 0.5, 0.2, 1.0) * depth; } } ENDCG } } } ``` --- ### **关键点说明** 1. **内置变量**:`UNITY_MATRIX_P`是当前相机的投影矩阵,其结构差异用于判断模式[^2]。 2. **深度处理**:正交投影的深度是线性的,而透视投影需要透视校正,可通过`Linear01Depth`统一处理。 3. **参数传递**:通过`_OrthoScale`等属性控制正交模式下的视觉效果。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值