1. 什么是语义
我们可以在unity创建的默认Shader中经常看到,SV_POSITION、POSITION、COLOR0等,这些大写的字母就是
CG/HLSL提供的语义。
通俗来讲,这些语义可以让Shader知道从哪里读取数据,并吧数据输出到哪里,在CG/HLSL的shader流水线中是
不可或缺的。需要注意的是,Unity并没有支持所有的语义。通常情况下,输入输出这些变量并不需要特别的意义,也
就是说,我们可以自行解决这些变量的用途。顶点着色器的输出结构中我们用COLOR0语义去描述color变量,color变
量本身存储什么,Shader并不关心。
然而Unity为了方便对模型数据的传输,对一些语义进行了特别的含义规定。例如,在顶点着色器的输入结构体
a2f用TEXCOORD0来描述texcoord,Unity会识别TEXCOORDO语义,以把模型的第一组纹理坐标填充到texcoord
中。需要注意的是,即使语义的名称一样,如果出现的位置不同,含义也不同。例如TEXCOORD0既可以用于描述
顶点着色器的输入结构体,也可以用于描述输出结构体。但在输入结构体中,TEXCOORD0有特别的含义,即把模型
的第一组纹理坐标存储在该变量中,而在输出结构体中,TEXCOORD0修饰的变量含义就可以由我们来决定。
2. SV 类语义
在DirectX10 以后,有了一种新的语义类型,就是 系统数值语义。这类语义是以SV开头的,SV代表的含义就是系统
数值。这些语义在渲染流水线中有特殊的含义,例如我们使用SV_POSITION 语义去修饰顶点着色器的输出变量pos,
那么就表示pos包含了可用于光栅化的变化后的顶点坐标(即齐次裁剪空间中的坐标)。用这些语义描述的变量是不
可以随意赋值的,因为流水线需要使用它们来完成特定的目的,例如渲染引擎会把用SV_POSITION 修复的变量经过
光栅化显示在屏幕上。又可以会看到用一个变量在不同的Shader里面使用了不同的语义修饰。例如,一些Shader 会
使用POSITION 而非SV_POSITION来修饰输出。SV_POSITION 是 DirectX 10中引入的系统数值语义,在绝大数平
台上,它和 POSITION 是等价的,但在某些平台(如索尼PS4)上必须使用SV_POSITION 来修饰着色器的输出,否
则无法让Shader正常工作。同样的例子还有COLOR 和 SV_Target。因为,为了让我们的Shader有更好的跨平台性,
对于这些有特殊含义的变量我们最好使用以SV开头的语义进行修饰。
3. Unity支持的语义
表1. 1 从应用阶传递模型数据给顶点着色器是Unity支持的常用语义
语义 | 描述 |
---|
POSITION | 模型空间中的顶点位置,通常是float4类型 |
NORMAL | 顶点法线,通常是float3类型 |
TANGENT | 顶点切线,通常是float4类型 |
TEXCOORDn | 该顶点的纹理坐标,TEXCOORD0 表示第一组纹理坐标,以此类推。通常是float2或者float4类型 |
COLOR | 顶点颜色,通常是fixed4或者float4类型 |
表 1. 2 从顶点着色器传递数据给片元着色器是Unity使用的常用语义
语义 | 描述 |
---|
SV_POSITION | 裁剪空间中的顶点坐标,结构体中必须包含一个用该语义修饰的变量。等同于DIrectX9中的POSITION,但最好使用SV_POSITION |
COLOR0 | 通常用于输出第一组顶点颜色,但不是必需的 |
COLOR1 | 通常用于输出第二种顶点颜色,但不是必需的 |
TEXCOORD0~TEXCOORD7 | 通常用于输出纹理坐标,但不是必需的 |
上面的语义中,除了SV_POSITION是有特殊含义外,其他语义对变量的含义没有明确要求,也就是说,我们可以存储任意值到这些语义描述变量中。通常,如果我们需要把一些自定义的数据从顶点着色器传递给片元着色器,一般选用TEXCOORD0等。
表 1. 2 Unity支持的片元着色器的输出语义
语义 | 描述 |
---|
SV_Target | 输出值将会存储到渲染目标中,等同于DIrectX9 中的 COLOR 语义,但最好使用 SV_Target |
4. 关于结构体的定义
struct v2f{
float4 pos :SV_POSITION;
fixed3 color0 : COLOR0;
fixed4 color1 : COLOR1;
half value0 : TEXCOORD0;
float2 value1 : TEXCOORD1;
}
需要注意的是,一个语义可以使用的寄存器只有能处理4个浮点数(float)。因此,如果我们想要定义矩阵类型,如果float34、float44等变量就需要使用更多的空间。一种方法是,把这些变量拆分成多个变量,例如对于float4*4的矩阵类型,我们可以拆分成4个float4类型的变量,每个变量存储了矩阵的一行数据。