5.2 一个最简单的顶点/片元着色器
5.2.1 顶点/片元着色器的基本结构
一个 Unity Shader 的基本结构。它包含了 Shader、Properties、SubShader、Fallback 等语义块。顶点/片元着色器的结构与之大体类似,它的结构如下:
Shader "MyShaderName"{
Properties{
//属性
}
SubShader{
//针对显卡A 的 SubShader
Pass{
//设置渲染状态的标签
//开始 CG 代码片段
CGPROGRAM
//该代码片段的编译指令,例如:
#pragma vertex vert
#pragma fragment frag
//CG 代码写在这里
ENDCG
//其他设置
}
// 其他需要的 Pass
}
SubShader {
//针对显卡 B 的 SubShader
}
// 上述SubShader 都失败后用于回调的 Unity Shader
Fallback "VertexLit"
}
其中,最重要的部分是 Pass 语义块,我们绝大部分的代码都是卸载这个语义块里面的。下面我们就来创建一个最简单的顶点/片元着色器。
// 代码的第一行通过shader语义定义了这个 Unity Shader 的名字
Shader "Unity Shaders Books/Chapter 5/Simple Shader" {
SubShader {
Pass{
//由CGPROGRAM 和 ENDCG 锁包围的 CG 代码片段。
CGPROGRAM
//它们将告诉 Unity ,vert函数包含了顶点着色器的代码 frag函数包含了片元着色器的代码。
#pragma vertex vert
#pragma fragment frag
//这就是本例使用的顶点着色器代码,它是逐顶点执行的。
//vert 寒色的输入v 包含了这个顶点的位置,这是通过 POSITION 语义指定的。
//它的返回值是一个 float4 类型的变量,它是该顶点在裁剪空间中的位置,
//POSITION 和 SV_POSITION 都是由 CG/HLSL 中的语义,它是不可省略的
//这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。
//例如这里,POSITIION 将告诉 Unity ,把模型的顶点坐标填充到输入参数 v中,
//SV_POSITION 将告诉 Unity,顶点着色器的输出是裁剪空间空的顶点坐标。
//如果没有这些语义来限制输入和输出参数的话,渲染器就完全不知道用户的和输入输出是什么
float4 vert(float4 v:POSITION) : SV_POSITION {
//这一步就是把顶点坐标从模型空间转换到裁剪空间中,
//UNITY_MATRIX_MVP 矩阵是 Unity 内置的模型*观察*投影矩阵
return mul(UNITY_MATRIX_MVP, v);
}
//在本例中,frag 函数没有任何输入,它的输出是一个 fixed4 类型的变量,
//并且使用了 SV_Target 语义进行限定。
//SV_Target 也是 HLSL中的一个系统语义,
//它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标(render target)中
//这里将输出到默认的帧缓存中。
fixed4 frag():SV_Target {
//返回一个表示白的的 fixed4 类型的变量。
//片元着色器输出的颜色的每一个分量范围在 [0,1],其中(0,0,0)黑色,(1,1,1)白色
return fixed4(1.0, 1.0,1.0 ,1.0);
}
ENDCG
}
}
}
5.2.2 模型数据从哪里来
现在我们想要得到模型上每一个顶点的纹理坐标和法线防线。使用纹理坐标来访问纹理,而法线可用于计算光照。因此我们想要为顶点着色器定义一个新的输入参数,这个参数不再是一个简单的数据模型,而是一个结构体。修改后的代码如下:
// 代码的第一行通过shader语义定义了这个 Unity Shader 的名字
Shader "Unity Shaders Books/Chapter 5/Simple Shader" {
SubShader {
Pass{
//由CGPROGRAM 和 ENDCG 锁包围的 CG 代码片段。
CGPROGRAM
//它们将告诉 Unity ,vert函数包含了顶点着色器的代码 frag函数包含了片元着色器的代码。
#pragma vertex vert
#pragma fragment frag
// 使用一个结构体来定义顶点着色器的输入
struct a2v {
// POSITION 语义告诉 Unity ,用模型空间的顶点坐标填充vertex变量
float4 vertex:POSITION;
// NORMAL 语义告诉unity ,用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
// TEXCOORD0 语义告诉 Unity,用模型的第一套纹理坐标填充 texcoord 变量
float4 texcoord:TEXCOORD0;
};
// 修改了 vert 函数的输入参数类型,把它设置为我们新定义的结构体 a2f,
//通过这种自定义结构体的方式,我们就可以在顶点着色器中访问模型数据。
float4 vert(a2v v) : SV_POSITION {
//使用 v.vertex 来访问模型空间的顶点坐标 从模型空间转换到裁剪空间中,
//UNITY_MATRIX_MVP 矩阵是 Unity 内置的模型*观察*投影矩阵
return mul(UNITY_MATRIX_MVP, v.vertex);
}
//在本例中,frag 函数没有任何输入,它的输出是一个 fixed4 类型的变量,
//并且使用了 SV_Target 语义进行限定。
//SV_Target 也是 HLSL中的一个系统语义,
//它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标(render target)中
//这里将输出到默认的帧缓存中。
fixed4 frag():SV_Target {
//返回一个表示白的的 fixed4 类型的变量。
//片元着色器输出的颜色的每一个分量范围在 [0,1],其中(0,0,0)黑色,(1,1,1)白色
return fixed4(1.0, 1.0,1.0 ,1.0);
}
ENDCG
}
}
}
5.2.3 顶点着色器和片元着色器通信
// 代码的第一行通过shader语义定义了这个 Unity Shader 的名字
Shader "Unity Shaders Books/Chapter 5/Simple Shader" {
SubShader {
Pass{
//由CGPROGRAM 和 ENDCG 锁包围的 CG 代码片段。
CGPROGRAM
//它们将告诉 Unity ,vert函数包含了顶点着色器的代码 frag函数包含了片元着色器的代码。
#pragma vertex vert
#pragma fragment frag
// 使用一个结构体来定义顶点着色器的输入
struct a2v {
// POSITION 语义告诉 Unity ,用模型空间的顶点坐标填充vertex变量
float4 vertex:POSITION;
// NORMAL 语义告诉unity ,用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
// TEXCOORD0 语义告诉 Unity,用模型的第一套纹理坐标填充 texcoord 变量
float4 texcoord:TEXCOORD0;
};
// 使用一个结构体来定义顶点着色器的输出
struct v2f {
// SV_POSITION 语义高斯 Unity ,pos 里包含了顶点在裁剪空间中的位置信息
float4 pos:SV_POSITION;
// COLOR0 语义可以用于存储颜色信息
fixed3 color:COLOR0;
};
// 修改了 vert 函数的输入参数类型,把它设置为我们新定义的结构体 a2f,
//通过这种自定义结构体的方式,我们就可以在顶点着色器中访问模型数据。
float4 vert(a2v v) : SV_POSITION {
// 声明输出结构
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
// v.normal 包含了顶点的法线方向,其分量范围在[-1.0,1.0]
// 下面的代码把分量范围映射到了[0.0 ,1.0]
// 存储到o.color 中传递到片元着色器
o.color = v.normal*0.5 + fixed3(0.5,0.5,0.5);
return o;
}
// 片元着色的输入实际上是把顶点着色器的输出进行插值后得到的结果。
fixed4 frag(v2f i):SV_Target {
//将插值后的 i.color 显示到屏幕上
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
5.2.4 使用属性
// 代码的第一行通过shader语义定义了这个 Unity Shader 的名字
Shader "Unity Shaders Books/Chapter 5/Simple Shader" {
Properties {
// 声明一个 Color 类型的属性、
_Color( "Color Tint",Color) = (1.0,1.0,1.0,1.0)
}
SubShader {
Pass{
//由CGPROGRAM 和 ENDCG 锁包围的 CG 代码片段。
CGPROGRAM
//它们将告诉 Unity ,vert函数包含了顶点着色器的代码 frag函数包含了片元着色器的代码。
#pragma vertex vert
#pragma fragment frag
// 在 CG 代码中,我们需要定义一个与属性名称和类型都匹配的变量
fixed4 _Color;
// 使用一个结构体来定义顶点着色器的输入
struct a2v {
// POSITION 语义告诉 Unity ,用模型空间的顶点坐标填充vertex变量
float4 vertex:POSITION;
// NORMAL 语义告诉unity ,用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
// TEXCOORD0 语义告诉 Unity,用模型的第一套纹理坐标填充 texcoord 变量
float4 texcoord:TEXCOORD0;
};
// 使用一个结构体来定义顶点着色器的输出
struct v2f {
// SV_POSITION 语义高斯 Unity ,pos 里包含了顶点在裁剪空间中的位置信息
float4 pos:SV_POSITION;
// COLOR0 语义可以用于存储颜色信息
fixed3 color:COLOR0;
};
// 修改了 vert 函数的输入参数类型,把它设置为我们新定义的结构体 a2f,
//通过这种自定义结构体的方式,我们就可以在顶点着色器中访问模型数据。
float4 vert(a2v v) : SV_POSITION {
// 声明输出结构
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
// v.normal 包含了顶点的法线方向,其分量范围在[-1.0,1.0]
// 下面的代码把分量范围映射到了[0.0 ,1.0]
// 存储到o.color 中传递到片元着色器
o.color = v.normal*0.5 + fixed3(0.5,0.5,0.5);
return o;
}
// 片元着色的输入实际上是把顶点着色器的输出进行插值后得到的结果。
fixed4 frag(v2f i):SV_Target {
fixed3 c = i.color;
// 使用 _Color 属性来控制输出颜色
c *= _Color.rgb;
return fixed4(c,1.0);
}
ENDCG
}
}
}
参考
Unity Shader入门精要
作者:冯乐乐