下面的Shader代码是一个比较基础的Shader的伪代码表示 :
Shader "Shader Name" {
Properties {
propertiesName1("display name1", PropertyTypeA) = DefaultValueA
propertiesName2("display name2", PropertyTypeB) = DefaultValueB
}
SubShaderA {
[Tags]
[RenderSetup]
Pass {
CDPROGRAM
#include "xxx.cginc"
ENDCG
}
}
SubShaderB {
//...
}
Fallback ""
}
Properties
Properties语义块支持的属性类型:
Shader "ShaderName" {
Properties {
_Int("Int", Int) = 2
_Float("Float",Float) = 1.5
_Range("Range", Range(0.0, 5.0)) = 3.0
_Color("Color", Color) = (1, 1, 1, 1)
_Vector("Vector", Vector) = (1, 1, 1, 1)
// Textures
_2D("2D", 2D) = ""{}
_Cube("Cube", Cube) = "White"{}
_3D("3D", 3D) = "black"{}
}
}
Pass
顶点着色器和片元着色器都写在Pass内,Pass定义了一次完整的渲染流程。
Pass语义如下:
Pass里也包含了自己的标签,标签类型有:
SubShader
每个UnityShader文件可以包含多个SubShader,至少包含一个:
在SubShader中定义一系列Pass,以级可选的RederSetup和Tags。但Pass数目过多,往往会导致渲染性能的下降。
RenderSetup
常见的渲染状态设置指令:
如果在SubShader中设置了上述的渲染状态时,会应用的所有的Pass中。我们也可以在设置了这些状态时,如果希望在某个Pass中不使用该渲染指令,可以单独设置RenderSetup。
Fallback
紧跟在各个SubShader后面的是Fallback,它会告诉Unity,如果前面的SubShader不能在显卡上运行,则运行Fallback定义的最低级的Shader。
一个简单的Shader代码如下:
// shader名字
Shader "Unity ShaderTest/Simple Test Shader"{
// SubShader使用默认渲染设置(RenderSetup)和标签设置(Tags)
Properties {
_Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader {
Pass {
CGPROGRAM
#include "UnityCG.cginc"
// #pragma vertex name // name为函数名,告诉Unity这个函数包含了顶点着色器代码
// #pragma fragment name // name告诉Unity这个函数包含了片元着色器代码
#pragma vertex vert
#pragma fragment frag
// 为了在CG代码中访问属性, 需要定义一个与属性名称和类型都匹配的变量
fixed4 _Color;
// application to vert
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
// vert to frag
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
// 参数 包含了这个顶点的位置, 通过POSITION语义指定的
v2f vert(a2v v) {
// mul 矩阵乘法 把顶点位置从模型空间转换到裁剪空间
// return mul(UNITY_MATRIX_MVP, v.vertex);
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// v.normal 包含了顶点的法线方向, 分量范围在[-1.0, 1.0]
// 把范围映射到[0, 1.0]
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
return o;
}
fixed4 frag(v2f i) : SV_Target {
//return fixed4(i.color, 1.0);
fixed3 c = i.color;
c *= _Color.rgb;
return fixed4(c, 1.0);
}
ENDCG
}
}
}
从头到尾说。这里定义了一个名叫Simple Test Shader的shader,它的路径为Unity ShaderTest/Simple Test Shader,然后定义了一个名为_Color的Properties;
接下来就是主干部分SubShader了,里面直接是一个Pass内容,从Pass的代码里可以看到,我们顶点/片元着色器都是写在CGPROGRAM和ENDCG里的;
#include "UnityCG.cginc",表示我们使用了UnityShader的一个头文件,后缀名为.cginc,我们会用到里面的成型的API等;
#pragma vertex vert和#pragma fragment frag,表示我们告诉Unity,vert函数定义了顶点着色器的代码,frag定义了片元着色器的代码;
fixed4 _Color; 为了在CG代码中访问属性, 我们需要定义一个与属性名称和类型都匹配的变量;
接下来,我们定义了两个struct,一个是从应用到顶点的结构体a2v,一个是从顶点到片元的结构体v2f,a2v会把从应用阶段的数据传送给顶点着色器,v2f会把顶点着色器计算后的值传给片元着色器;
最后,片元着色器的输出语义为SV_Target,它也是HLSL的一个系统语义,告诉渲染器,把用户的输出颜色存储到一个渲染目标中。
vert函数的输入a2v,内置了3个变量,其中vertex通过POSITION语义来告诉Unity,将模型的顶点坐标传到自己的空间里;normal表示模型空间的法线,通过NORMAL语义来指定;TEXCOORD0来告诉Unity,使用模型的第一套纹理来填充texcoord;
v2f作为顶点着色器的输出,它必须要包含的一个值,语义是SV_POSITION,它告诉Unity,顶点着色器的输出是裁剪空间中的坐标,否则片元着色器无法得知裁剪空间中的坐标值。
我们在这段Shader代码中用到了Color类型,它的变量类型是float4/half4/fixed4,类似的还有:
上图中看到了CG/HLSL中的3种精度数值类型,float/half/fixed:
我们在选择精度类型时,尽可能选用精度较低的类型。
Unity支持的语义:
1.从应用阶段传递模型数据给顶点着色器时Unity支持的常用语义:
上图里,TEXCOORDn,其中n的数目和ShaderModel有关,一般在ShaderModel2和3里,n等于8,而在4和5里,n等于16;
POSITION,TANGENT,NORMAL这些数据,在Unity中,它们是由使用该材质的MeshRender组件提供的,每帧进行DrawCall的时候,MeshRender都会负责把模型数据传递给UnityShader。
2. 从顶点着色器传递数据给片元着色器时Unity使用的常用语义
3.片元着色器输出时常用的语义
UnityCG.cginc种一些常用的帮助函数:
UnityShader内置的一些变量:
1.Unity内置的变换矩阵
其中_World2Object(Unity5.5中已经更名为_unityWorldToObject),在光照部分里,法线和光源方向的计算中会用到,因为计算法线变换时,需要用到逆矩阵。
2. Unity内置的摄像机和屏幕参数