1. 程式人生 > >Shader學習筆記(二):Vertex/Fragment Shader

Shader學習筆記(二):Vertex/Fragment Shader

這篇文章討論如何寫頂點、片元著色器(Vertex/Fragment Shader)。

概念解釋

先看一個完整例子,關鍵地方我做了標記。先熟悉大致結構,後面我會詳細解釋:

Shader "Unlit/NewUnlitShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            // CG程式起始位置
CGPROGRAM // 編譯指令:表示有頂點著色器 #pragma vertex vert // 編譯指令:表示有片元著色器 #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" // 自定義的結構 struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; // 自定義的結構
struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; // 頂點著色器函式 v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o,o.vertex); return
o; } // 片元著色器函式 fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } }
關鍵概念的理解
  • 可程式設計渲染管線:與固定渲染管線不同,可以讓程式設計師自己控制渲染管線的一部分,分為Vertex Shader和Fragment Shader。
  • Vertex Shader:作用於每個頂點,通常是處理從世界空間到裁剪空間(螢幕座標)的座標轉換,後面緊接的是光柵化。
  • Fragment Shader:作用於每個螢幕上的片元(這裡可近似理解為畫素),通常是計算顏色。
  • ShaderLab:Unity專有的shader語言,Surface Shader全部由ShaderLab實現,Vertex/Fragment是由ShaderrLab內嵌CG/HLSL實現。
  • Shading Language: 三種主流shader語言,均可以在ShaderLab中巢狀使用,官方推薦使用CG/HLSL(這兩種語言由Microsoft和Nvidia達成一致,所以基本等價)。詳見下面列表:
名字 全名 開發商 巢狀關鍵字
CG C for Graphic Nvidia CGPROGRAM
HLSL High Level Shading Language Microsoft CGPROGRAM
GLSL OpenGL Shading Language OpenGL GLSLPROGRAM

DirectX在Windows和遊戲領域稱霸,OpenGL則在移動領域勝出。
關於兩者孰優孰劣的討論數不勝數。
這裡總結了DirectX在Windows平臺勝出的一些原因:
1. DirectX更早推出可程式設計渲染管線
2. OpenGL成員太多,新標準推進緩慢
3. DirectX不僅是圖形庫,還包括聲音、網路等一套遊戲開發解決方案
4. DirectX不向後相容,OpenGL為相容性保留了很多過時的設計

Vertex/Fragment語法
  • SubShader:針對不同的硬體做不同的處理,執行時依次掃描,都失敗則FallBack。
  • Pass:一個SubShader中包含1到多個Pass,通常只需一個Pass。多個Pass可用於定義多渲染路徑(Rendering Path),執行時選擇執行哪個,常見於自定義光照模型中陰影處理。
  • Shader Semantics:如float4 vertex : POSITION,冒號後面是這個變數的語義,與GPU互動時用到。
  • Vertex Shader的輸入輸出
    • 輸入:可以是以下三種:
      • 由基本語義定義的變數:POSITION, NORMAL, TEXCOORD0, TEXCOORD1, …, TANGENT, COLOR
      • 內建的結構:appdata_base, appdata_tan, appdata_full
      • 自定義的結構。
    • 輸出:固定有SV_POSITION,其他需要用到的varying變數。語義大部分情況下不重要,用TEXCOORD0就好。
  • Fragment Shader的輸入輸出
    • 輸入:同Vertex Shader的輸出。
    • 輸出:SV_Target,通常是單個RGBA值。

例項解析

下面看幾個Unity官方manual中的例項,加深理解:

  • SimpleUnlitTextureShader:簡單的紋理貼圖,變換頂點座標,按uv找貼圖上的顏色。

這裡寫圖片描述

Shader "Unlit/SimpleUnlitTexturedShader"
{
    Properties
    {
        // we have removed support for texture tiling/offset,
        // so make them not be displayed in material inspector
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            // use "vert" function as the vertex shader
            #pragma vertex vert
            // use "frag" function as the pixel (fragment) shader
            #pragma fragment frag

            // vertex shader inputs
            struct appdata
            {
                float4 vertex : POSITION; // vertex position
                float2 uv : TEXCOORD0; // texture coordinate
            };

            // vertex shader outputs ("vertex to fragment")
            struct v2f
            {
                float2 uv : TEXCOORD0; // texture coordinate
                float4 vertex : SV_POSITION; // clip space position
            };

            // vertex shader
            v2f vert (appdata v)
            {
                v2f o;
                // transform position to clip space
                // (multiply with model*view*projection matrix)
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                // just pass the texture coordinate
                o.uv = v.uv;
                return o;
            }

            // texture we will sample
            sampler2D _MainTex;

            // pixel shader; returns low precision ("fixed4" type)
            // color ("SV_Target" semantic)
            fixed4 frag (v2f i) : SV_Target
            {
                // sample texture and return it
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}
  • SingleColor:給模型賦予單色。vertex只傳出SV_POSITION,fragment不傳參。CG中直接引用了ShaderLab的Properties引數_Color(ShaderLab和CG的型別存在對應關係)。

這裡寫圖片描述

Shader "Unlit/SingleColor"
{
    Properties
    {
        // Color property for material inspector, default to white
        _Color ("Main Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // vertex shader
            // this time instead of using "appdata" struct, just spell inputs manually,
            // and instead of returning v2f struct, also just return a single output
            // float4 clip position
            float4 vert (float4 vertex : POSITION) : SV_POSITION
            {
                return mul(UNITY_MATRIX_MVP, vertex);
            }

            // color from the material
            fixed4 _Color;

            // pixel shader, no inputs needed
            fixed4 frag () : SV_Target
            {
                return _Color; // just return it
            }
            ENDCG
        }
    }
}
  • WorldSpaceNormal:根據法向量來變色。worldNormal作為varying變數傳給fragment。

這裡寫圖片描述

Shader "Unlit/WorldSpaceNormals"
{
    // no Properties block this time!
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // include file that contains UnityObjectToWorldNormal helper function
            #include "UnityCG.cginc"

            struct v2f {
                // we'll output world space normal as one of regular ("texcoord") interpolators
                half3 worldNormal : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            // vertex shader: takes object space normal as input too
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // UnityCG.cginc file contains function to transform
                // normal from object to world space, use that
                o.worldNormal = UnityObjectToWorldNormal(normal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = 0;
                // normal is a 3D vector with xyz components; in -1..1
                // range. To display it as color, bring the range into 0..1
                // and put into red, green, blue components
                c.rgb = i.worldNormal*0.5+0.5;
                return c;
            }
            ENDCG
        }
    }
}
  • SkyReflection:反射環境。對比由Surface Shader的實現,可發現簡潔很多。float4表示點,float3表示向量。

這裡寫圖片描述

Shader "Unlit/SkyReflection"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };

            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}

再談與Surface Shader的關係

上一篇說到Surface Shader是簡化版的shader編寫工具,通過封裝減少了程式設計師的重複工作量。
那麼Surface Shader是如何編譯成Vertex/Fragment Shader的呢?它們三者的先後關係又是怎樣?

我的理解是:Surface Shader的surf函式位於fragment shader的起始階段,編譯後生成的是Fragment Shader。這點可用Unity自帶的shader inspector工具檢視。具體可參見知乎上的討論,說得很詳細了,這裡不再展開。