本教程将教您如何在 Unity 着色器中编写顶点和片元程序的基础知识。有关 ShaderLab 的基本介绍,请参阅入门教程。如果要编写与光照交互的着色器,请阅读表面着色器。
让我们先简单回顾一下着色器的一般结构:
Shader "MyShaderName"
{
Properties
{
// 此处为材质属性
}
SubShader // 图形硬件 A 的子着色器
{
Pass
{
// 通道命令 ...
}
// 更多通道(如果需要)
}
// 更多子着色器(如果需要)
FallBack "VertexLit" // 可选回退
}
最后我们介绍一个新命令:__FallBack “VertexLit”。Fallback 命令可以在着色器的末尾使用;如果当前着色器中没有__子着色器__可以在用户的图形硬件上运行,该命令会告诉应该使用哪个着色器。效果等同于在结尾包含回退着色器中的所有子着色器。例如,如果您要编写一个花哨的法线贴图着色器,那么您可以回退到内置的 VertexLit__ 着色器,而不必为旧显卡编写一个非常基本的非法线贴图子着色器。
第一个着色器教程中介绍了着色器的基本构建块,同时也提供了关于属性、子着色器和通道的完整文档。
构建子着色器的一种快速方法是使用在其他着色器中定义的通道。命令 UsePass 就能执行此操作,因此您可以通过简洁的方式重用着色器代码。例如,以下命令使用内置 Specular 着色器中名为“FORWARD”的通道: UsePass “Specular/FORWARD”。
要让 UsePass 运行,必须为要使用的通道提供一个名称。通道中的 Name 命令将为其提供名称:__Name “MyPassName”__。
我们在第一个教程中描述了仅使用单个纹理组合指令的通道。现在演示如何在通道中使用顶点和片元程序。
使用顶点和片元程序(所谓的“可编程管线”)时,图形硬件中的大多数硬编码功能(“固定函数管线”)将被关闭。例如,使用顶点程序会完全关闭标准 3D 变换、光照和纹理坐标生成。同样,使用片元程序会取代将在 SetTexture 命令中定义的任何纹理组合模式;因此不需要 SetTexture 命令。
编写顶点/片元程序需要精通 3D 变换、光照和坐标空间,因为您必须自己重写 OpenGL等 API 中内置的固定功能。另一方面,您不仅仅可以使用内置功能!
ShaderLab 中的着色器通常是用 Cg/HLSL 编程语言编写的。Cg 和 DX9 风格的 HLSL 实际上是同一种语言,所以我们将互换使用 Cg 和 HLSL(有关详细信息,请参阅本页)。
着色器代码是通过在着色器文本中嵌入“Cg/HLSL 代码片段”来编写的。Unity Editor 将代码片段编译为低级着色器程序集,游戏数据文件中包含的最终着色器仅包含此低级程序集或字节码(根据平台而定)。在 __Project 视图__中选择着色器时,检视面板有一个按钮可显示已编译的着色器代码,这可以作为调试助手。Unity 自动编译所有相关平台(Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)的 Cg 代码片段。请注意,由于 Cg/HLSL 代码是由 Editor 编译的,因此无法在运行时从脚本创建着色器。
通常,代码片段放在 Pass 代码块内。如下所示:
Pass {
// ...常规通道状态设置 ...
CGPROGRAM
// 此代码片段的编译指令,例如:
#pragma vertex vert
#pragma fragment frag
// Cg/HLSL 代码本身
ENDCG
// ...通道设置的剩余部分 ...
}
以下示例演示了一个完整的着色器,它将对象法线渲染为彩色:
Shader "Tutorial/Display Normals" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
ENDCG
}
}
}
当应用于对象时,它将生成如下图像:
我们的“Display Normals”着色器没有任何属性,包含一个子着色器,子着色器只有一个通道,通道中除了 Cg/HLSL 代码外无任何其他内容。让我们逐个剖析代码:
CGPROGRAM
# pragma vertex vert
# pragma fragment frag
// ...
ENDCG
整个代码片段写在 CGPROGRAM 与 ENDCG 关键字之间。在开头,编译指令以 #pragma 语句形式给出:
编译之后的指令只是普通的 Cg/HLSL 代码。我们首先包含一个内置 include 文件:
# include "UnityCG.cginc"
UnityCG.cginc 文件包含常用的声明和函数,以便着色器可以保持较小(有关详细信息,请参阅着色器 include 文件页面)。这里我们将使用该文件中的 appdata_base 结构。我们可以直接在着色器中定义它们,当然不包括文件。
接下来我们定义一个“顶点到片元”结构(这里名为 __v2f__):从顶点传递给片元程序的信息。我们将传递位置和颜色参数。颜色将在顶点程序中计算,并在片元程序中输出。
我们继续定义顶点程序,即 vert 函数。在这里,我们将位置和输出输入法线计算为颜色: o.color = v.normal * 0.5 + 0.5;
法线分量在 –1 到 1 范围内,而颜色在 0 到 1 范围内,因此我们在上面的代码中缩放和偏置法线。接下来我们定义一个片元程序,即 frag 函数,该函数只输出计算所得颜色,以 1 为 Alpha 分量:
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
就是这样,我们的着色器完成了!即使这个简单的着色器对于可视化网格法线也非常有用。
当然,这个着色器根本不会对光源做出响应,这就是事情变得有趣的地方;有关详细信息,请参阅表面着色器。
在着色器中定义属性时,您将为它们指定一个名称,如 _Color 或 _MainTex。要在 Cg/HLSL 中使用它们,只需定义匹配名称和类型的变量。有关详细信息,请参阅着色器程序中的属性页面。
下面是一个完整的着色器,显示由颜色调制的纹理。当然,您也可以在纹理组合器调用中轻松进行相同操作,但这里的重点只是展示如何在 Cg 中使用属性:
Shader "Tutorial/Textured Colored" {
Properties {
_Color ("Main Color", Color) = (1,1,1,0.5)
_MainTex ("Texture", 2D) = "white" { }
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 _MainTex_ST;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 texcol = tex2D (_MainTex, i.uv);
return texcol * _Color;
}
ENDCG
}
}
}
此着色器的结构与前一个示例中的相同。这里我们定义两个属性,即 _Color 和 _MainTex。在 Cg/HLSL 代码中,我们定义了相应的变量:
fixed4 _Color;
sampler2D _MainTex;
有关更多信息,请参阅使用 Cg/HLSL 访问着色器属性。
这里的顶点和片元程序没有任何花哨的东西;顶点程序使用 UnityCG.cginc 中的 TRANSFORM_TEX 宏来确保正确应用纹理比例和偏移,片元程序只是对纹理进行采样并乘以颜色属性。
我们已经展示了如何通过几个简单的步骤编写自定义着色器程序。虽然这里显示的示例非常简单,但您可以编写复杂的任意着色器程序!这可以帮助您充分利用 Unity 并获得最佳渲染效果。
此处是完整的 ShaderLab 参考手册,顶点和片元着色器程序示例页面中提供了更多示例。我们还在 forum.unity3d.com 上有一个着色器论坛,请访问这个论坛以获取与着色器相关的帮助!快乐编程,享受 Unity 和 ShaderLab 的强大功能。