5开始Unity Shader学习之路
在初级篇中实现的Unity Shader大多不能直接用于真是项目中,因为他们缺少了完整的光照计算,例如阴影、光照衰弱等。仅仅是为了阐述一些实现原理。
顶点/片元着色器的基本结构
Shader "MyShaderName"{
Properties{
//属性
}
SubShader{
//针对显卡a的SubShader
Pass{
//设置渲染状态和标签
//开始CG代码片段
CGPROGRAM
//该代码片段的编译指令,例如:
#pragma vertex vert
#pragma fragment frag
//CG代码写在这里
ENDCG
//其他设置
}
//针对显卡B的SubShader
}
//上述SubShader都失败后用于回调的Unity Shader
Fallback "VertexLit"
}
其中,最重要的部分是Pass语义块,我们绝大部分的代码都是写在这个语义块里面的。
Shader "Custom/Test" {
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// POSITION 和SV_POSITION都是CG/HLSl中的语义(semantics)他们是不可省略的
//这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。
//这里POSITION将告诉Unity,把模型的顶点坐标填充到输入参数v中,
//SV_POSITION将告诉Unity,顶点着色器的输出是裁剪空间中的顶点坐标。
//UNITY_MATRIX_MVP矩阵是Unity内置的模型.观察.投影矩阵(4.8);
float4 vert(float4 v : POSITION) :SV_POSITION{
return mul(UNITY_MATRIX_MVP,v);
}
//本例中frag没有任何输入。它的输出是一个fixed4类型的变量,并且使用了SV_Target语义进行限定。
//SV_Target也是HLSL中的一个系统语义,他等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标中,这里将输出到默认的帧缓存中。
//片元着色器中的代码很简单,返回一个表示白色的fixed4类型的变量。
fixed4 frag() : SV_Target{
return fixed4(1.0,1.0,1.0,0.0);
}
ENDCG
}
}
}
#pragma vertex vert
#pragma fragment frag
它们告诉Unity,哪个函数包含可顶点着色器的代码,哪个函数包含了片元着色器的代码。
在上面的例子中,在顶点着色器中我们使用POSITION语义得到了模型的顶点位置。
现在,我们想要得到模型上每个顶点的纹理坐标和法线方向。我们需要使用纹理坐标来访问纹理,而法线可用于计算光照。因此我们需要为顶点着色器定义一个新的输入参数,这个参数不在是一个简单的数据类型,而是一个结构体。
Shader "Custom/Test" {
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//使用一个结构体来定义顶点着色器的输入
struct a2v {
//POSITION语义告诉Unity,用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL语义告诉Unity,用模型空间的法线方向填充normal变量
float3 normal:NORMAL;
//TEXCOORDO语义告诉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,它包含立顶点着色器需要的模型数据。它包含了顶点着色器需要的模型数据。Unity支持的语义有:POSITION,TANGENT,NORMAL,TEXCOORD0,TEXCOORD1,TEXCOORD2,TEXCOORD3,COLOR等。
为了创建一个自定义的结构体,我们必须使用如下格式来定义它:
struct StructName{
Type Name:Semantic;
Type Name:Semantic;
};
其中,语义是不可以被省略的。在5.4节中,我们将给出这些语义的含义和用法。
a表示应用(application),v表示顶点着色器(vertex shadr),a2v的意思就是把数据从应用阶段传递到顶点着色器中。
那么,填充到POSITION,TANGENT,NORMAL这些语义块中的数据究竟是从哪里来的呢?在Unity中,他们是由使用该材质的Mesh Render组件提供的。在每帧调用Draw Call的时候,Mesh Render组件会把他负责渲染的模型数据发送给Unity