通过前面介绍的渲染管线的大概流程:从应用程序阶段传递数值到顶点着色器,然后又经顶点着色器将数据传递给片断着色器并最终显示在屏幕上。
在应用阶段传入顶点着色器的数据我们看到有这样的语法:
struct appdata
{
float4 vertex : POSITION;
};
这里我们声明了一个float4类型的变量vertex,并给予了它顶点数据的语义(在变量后加冒号并跟一个语义),也就是说vertex变量将代表着模型的顶点数据被我们使用与传递。
那么都有哪些语义呢?如下:
struct appdata
{
float4 vertex : POSITION; //顶点
float4 tangent : TANGENT; //切线
float3 normal : NORMAL; //法线
float4 texcoord : TEXCOORD0; //UV1
float4 texcoord1 : TEXCOORD1; //UV2
float4 texcoord2 : TEXCOORD2; //UV3
float4 texcoord3 : TEXCOORD3; //UV4
fixed4 color : COLOR; //顶点色
};
仔细观察,这些正好也是模型在Unity中的所有数据信息。
在UnityCG.cginc中内置定义了三种常用的结构,我们也可以直接引用并调用,有关cginc的具体用法我们后面再讨论,这里为了熟悉学习,我们尽量自己定义结构。
顶点着色器到片断着色器的数据
顶点着色器在处理完应用阶段传过来的数据后,会需要输出并传入片断着色器,这个时候我们同样需要定义一个结构来承载其中的数据,同样的,输出给片断着色器的值也需要语义来标识。
struct v2f
{
float4 pos:SV_POSITION;
};
这里声明了float4类型的变量pos,并指定为SV_POSITION语义,表示pos就是顶点着色器输出的屏幕裁剪空间下的顶点位置。这条语义是必须要有的,否则GPU无法进行接下来的光栅化处理。
其实在现代GPU上对这里的语义如何定义并不关心了(除了SV_POSITION以外),主要是部分OpenGL ES2.0上面才需要特别注意而已。
这里的语义除了SV_POSITION以外,我们还有另外两种选择:
TEXCOORD0~N系列
例如TEXCOORD0、TEXCOORD1、TEXCOORD2…等等,主要用于高精度数据。
COLOR0~N系列
例如COLOR0、COLOR1、COLOR2…等等,主要用于低精度数据。
虽然这两种语义我们可以根据需要自由定义,但是,它并不是可以无限定义的,不同的GPU硬件有不同的数量限制。
以下为手机平台的常见规则:
OpenGL ES2.0支持最多8个
OpenGL ES3.0支持最多16个
从性能优化角度来讲,数量越少性能越好。
另外,每个语义是4维向量,利用好这一点,可以大大节省总数量。
VFACE
在片断着色器中还有些特殊的语义的可以识别,比如VFACE
如果渲染表面朝向摄像机,则Face节点输出正值1,如果远离摄像机,则输出负值-1。
Shader "Unlit/Test2"
{
Properties
{
_FrontTex("FrontTex", 2d) = "white"{}
_BackTex("BackTex", 2d) = "white"{}
}
SubShader
{
cull off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
sampler2D _FrontTex;
sampler2D _BackTex;
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 frag(v2f i,float face : VFACE) : SV_Target
{
fixed4 col = 1;
col = face > 0 ? tex2D(_FrontTex,i.uv) : tex2D(_BackTex,i.uv);
return col;
}
ENDCG
}
}
}
片断着色器输出相关语义
通常情况下,片断着色器最终只需返回一个颜色值即可,也是我们最常见到的编写方式,如下:
fixed4 frag (v2f i ) : SV_TARGET
这里的SV_TARGET就是指定输出颜色到RenderTarget的语义,其实我们也可以采用Struct的方式,就是像应用阶段到顶点与顶点到片断一样,只是由于平时我们只需返回一个颜色所以就无需再用一个Struct了(你非要用也是可以的)。
当我们利用Struct时,就可以通过下列语义来输出多个内容:
SV_Target0〜N
默认SV_TARGET0,也就是SV_TARGET,还有SV_TARGET1,SV_TARGET2…这个在需要输出多个RenderTarget时很有用。
SV_Depth
一般情况下,模型的像素深度值在光栅化时会自动插值计算得出,并不需要我们做额外的处理,但这并不代表不可以修改它,通过在片断着色器中输出SV_DEPTH语义可以更改像素的深度值。
注意此功能相对会消耗性能,在没有特别需求的情况下尽量不要用!