3.自定义材质面板-自带Shader材质属性绘制类-Shader变体和关键字
3.1 知识点
Shader中的变体是什么
Shader Variant(变体)指的是一个 Shader 的特定配置,通过不同的关键字(Keywords)和设置组合来实现不同的效果。每一种关键字组合或设置都会生成一个独立的 Shader 变体,最终以二进制形式存储在构建文件中供运行时使用。
说人话:Shader 变体就是基于一个 Shader 文件中的代码,编译生成多个版本的 Shader,它基于关键字来生成各种不同的版本。
举例说明:
一个 Shader 中有两个关键字,每个关键字有两种状态(启用或禁用),最终生成的变体数量为 2² = 4
- 变体 1:没有启用任何关键字(默认)。
- 变体 2:启用了关键字 1。
- 变体 3:启用了关键字 2。
- 变体 4:同时启用了关键字 1 和关键字 2。
运行时,Unity 会根据具体设置选择与之匹配的变体来使用。
Unity 的一些内置功能会隐式生成变体,比如光照模式、雾效、渲染管线等。
局部Shader关键字
局部关键字的声明有两种方式:
- 使用
#pragma shader_feature
来声明关键字,此方式仅在关键字启用时生成对应的 Shader 变体。 - 使用
#pragma multi_compile
来声明关键字,此方式无论关键字是否启用都会生成对应的 Shader 变体。
关键词的命名规则通常采用大写字母和下划线分隔(例如:_Test_Keyword)。
两种声明方式的主要区别在于生成 Shader 变体的数量:
- shader_feature:编译更快,Shader 文件更小,适用于开关功能较少的场景。
- multi_compile:编译时间较长,Shader 文件更大,适用于需要完整覆盖功能的场景。
在使用时,通过条件编译指令判断关键字的启用状态,例如:
#if defined(关键词名)
// ...
#elif defined(关键词名)
// ...
#else
// ...
#endif
以根据关键字的状态处理不同的逻辑。
示例根据不同编译指令生成变体的Shader代码
Shader "Unlit/Lesson03_Shader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
// 该编译指令声明的关键字,只有关键词启用时才会生成对应 Shader 变体
// #pragma shader_feature _TEST_KEYWORD1 _TEST_KEYWORD2
// 按片元着色器的示例,如果都不启用,则只生成一个变体
// 该编译指令声明的关键字,不管是否启用都会生成对应 Shader 变体
#pragma multi_compile _TEST_KEYWORD1 _TEST_KEYWORD2
// 按片元着色器的示例,如果都不启用,还是会生成四个变体
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// 分支判断示例(注释掉的部分为可选逻辑)
// #if defined(_TEST_KEYWORD1)
// ...
// #elif defined(_TEST_KEYWORD2)
// ...
// #else
// ...
// #endif
// 逻辑判断:基于不同的关键字可能产生四种不同的变体
fixed4 col = fixed4(1, 1, 1, 1); // 默认颜色白色
#if defined(_TEST_KEYWORD1)
col = tex2D(_MainTex, i.uv); // 取纹理颜色
#endif
#if defined(_TEST_KEYWORD2)
UNITY_APPLY_FOG(i.fogCoord, col); // 应用雾效
#endif
return col;
}
ENDCG
}
}
}
Shader中利用C#代码启用和禁用关键字
Shader 中的关键字是一种用于控制 Shader 变体的机制,允许我们通过启用或禁用特定功能来动态调整 Shader 的行为。
Shader 中的关键字可以分为两种:
- 全局关键字:Unity 自带的关键字,所有 Shader 中共享。可通过 C# 代码调用
来控制关键字的启用和禁用。Shader.EnableKeyword("全局关键字名"); Shader.DisableKeyword("全局关键字名");
- 局部关键字:仅在特定 Shader 中有效,不会影响其他 Shader。Unity 推荐使用局部关键字以减少关键字冲突问题。
可在 C# 代码中通过材质球的方式调用
来控制关键字的启用和禁用。material.EnableKeyword("局部关键字名"); material.DisableKeyword("局部关键字名");
内置关键字
变体和条件分支的区别
条件分支语句会带来逻辑上的执行开销,虽然运行时直接判断,但仍会对性能产生影响。
而 Shader 变体虽然会增加编译时间并占用更多的存储内容,但其性能较高,灵活性强。在静态功能选择时(例如 Shader 在运行时不会动态切换功能的场景),使用变体更优;而在需要动态切换功能的场景下,条件分支语句则更适合。
总结
Shader 变体是在编译时基于关键字生成多个版本的 Shader 代码,在运行时根据关键字状态选择相应的版本来渲染物体。
3.2 知识点代码
Lesson03_Shader.shader
Shader "Unlit/Lesson03_Shader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
//该编译指令声明的关键字,只有关键词启用时才会生成对应Shader变体
// #pragma shader_feature _TEST_KEYWORD1 _TEST_KEYWORD2
//按片元着色器的示例,如果都不启用,则只生成一个变体
//该编译指令声明的关键字,不管是否启用都会生成对应Shader变体
#pragma multi_compile _TEST_KEYWORD1 _TEST_KEYWORD2
//按片元着色器的示例,如果都不启用,还是会生成四个变体
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o, o.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// 分支判断
// #if defined(_TEST_KEYWORD1)
//
// #elif defined(_TEST_KEYWORD2)
//
// #else
//
// #endif
// 逻辑判断 基于不同的关键字可能产生四种不同的变体
fixed4 col = fixed4(1, 1, 1, 1); // 默认颜色白色
#if defined(_TEST_KEYWORD1)
col = tex2D(_MainTex, i.uv); // 取纹理颜色
#endif
#if defined(_TEST_KEYWORD2)
UNITY_APPLY_FOG(i.fogCoord, col);// 应用雾效
#endif
// // sample the texture
// fixed4 col = tex2D(_MainTex, i.uv);
// // apply fog
// UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
Lesson03_自定义材质面板_自带Shader材质属性绘制类_Shader变体和关键字.cs
using UnityEngine;
public class Lesson03_自定义材质面板_自带Shader材质属性绘制类_Shader变体和关键字 : MonoBehaviour
{
void Start()
{
#region 知识点一 Shader中的变体是什么?
//Shader Variant(变体)指的是一个Shader的特定配置
//通过不同的关键字(Keywords)和 设置组合 来实现不同的效果
//每一种关键字组合或设置都会生成一个独立的Shader变体
//最终以二进制形式存储在构建文件中供运行时使用
//说人话:
//Shader变体就是基于一个Shader文件当中的代码,编译生成多个版本的Shader
//它基于关键字来生成各种不同的版本
//举例说明
//一个Shader中有两个关键字
//每个关键字有两种状态(启用或禁用)
//最终生成的变体数量为2²=4
//变体 1:没有启用任何关键字(默认)。
//变体 2:启用了 关键字1。
//变体 3:启用了 关键字2。
//变体 4:同时启用了 关键字1 和 关键字2
//运行时,Unity会根据具体设置选择与之匹配的变体来使用
//Unity的一些内置功能会隐式的生成变体
//比如
//光照模式
//雾效
//渲染管线
//等等
#endregion
#region 知识点二 局部Shader关键字
//1.声明:
//声明局部关键字有两种方式
// #pragma shader_feature 或 #pragma multi_compile 来声明关键词
// 1-1.#pragma shader_feature 关键词1 关键词2 关键词3....
// 作用:
// 该编译指令声明的关键字,只有关键词启用时才会生成对应Shader变体
// 1-2.#pragma multi_compile 关键词1 关键词2 关键词3....
// 作用:
// 该编译指令声明的关键字,不管是否启用都会生成对应Shader变体
//关键词命名规则
//通常使用大写字母和下划线分隔(_大写单词_大写单词...)
//比如:_Test_Keyword
//两种声明方式的区别:
//主要区别在于 生成Shader变体的数量
//shader_feature:
//编译更快,Shader文件更小,适用开关功能较少的场景
//multi_compile:
//编译更长,Shader文件更大,适用需要完整覆盖的功能
//2.使用
//关键字使用规则:
// 利用
// #if defined(关键词名)
// ....
// #elif defined(关键词名)
// ....
// #else
// ....
// #endif
// 配合使用,判断关键词启用时处理什么逻辑
#endregion
#region 知识点三 Shader中利用C#代码启用和禁用关键字
//Unity Shader中的关键字是一种用于控制Shader变体的机制
//允许我们通过启用或者禁用特定功能来动态调整Shader的行为
//Shader中的关键字可以分为2种
//1.全局关键字
//Unity自带的关键字,所有Shader中共享
//可以在C#代码中通过
//Shader.EnableKeyword("全局关键字名")
//Shader.DisableKeyword("全局关键字名")
//来控制关键字的启用和禁用
//2.局部关键字
//只在特定的Shader中有效,不会影响其他Shader
//Unity更推荐使用局部关键字以减少关键字冲突问题
//可以在C#代码中通过
//材质球.EnableKeyword("局部关键字名")
//材质球.DisableKeyword("局部关键字名")
//来控制关键字的启用和禁用
#endregion
#region 知识点四 变体和条件分支的区别
//条件分支语句会带来逻辑上执行的开销
//虽然运行时直接判断但是会带来对性能的影响
//变体虽然会增加编译时间并占用更多的内容
//但是它的性能高,灵活性强
//在静态功能选择时变体更优 比如随着游戏运行这个Shader不会动态切换的
//在动态功能选择时条件分支语句更适合 比如随着游戏运行这个Shader会随时动态切换的
#endregion
#region 总结
//Shader 变体就是在编译时基于关键字生成多个版本的 Shader 代码,
//在运行时根据关键字状态选择相应的版本来渲染物体。
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com