在書寫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 } } }