在书写HLSL shader程序时,输入和输出变量需要拥有他们 含义来表明语义。这在HLSL shader中是一个标准的做法。
Vertex shader 输入语义
主顶点着色器函数(被指令 #pragma vertex 标记)需要在所有的输入参数中加上语义。这些对应于单个网格数据元素,比如顶点位置,网格法线,还有贴图坐标等。简单例子如下:
Shader "Unlit/Show UVs" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag struct v2f { float2 uv : TEXCOORD0; float4 pos : SV_POSITION; }; v2f vert ( float4 vertex : POSITION, // vertex position input float2 uv : TEXCOORD0 // first texture coordinate input ) { v2f o; o.pos = UnityObjectToClipPos(vertex); o.uv = uv; return o; } fixed4 frag (v2f i) : SV_Target { return fixed4(i.uv, 0, 0); } ENDCG } } }
Fragment shader 输出语义
大多数情况下,片段着色器输出一个额颜色,并且含有 SV_Target语义。就比如:
fixed4 frag(v2f i): SV_Target
函数 frag 有一个返回类型为 fixed4 的值。因为它只会返回一个值,那么语义就可以直接定义在函数上。
当然,输出返回一个结构体也是可以的。上面的片段着色器可以被重写为下面这个样子,并且他们的功能一致:
struct fragOutput { fixed4 color : SV_Target; }; fragOutput frag(v2f i) { fragOutput o; o.color = fixed4(i.uv,0,0); return o; }
从片段着色器中返回结构体,对于那些不单单返回一个颜色的着色器非常有用。支持片段着色器输出的额外的语义如下:
SV_TargetN: 多个渲染目标
SV_Target1,SV_Target2等等:这些是shader输出的额外的颜色。这种做法在一次性渲染多个目标时使用(也叫作多重渲染目标渲染技术,MRT)。当然,SV_Target0 就等于SV_Target
SV_Depth:像素着色器深度输出
通常情况,片段着色器不会覆写 Z 缓冲区的值,在三角形光栅化阶段,一般都用的是默认值。然而,对于一些特效,对每个像素使用自己的z缓冲区值是非常有帮助的。
注意在许多GPU上,深度缓冲优化是关闭了的,所以在没有必须的理由时,最好不要重写深度缓冲区的值。由 SV_Depth 引发的性能消耗 根据GPU架构不同而不同,但是总体上来说这个消耗与alpha 测试相当。
Vertex shader 输出和 fragment shader 输入
一个vertex shader需要输出 一个顶点的 最终剪切空间坐标,这样GPU才知道在屏幕上的哪个地方、多少深度来光栅化它,这个输出需要有 SV_POSITION 语义,而且它的类型是一个 float4类型的数据。
vertex shader产生的任何其他输出都是你特殊着色器所需要的。从顶点着色器输出的值将被用来渲染三角形面,并且每个像素的值将被会作为输入数据传给片段着色器。
许多现代的GPU不需要真正关心这些变量有哪些语义;然而一些老系统却是需要一些特别的语义:
TEXCOORD0,TEXCOORD1等等用来表明任意高精度数据,比如坐标或者位置。
COLOR0,COLOR1 语义在顶点着色器输出和片段着色器输入中代表 低精度,0-1范围内的数据。
为了最好的跨平台支持, 在顶点着色器的输出与片段说色器的输入中,最好打上 TEXCOORDn语义标签。
插入器数量限制
将信息从顶点着色器传递到片段着色器中时,插入器变量可以使用的总量有着数量限制。限制数量与平台和GPU有关:
- 最高8个:OpenGL ES 2.0 (ios/android), Direct3D 11 9.x level (Windows Phone) and Direct3 9 shader model 2.0 (old PCs).虽然插入器数量被限制了,但是每个插入器可以是一个4分量的向量,一些着色器将数据打包到一起来不超过限制。比如,两个贴图坐标可以被一个float4变量传递。
- 最高10个:Direct3D 9 shader model 3.0 (
#pragma target 3.0
). - 最高16个:OpenGL ES 3.0 (iOS/Android), Metal (iOS).
- 最高32个:Direct3D 10 shader model 4.0 (
#pragma target 4.0
).
为了性能着想,不管你是啥平台,最好都尽量使用更低数量的插入器。
其它特别的语义
Screen space pixel position:VPOS
一个片段着色器可以接受一个作为特别 VPOS语义呈现的像素的位置。这个特性只有在以 model 3.0开头的着色器中才存在。所以着色器需要编译指令:#pragma target 3.0
在不同的平台,屏幕空间位置的输入在底层上是不同的,所以为了最大限度的可移植性,在使用屏幕空间位置时,使用 UNITY_VPOS_TYPE标签。(大多数情况为float4,但是在D3D9 为float2)。
此外, 使用像素位置语义让在一个顶点到片段的结构体 同时拥有 剪切空间位置(SV_POSITION)和VPOS(屏幕空间位置)变得困难。所以顶点着色器需要将 剪切空间位置(SV_POSITION)作为一个单独的输出变量。请看下面的例子:
Shader "Unlit/Screen Position" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 // note: no SV_POSITION in this struct struct v2f { float2 uv : TEXCOORD0; }; v2f vert ( float4 vertex : POSITION, // vertex position input float2 uv : TEXCOORD0, // texture coordinate input out float4 outpos : SV_POSITION // clip space position output ) { v2f o; o.uv = uv; outpos = UnityObjectToClipPos(vertex); return o; } sampler2D _MainTex; fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target { // screenPos.xy will contain pixel integer coordinates. // use them to implement a checkerboard pattern that skips rendering // 4x4 blocks of pixels // checker value will be negative for 4x4 blocks of pixels // in a checkerboard pattern screenPos.xy = floor(screenPos.xy * 0.25) * 0.5; float checker = -frac(screenPos.r + screenPos.g); // clip HLSL instruction stops rendering a pixel if value is negative clip(checker); // for pixels that were kept, read the texture and output it fixed4 c = tex2D (_MainTex, i.uv); return c; } ENDCG } } }
Face Orientation:VFACE
片段着色器可以接受一个变量,这个变量表明当前渲染的表面是否面对着摄像机,或者背对着摄像机。这一点在渲染那些两面都可以被看到的几何体时非常有用-比如渲染叶子。语义 VFACE修饰的 输入变量将会包含一个正值表明正对着的三角形,一个负数来表明背对这的三角形。
这个特性只有在 model 3.0 的着色器中才有,所以着色器必须包含编译指令: #pragma target 3.0,例子如下:
Shader "Unlit/Face Orientation" { Properties { _ColorFront ("Front Color", Color) = (1,0.7,0.7,1) _ColorBack ("Back Color", Color) = (0.7,1,0.7,1) } SubShader { Pass { Cull Off // turn off backface culling CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 float4 vert (float4 vertex : POSITION) : SV_POSITION { return UnityObjectToClipPos(vertex); } fixed4 _ColorFront; fixed4 _ColorBack; fixed4 frag (fixed facing : VFACE) : SV_Target { // VFACE input positive for frontbaces, // negative for backfaces. Output one // of the two colors depending on that. return facing > 0 ? _ColorFront : _ColorBack; } ENDCG } } }
Vertex ID: SV_VertexID
顶点着色器可以接收一个具有顶点数作为无符号整数的变量。当你想从贴图或者计算缓冲区中拿到额外的每个顶点数据时,这样就非常有用。
这个特性只有在DX10 和GLCore/OpenGL ES 3中才支持,所以需要引入编译指令 #pragma target 3.5,例子如下:
Shader "Unlit/VertexID" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.5 struct v2f { fixed4 color : TEXCOORD0; float4 pos : SV_POSITION; }; v2f vert ( float4 vertex : POSITION, // vertex position input uint vid : SV_VertexID // vertex ID, needs to be uint ) { v2f o; o.pos = UnityObjectToClipPos(vertex); // output funky colors based on vertex ID float f = (float)vid; o.color = half4(sin(f/10),sin(f/100),sin(f/1000),0) * 0.5 + 0.5; return o; } fixed4 frag (v2f i) : SV_Target { return i.color; } ENDCG } } }