根据我们上一次学习的Shader的结构,我们来编写我们的第一个Shader:
Shader "Custom/TestShader" {
Properties{
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert(float4 v:POSITION):SV_POSITION{
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_Target{
return fixed4(1,1,1,1);
}
ENDCG
}
}
}
在代码都在Pass块中,Pass中所有代码都必须在CGPROGRAM与ENDCG之间:
CGPROGRAM
XXX
ENDCG
紧接着的是两个#pragma语句,#pragma vertex vert是声明顶点着色器函数的语句,而#pragma fragment frag是声明片元着色器的语句,其中的vert与frag是两个函数的函数名,是可变的。
#pragma vertex name1
#pragma fragment name2
然后是vert函数与frag函数的实现,vert的输入是一个float4类型的数,它的语义是POSITION即一个顶点位置。它的返回值也是一个float4类型的数,语义为SV_POSITION即裁剪空间的一个坐标。内部的处理是UnityObjectToClipPos(v),是把顶点位置从模型空间变换到裁剪空间,等同于mul(UNITY_MATRIX_MVP, v)。frag函数没有输入,它的输出是一个fixed4类型的数,语义为SV_TARGET,是指把用户输出的颜色存储到一个渲染目标中。
float4 vert(float4 v:POSITION):SV_POSITION{
return UnityObjectToClipPos(v);
}
fixed4 frag():SV_Target{
return fixed4(1,1,1,1);
}
这样我们就成功编写了最简单的Shader,在Unity中创建一个新的材质,采用我们的这个Shader,然后将这个材质拖动到Cube和Sphere上,就可以看到效果了:
但是这时我们会发现很多的问题,比如我们顶点函数获取的输入只有一个顶点坐标位置,能处理的信息非常有限,而且我们的Properties还是空的,所以我们要学习一个更常用的框架结构。
Shader "Custom/SimpleShader" {
Properties{
_Color("Color Tint",Color) = (1,1,1,1)
}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
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;
c *= _Color.rgb;
return fixed4(c,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
相比于我们的TestShader,SimpleShader中做了如下改动:
在Properties中定义了一个变量_Color
vert与frag函数的输入输出
vert与frag函数的内部处理
Properties中定义的变量_Color,其名字是Color Tint,类型是Color,用四维数(1,1,1,1)赋值。注意句末不要加分号!
_Color("Color Tint",Color) = (1,1,1,1)
vert函数的输入是一个新定义的结构a2v,其含义是application(应用)到vertex(顶点处理)。返回值为v2f,其含义为vertex(顶点处理)到fragment(片元处理)。
在a2v中定义了顶点位置vertex,其语义为POSITION,就是TestShader中vert函数的输入。然后我们还定义了 法线normal:NORMAL 与 第一套纹理坐标texcoord:TEXCOORD。
在v2f中定义了裁剪空间坐标pos:SV_POSITION及存储颜色信息的color:COLOR0。
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
fixed3 color:COLOR0;
};
vert函数中计算了裁剪空间的坐标并传递给v2f结构中的pos,然后将法线分量范围从[-1,1]变换到[0,1],传递给v2f结构中的color。
frag函数中根据vert中法线计算得到的color与属性中的_Color计算,注意_Color需要在Pass中声明并且名称与Properties中的一样。
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
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;
c *= _Color.rgb;
return fixed4(c,1.0);
}
这样我们得到了一个具有一定通用性结构的Shader,可以观察其效果:
最后,我们来学习一下Unity一些内置变量及函数:
appdata_base 用于顶点着色器的输入 包含顶点位置,顶点法线,第一组纹理坐标
appdata_tan 用于顶点着色器的输入 包含顶点位置,顶点切线,顶点法线,第一组纹理坐标
appdata_full 用于顶点着色器的输入 包含顶点位置,顶点切线,顶点法线,四组(或更多)纹理坐标
appdata_img 用于顶点着色器的输入 包含顶点位置,第一组纹理坐标
v2f_img 用于顶点着色器的输出,片元着色器的输入 包含裁剪空间中的位置,纹理坐标
float3 WorldSpaceViewDir(float4) 输入一个模型空间的顶点位置,返回其 世界空间 中 从该点到摄像机 的方向
float3 ObjSpaceViewDir(float4) 输入一个模型空间的顶点位置,返回其 模型空间 中 从该点到摄像机 的方向
float3 WorldSpaceLightDir(float4) 输入一个模型空间的顶点位置,返回其 世界空间 中 从该点到光源 的方向(仅在前向渲染中)
float3 ObjSpaceLightDir(float4) 输入一个模型空间的顶点位置,返回其 模型空间 中 从该点到光源 的方向(仅在前向渲染中)
float3 UnityObjectToWorldNormal(float3) 用于将法线从模型空间变换到世界空间
float3 UnityObjectToWorldDir(float3) 用于将方向矢量从模型空间变换到世界空间
float3 UnityWorldToObjDir(float3) 用于将方向矢量从世界空间变换到模型空间