[UnityShader入门精要读书笔记]06.顶点/片元着色器基本结构
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语义块。由CGPROGRAM 和ENDCG所包围的CG代码片段,里边有两条非常重要的渲染指令,
#pragma vertex vert
#pragma fragment frag
它将告诉Unity ,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码。
这两个函数不一定是,vert和frag,但通常使用这两个名字命名,首先看下vert函数的定义:
float4 vert(float4 v : POSITION) : SV_POSITION{
return mul(UNITY_MATRIX_MVP, v);
}
顶点着色器是逐顶点执行的,vert函数的输入v包含了这个顶点的位置,这是通过POSITION语义指定的。它的返回值是一个float4类型的变量,它是该顶点在裁剪空间中的位置,POSITION和SV_POSITION都是CG/HLSL中的语义,他们是不可省略的。POSITION将告诉Unity,把模型的顶点坐标填充到输入参数v中,SV_POSITION将高速Unity,顶点着色器的输出是裁剪空间中的顶点坐标。
再看下frag函数的定义:
fixed4 frag() : SV_Target{
return fixed4(1.0,1.0,1.0,1.0);
}
例子中,frag函数没有任何输入,它的输出是一个fixed4类型的变量。并且使用了SV_Target语义进行限定。SV_Target也是HLSL中的一个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓存中。
Shader "Unlit/simple shader"
{
SubShader
{
Pass
{
CGPROGRAM
#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;
};
float4 vert (a2v v) : SV_POSITION
{
//使用v.vertex来访问模型空间的顶点坐标。
return mul(UNITY_MATRIX_MVP,v.vertex);
}
fixed4 frag () : SV_Target
{
return fixed4(1.0,1.0,1.0,1.0);
}
ENDCG
}
}
}
在上面的代码中,我们定义了一个新的结构体a2v,它包含了顶点着色器需要的模型数据,在a2v的定义中,我们用到了更多UNITY支持的语义。如NORMAL和TEXCOORD,当它们作为顶点着色器输入时都有特定含义的,因为Unity会根据这些语义来填充这个结构体,对于顶点着色器的输出,Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,COLOR等。
在实践中,我们希望从顶点着色器输出一些数据,例如把模型的法线、纹理坐标等传递给片元着色器。
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/simple shader"
{
SubShader
{
Pass
{
CGPROGRAM
#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_POSITIO;
//COLOR0语义用于存储颜色信息。
fixed3 color : COLOR0;
};
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, 0.5);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//将插值后的i.color显示到屏幕上
return fixed4(i.color,1.0);
}
ENDCG
}
}
}
在上面的代码中,我们声明了一个新的结构体v2f。v2f用在顶点着色器和片元着色器之间传递信息。同样的,v2f中也需要指定每个变量的语义。在本例中,我们使用了SV_POSITION和COLOR语义。顶点着色器的输出结构中,必须包含一个变量,它的语义是SV_POSITION。否则,渲染器将无法得到裁剪空间中的顶点坐标,也就无法把顶点渲染到屏幕上。COLOR0语义中的数据则可以由用户自定义,但一般都是存储颜色。
顶点着色器是逐顶点调用的,而片元着色器是逐片元调用的。片元着色器中的输入实际上是把顶点着色器的输出进行插值后得到的结果。
有时CG变量前边会有一个uniform关键字,例如
uniform fixed4 _Color;
uniform关键词是CG中修饰变量和参数的一种修饰词,它仅仅用于提供一些关于该变量的初始值是如何指定和存储的相关信息,在UnityShader中,uniform可以省略。