1. 程式人生 > >Unity ShaderLab學習總結

Unity ShaderLab學習總結

Why Bothers?

為什麼已經有ShaderForge這種視覺化Shader編輯器、為什麼Asset Store已經有那麼多炫酷的Shader元件可下載,還是有必要學些Shader的編寫?

2014-0718-1607-11-33.png

  • 因為上面這些Shader工具/元件最終都是以Shader檔案的形式而存在。
  • 需要開發人員/技術美術有能力對Shader進行功能分析、效率評估、選擇、優化、相容、甚至是Debug。
  • 對於特殊的需求,可能還是直接編寫Shader比較實際、高效。

總之,Shader編寫是重要的;但至於緊不緊急,視乎專案需求。

涉及範圍

本文只討論Unity ShaderLab相關的知識和使用方法。但,

  • 也不討論具體的渲染技巧
  • 移動裝置GPU和桌面裝置GPU硬體架構上有較多不同點,詳見下面的“移動裝置GPU架構簡述”一章。

使用Shader

2014-0720-1007-25-36.png

如上圖,一句話總結:

  1. GameObject裡有MeshRenderer,
  2. MeshRenderer裡有Material列表,
  3. 每個Material裡有且只有一個Shader;
  4. Material在編輯器暴露該Shader的可調屬性。

所以關鍵是怎麼編寫Shader。

Shader基礎

編輯器

使用MonoDevelop這反人類的IDE來編寫Shader居然是讓人滿意的。有語法高亮,無語法提示。 如果習慣VisualStudio,可以如下實現.Shader檔案的語法高亮。

  • 下載作者donaldwu自己新增的關鍵詞檔案usertype.dat。其包括了Unity ShaderLab的部分關鍵字,和HLSL的所有關鍵字。關鍵字以後持續新增中。
  • 將下載的usertype.dat放到Microsoft Visual Studio xx.x\CommonX\IDE\資料夾下;
  • 開啟VS,工具>選項>文字編輯器>副檔名,副檔名裡填“shader”,編輯器選VC++,點選新增;
  • 重啟VS,Done。

Shader

Shader "ShaderLab Tutorials/TestShader"
{
    // ...
}

2014-0720-1707-17-42.png

Shader的名字會直接決定shader在material裡出現的路徑

SubShader

Shader "ShaderLab Tutorials/TestShader" {
    SubShader
    {
        //...
    }
}

一個Shader有多個SubShader。一個SubShader可理解為一個Shader的一個渲染方案。即SubShader是為了針對不同的渲染情況而編寫的。每個Shader至少1個SubShader、理論可以無限多個,但往往兩三個就足夠。 一個時刻只會選取一個SubShader進行渲染,具體SubShader的選取規則包括:

  • 從上到下選取
  • SubShader的標籤、Pass的標籤
    • 是否符合當前的“Unity渲染路徑”
    • 是否符合當前的ReplacementTag
  • SubShader是否和當前的GPU相容

按此規則第一個被選取的SubShader將會用於渲染,未被選取的SubShader在這次渲染將被忽略。

SubShader的Tag

Shader "ShaderLab Tutorials/TestShader" {
    SubShader
    {
        Tags { "Queue"="Geometry+10" "RenderType"="Opaque" }
        //...
    }
}

SubShader內部可以有標籤(Tags)的定義。Tag指定了這個SubShader的渲染順序(時機),以及其他的一些設定。

  • "RenderType"標籤。Unity可以執行時替換符合特定RenderType的所有Shader。Camera.RenderWithShaderCamera.SetReplacementShader配合使用。Unity內建的RenderType包括:
    • "Opaque":絕大部分不透明的物體都使用這個;
    • "Transparent":絕大部分透明的物體、包括粒子特效都使用這個;
    • "Background":天空盒都使用這個;
    • "Overlay":GUI、鏡頭光暈都使用這個;
    • 使用者也可以定義任意自己的RenderType這個標籤所取的值。
    • 應注意,Camera.RenderWithShaderCamera.SetReplacementShader不要求標籤只能是RenderTypeRenderType只是Unity內部用於Replace的一個標籤而已,你也可以自定義自己全新的標籤用於Replace。 比如,你為自己的ShaderA.SubShaderA1(會被Unity選取到的SubShader,常為Shader檔案中的第一個SubShader)增加Tag為"Distort"="On",然後將"Distort"作為引數replacementTag傳給函式。此時,作為replacementShader實參的ShaderB.SubShaderB1中若有也有一模一樣的"Distort"="On"、且這A的材質引數包含B所需材質引數,則此SubShaderB1將代替SubShaderA1用於本次渲染。
  • "Queue"標籤。定義渲染順序。預製的值為
  • "Background"。值為1000。比如用於天空盒。
  • "Geometry"。值為2000。大部分物體在這個佇列。不透明的物體也在這裡。這個佇列內部的物體的渲染順序會有進一步的優化(應該是從近到遠,early-z test可以剔除不需經過FS處理的片元)。其他佇列的物體都是按空間位置的從遠到近進行渲染。
  • "AlphaTest"。值為2450。已進行AlphaTest的物體在這個佇列。
  • "Transparent"。值為3000。透明物體。
  • "Overlay"。值為4000。比如鏡頭光暈。
  • 使用者可以定義任意值,比如"Queue"="Geometry+10"
  • "ForceNoShadowCasting",值為"true"時,表示不接受陰影。
  • "IgnoreProjector",值為"true"時,表示不接受Projector元件的投影。

另,關於渲染佇列和Batch的非官方經驗總結是,一幀的渲染佇列的生成,依次決定於每個渲染物體的:

  • Shader的RenderType tag,
  • Renderer.SortingLayerID,
  • Renderer.SortingOrder,
  • Material.renderQueue(預設值為Shader裡的"Queue"),
  • Transform.z(ViewSpace)(預設為按z值從前到後,但當Queue是“Transparent”的時候,按z值從後到前)。

這個渲染佇列決定了之後(可能有dirty flag的機制?)渲染器再依次遍歷這個渲染佇列,“同一種”材質的渲染物體合到一個Batch裡。

Pass

Shader "ShaderLab Tutorials/TestShader" {
    SubShader {
        Pass
        {
            //...
        }
    }
}

一個SubShader(渲染方案)是由一個個Pass塊來執行的。每個Pass都會消耗對應的一個DrawCall。在滿足渲染效果的情況下儘可能地減少Pass的數量。

Pass的Tag

Shader "ShaderLab Tutorials/TestShader" {
    SubShader {
        Pass
        {
            Tags{ "LightMode"="ForwardBase" }
            //...
        }
    }
}

和SubShader有自己專屬的Tag類似,Pass也有Pass專屬的Tag。 其中最重要Tag是 "LightMode",指定Pass和Unity的哪一種渲染路徑(“Rendering Path”)搭配使用。除最重要的ForwardBaseForwardAdd外,這裡需額外提醒的Tag取值可包括:

  • Always,永遠都渲染,但不處理光照
  • ShadowCaster,用於渲染產生陰影的物體
  • ShadowCollector,用於收集物體陰影到螢幕座標Buff裡。

其他渲染路徑相關的Tag詳見下面章節“Unity渲染路徑種類”。 具體所有Tag取值,可參考ShaderLab syntax: Pass Tags

FallBack

Shader "ShaderLab Tutorials/TestShader"{
    SubShader { Pass {} }
    
    FallBack "Diffuse" // "Diffuse"即Unity預製的固有Shader
    // FallBack Off //將關閉FallBack
}

當本Shader的所有SubShader都不支援當前顯示卡,就會使用FallBack語句指定的另一個Shader。FallBack最好指定Unity自己預製的Shader實現,因其一般能夠在當前所有顯示卡執行。

Properties

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {
    _Range ("My Range", Range (0.02,0.15)) = 0.07 // sliders
    _Color ("My Color", Color) = (.34, .85, .92, 1) // color
    _2D ("My Texture 2D", 2D) = "" {} // textures
    _Rect("My Rectangle", Rect) = "name" { }
    _Cube ("My Cubemap", Cube) = "name" { }
    _Float ("My Float", Float) = 1
    _Vector ("My Vector", Vector) = (1,2,3,4)

    // Display as a toggle.
    [Toggle] _Invert ("Invert color?", Float) = 0
    // Blend mode values
    [Enum(UnityEngine.Rendering.BlendMode)] _Blend ("Blend mode", Float) = 1
    //setup corresponding shader keywords.
    [KeywordEnum(Off, On)] _UseSpecular ("Use Specular",  Float) = 0
    }
    
    // Shader
    SubShader{
        Pass{
          //...
          uniform float4 _Color;
          //...
          float4 frag() : COLOR{ return fixed4(_Color); }
          //...
             #pragma multi_compile __ _USESPECULAR_ON
          }
    }
    
    //fixed pipeline
    SubShader   {
        Pass{
            Color[_Color]
        }
    }
}
  • Shader在Unity編輯器暴露給美術的引數,通過Properties來實現。
  • 所有可能的引數如上所示。主要也就Float、Vector和Texture這3類。
  • 除了通過編輯器編輯Properties,指令碼也可以通過Material的介面(比如SetFloatSetTexture編輯)
  • 之後在Shader程式通過[name](固定管線)或直接name(可程式設計Shader)訪問這些屬性。
  • 在每一個Property前面也能類似C#那樣新增Attribute,以達到額外UI面板功能。詳見MaterialPropertyDrawer.html

Shader中的資料型別

有3種基本數值型別:floathalffixed。 這3種基本數值型別可以再組成vector和matrix,比如half3是由3個half組成、float4x4是由16個float組成。

  • float:32位高精度浮點數。
  • half:16位中精度浮點數。範圍是[-6萬, +6萬],能精確到十進位制的小數點後3.3位。
  • fixed:11位低精度浮點數。範圍是[-2, 2],精度是1/256。

資料型別影響效能

  • 精度夠用就好。
    • 顏色和單位向量,使用fixed
    • 其他情況,儘量使用half(即範圍在[-6萬, +6萬]內、精確到小數點後3.3位);否則才使用float

ShaderLab中的Matrix

當提到“Row-Major”、“Column-Major”,根據不同的場合,它們可能指不同的意思:

  • 數學上的,主要是指向量V是Row Vector、還是Column Vector。引用自[Game Engine Architecture 2nd Edition, 183]。留意到V和M的乘法,當是Row Vector的時候,數學上寫作VM,Matrix在右邊,Matrix的最下面一行表示Translate;當是Column Vector的時候,數學上寫作MtVt,Matrix在左邊並且需要轉置,Matrix最右面一列表示Translate。
  • 訪問介面上的:Row-Major即MyMatrix[Row][Column]、Column-Major即MyMatrix[Column][Row]。HLSL/CG的訪問介面都是Row-Major,比如MyMatrix[3]返回的是第3行;GLSL的訪問介面是Column-Major,比如MyMatrix[3]返回的是第3列。
  • 暫存器儲存上的:每個元素是按行儲存在暫存器中、還是按列儲存在暫存器中。需要關注它的一般情況舉例是,float2x3的MyMatrix,到底是佔用2個暫存器(Row-Major)、還是3個暫存器(Column-Major)。在HLSL裡,可以通過#pragmapack_matrix設定row_major或者column_major。

上述情況,互不相干。 然後,ShaderLab中,數學上是Column Vector、訪問介面上是Row-Major、儲存上是(尚未查明)。

ShaderLab中各個Space的座標系

一般情況下,從Vertex Buff輸入頂點到Vertex Shader,

  • 該頂點為左手座標系Model Space中的頂點vInModel, 其用w=1的Homogenous Cooridniates(故等效於Cartesian Coordinates)表達vInModel = float4(xm, ym, zm, 1)
  • vInWrold = mul(_Object2World , vInModel)後,得出左手座標系World Space中的vInWorld,其為w=1的Homogenous Cooridniates(故等效於Cartesian Coordinates)vInWorld = float4(xw, yw, zw, 1)
  • vInView = mul(UNITY_MATRIX_V , vInWrold)後,得出右手座標系View Space中的vInView,其為w=1的Homogenous Cooridniates(故等效於Cartesian Coordinates)vInWorld = float4(xv, yv, zv, 1)
  • vInClip = mul(UNITY_MATRIX_P , vInView)後,得出左手座標系Clip Space中的vInClip,其為w往往不等於1的Homogenous Cooridniates(故往往不等效於Cartesian Coordinates)vInClip = float4(xc, yc, zc, wc); 設r、l、t、b、n、f的長度絕對值如下圖:

    注意View Space中攝像機前方的z值為負數、-z為正數。則GL/DX/Metal的Clip Space座標為:
    • GL:
      • xc=(2nx+rz+lz)/(r-l);
      • yc=(2ny+tz+bz)/(t-b);
      • zc=(-fz-nz-2nf)/(f-n);
      • wc=-z;
    • DX/Metal:
      • xc=(2nx+rz+lz)/(r-l);
      • yc=(2ny+tz+bz)/(t-b);
      • zc=(-fz-nf)/(f-n);
      • wc=-z;
  • vInNDC = vInClip / vInClip.w後,得出左手座標系Normalized Device Coordinates中的vInNDC,其為w=1的Homogenous Cooridniates(故等效於Cartesian Coordinates)vInNDC = float4(xn, yn, zn, 1)xnyn的取值範圍為[-1,1]。
    • GL: zn=zc/wc=(fz+nz+2nf)/((f-n)z);
    • DX/Metal: zn=zc/wc=(fz+nf)/((f-n)z);
    • 在Unity中,zn的取值範圍可以這樣決定:
      • 如果UNITY_REVERSED_Z已定義,zn的取值範圍是[UNITY_NEAR_CLIP_VALUE, 0],即[1,0]
      • 如果UNITY_REVERSED_Z未定義,zn的取值範圍是[UNITY_NEAR_CLIP_VALUE, 1]
        • 如果SHADER_API_D3D9/SHADER_API_D3D11_9X定義了,即[0,1]
        • 否則,即OpenGL情況,即[-1,1]
v2f vert (appdata v)
{
    v2f o;
    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    // 1 、2、3是等價的,和4是不等價的
    // 因為是M在左、V在右,所以是Column Vector
    // 因為是HLSL/CG語言,所以是訪問方式是Row-Major
    o.rootInView = mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1)); // 1
    o.rootInView = float4(UNITY_MATRIX_MV[0].w, UNITY_MATRIX_MV[1].w, UNITY_MATRIX_MV[2].w, 1); // 2                
    o.rootInView = UNITY_MATRIX_MV._m03_m13_m23_m33;  // 3
    //o.rootInView = UNITY_MATRIX_MV[3]; // 4

    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    // 因為是ViewSpace是右手座標系,所以當root在view前面的時候,z是負數,所以需要-z才能正確顯示顏色
    fixed4 col = fixed4(i.rootInView.x, i.rootInView.y, -i.rootInView.z, 1);
    return col;
}

struct appdata
{
    float4 vertex : POSITION;
};
struct v2f
{
    float4 rootInView : TEXCOORD0;
    float4 vertex : SV_POSITION;
};

Shader形態

Shader形態之1:固定管線

固定管線是為了相容老式顯示卡。都是頂點光照。之後固定管線可能是被Unity拋棄的功能,所以最好不學它、當它不存在。特徵是裡面出現了形如下面Material塊、沒有CGPROGRAMENDCG塊。

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {
    _Color ("My Color", Color) = (.34, .85, .92, 1) // color
    }
    
    // Fixed Pipeline
    SubShader
    {
        Pass
        {
            Material{
            Diffuse [_Color]
            Ambient [_Color]
            }
            
            Lighting On
        }
    }
}

Shader形態之2:可程式設計Shader

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {}
    
    SubShader
    {
        Pass
        {
          // ... the usual pass state setup ...
          
          CGPROGRAM
          // compilation directives for this snippet, e.g.:
          #pragma vertex vert
          #pragma fragment frag
          
          // the Cg/HLSL code itself
          float4 vert(float4 v:POSITION) : SV_POSITION{
            return mul(UNITY_MATRIX_MVP, v);
          }
          float4 frag() : COLOR{
            return fixed4(1.0, 0.0, 0.0, 1.0);
          }
          ENDCG
          // ... the rest of pass setup ...
          }
    }
}
  • 功能最強大、最自由的形態。
  • 特徵是在Pass裡出現CGPROGRAMENDCG
  • 編譯指令#pragma。詳見官網Cg snippets。其中重要的包括:
編譯指令 示例/含義
#pragma vertex name#pragma fragment name 替換name,來指定Vertex Shader函式、Fragment Shader函式。
#pragma target name 替換name(為2.03.0等)。設定編譯目標shader model的版本。
#pragma only_renderers name name ...#pragma exclude_renderers name name... #pragma only_renderers gles gles3#pragma exclude_renderers d3d9 d3d11 opengl, 只為指定渲染平臺(render platform)編譯
  • 引用庫。通過形如#include "UnityCG.cginc"引入指定的庫。常用的就是UnityCG.cginc了。其他庫詳見官網Built-in shader include files
  • ShaderLab內建值。Unity給Shader程式提供了便捷的、常用的值,比如下面例子中的UNITY_MATRIX_MVP就代表了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values
  • Shader輸入輸出引數語義(Semantics)。在管線流程中每個階段之間(比如Vertex Shader階段和FragmentShader階段之間)的輸入輸出引數,通過語義字串,來指定引數的含義。常用的語義包括:COLORSV_PositionTEXCOORD[n]。完整的引數語義可見HLSL Semantic(由於是HLSL的連線,所以可能不完全在Unity裡可以使用)。
  • 特別地,因為Vertex Shader的的輸入往往是管線的最開始,Unity為此內建了常用的資料結構:
資料結構 含義
appdata_base vertex shader input with position, normal, one texture coordinate.
appdata_tan vertex shader input with position, normal, tangent, one texture coordinate.
appdata_full vertex shader input with position, normal, tangent, vertex color and two texture coordinates.
appdata_img vertex shader input with position and one texture coordinate.

Shader形態之3:SurfaceShader

Shader "ShaderLab Tutorials/TestShader"
{
    Properties {   }

    // Surface Shader
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
          float4 color : COLOR;
      };
      void surf (Input IN, inout SurfaceOutput o) {
          o.Albedo = 1;
      }
      ENDCG
    }
    FallBack "Diffuse"
}
  • SurfaceShader可以認為是一個光照Shader的語法糖、一個光照VS/FS的生成器。減少了開發者寫重複程式碼的需要。
  • 在手遊,由於對效能要求比較高,所以不建議使用SurfaceShader。因為SurfaceShader是一個比較“通用”的功能,而通用往往導致效能不高。
  • 特徵是在SubShader裡出現CGPROGRAMENDCG塊。(而不是出現在Pass裡。因為SurfaceShader自己會編譯成多個Pass。)
  • 編譯指令是:#pragma surface surfaceFunction lightModel [optionalparams]
  • surfaceFunction:surfaceShader函式,形如void surf (Input IN, inout SurfaceOutput o)
  • lightModel:使用的光照模式。包括Lambert(漫反射)和BlinnPhong(鏡面反射)。
    • 也可以自己定義光照函式。比如編譯指令為#pragma surface surf MyCalc
      • 在Shader裡定義half4 LightingMyCalc (SurfaceOutput s, 引數略)函式進行處理(函式名在簽名加上了“Lighting”)。
  • 你定義輸入資料結構(比如上面的Input)、編寫自己的Surface函式處理輸入、最終輸出修改過後的SurfaceOutput。SurfaceOutput的定義為
struct SurfaceOutput {
    half3 Albedo; // 紋理顏色值(r, g, b)
    half3 Normal; // 法向量(x, y, z)
    half3 Emission; // 自發光顏色值(r, g, b)
    half Specular; // 鏡面反射度
    half Gloss; // 光澤度
    half Alpha; // 不透明度
};

Shader形態之4:Compiled Shader

點選a.shader檔案的“Compile and show code”,可以看到該檔案的“編譯”過後的ShaderLab shader檔案,檔名形如Compiled-a.shader。 其依然是ShaderLab檔案,其包含最終提交給GPU的shader程式碼字串。 先就其結構進行簡述如下,會發現和上述的編譯前ShaderLab結構很相似。


// Compiled shader for iPhone, iPod Touch and iPad, uncompressed size: 36.5KB
// Skipping shader variants that would not be included into build of current scene.
Shader "ShaderLab Tutorials/TestShader"
{
    Properties {...}
    SubShader {
        // Stats for Vertex shader:
        //        gles : 14 avg math (11..19), 1 avg texture (1..2)
        //       metal : 14 avg math (11..17)
        // Stats for Fragment shader:
        //       metal : 14 avg math (11..19), 1 avg texture (1..2)
        Pass {
            Program "vp" // vertex program
            {
                SubProgram "gles" {
                    // Stats: 11 math, 1 textures
                    Keywords{...} // keywords for shader variants ("uber shader")

                    //shader codes in string
                    "
                    #ifdef VERTEX
                    vertex shader codes
                    #endif

                    // Note, on gles, fragment shader stays here inside Program "vp"
                    #ifdef FRAGMENT
                    fragment shader codes
                    #endif
                    " 
                }

                SubProgram "metal"  {
                    some setup
                    Keywords{...}

                    //vertex shader codes in string
                    "..."
                }
            }

            Program "fp" // fragment program
            {
                SubProgram "gles" {
                    Keywords{...}
                    "// shader disassembly not supported on gles" //(because gles fragment shader codes are in Program "vp") 
                }

                SubProgram "metal" {
                    common setup
                    Keywords{...}

                    //fragment shader codes in string
                    "..."
                }
            }
        }
    }

    ...
}

Unity渲染路徑(Rendering Path)種類

概述

開發者可以在Unity工程的PlayerSettings設定對渲染路徑進行3選1:

  • Deferred Lighting,延遲光照路徑。3者中最高質量地還原光照陰影。光照效能只與最終畫素數目有關,光源數量再多都不會影響效能。
  • Forward Rendering,順序渲染路徑。能發揮出Shader全部特性的渲染路徑,當然也就支援畫素級光照。最常用、功能最自由,效能與光源數目*受光照物體數目有關,具體效能視乎其具體使用到的Shader的複雜度。
  • Vertex Lit,頂點光照路徑。頂點級光照。效能最高、相容性最強、支援特性最少、品質最差。

渲染路徑的內部階段和Pass的LightMode標籤

每個渲染路徑的內部會再分為幾個階段。 然後,Shader裡的每個Pass,都可以指定為不同的LightMode。而LightMode實際就是說:“我希望這個Pass在這個XXX渲染路徑的這個YYY子階段被執行”。

Deferred Ligting

|渲染路徑內部子階段|對應的LightMode|描述 |-|-| |Base Pass|"PrepassBase"|渲染物體資訊。即把法向量、高光度到一張ARGB32的物體資訊紋理上,把深度資訊儲存在Z-Buff上。| |Lighting Pass|無對應可程式設計Pass|根據Base Pass得出的物體資訊,在螢幕座標系下,使用BlinnPhong光照模式,把光照資訊渲染到ARGB32的光照資訊紋理上(RGB表示diffuse顏色值、A表示高光度) |Final Pass|"PrepassFinal"|根據光照資訊紋理,物體再渲染一次,將光照資訊、紋理資訊和自發光資訊最終混合。LightMap也在這個Pass進行。

Forward Rendering

|渲染路徑內部子階段|對應的LightMode|描述 |-|-| |Base Pass|"ForwardBase"|渲染:最亮一個的方向光光源(畫素級)和對應的陰影、所有頂點級光源、LightMap、所有LightProbe的SH光源(Sphere Harmonic,球諧函式,效率超高的低頻光)、環境光、自發光。| |Additional Passes|"ForwardAdd"|其他需要畫素級渲染的的光源 注意到的是,在Forward Rendering中,光源可能是畫素級光源、頂點級光源或SH光源。其判斷標準是:

  • 配製成“Not Important”的光源都是頂點級光源和SH光源
  • 最亮的方向光永遠都是畫素級光源
  • 配置成“Important”的都是畫素級光源
  • 上面2種情況加起來的畫素級光源數目小於“Quality Settings”裡面的“Pixel Light Count”的話,會把第1種情況的光源補為額外的畫素級光源。

另外,配置成“Auto”的光源有更復雜的判斷標註,截圖如下:

2014-0720-1607-31-40.png

Vertex Lit

|渲染路徑內部子階段|對應的LightMode|描述 |-|-| |Vertex|"Vertex"|渲染無LightMap物體| |VertexLMRGBM|"VertexLMRGBM"|渲染有RGBM編碼的LightMap物體 |VertexLM|"VertexLM"|渲染有雙LDR編碼的LightMap物體

不同LightMode的Pass的被選擇

一個工程的渲染路徑是唯一的,但一個工程裡的Shader是允許配有不同LightMode的Pass的。 在Unity,策略是“從工程配置的渲染路徑模式開始,按Deferred、Forward、VertxLit的順序,搜尋最匹配的LightMode的一個Pass”。 比如,在配置成Deferred路徑時,優先選有Deferred相關LightMode的Pass;找不到才會選Forward相關的Pass;還找不到,才會選VertexLit相關的Pass。 再比如,在配置成Forward路徑時,優先選Forward相關的Pass;找不到才會選VertexLit相關的Pass。

移動裝置GPU架構簡述

《The Mali GPU: An Abstract Machine》系列以Arm Mali GPU為例子給出了全面的討論,現簡述如下:

  • Part 1 - Frame Pipelining
    • Application/Geometry/Fragment三階段組成,三者中最大才是瓶頸
    • OpenGL的同步API是個“illusion”,事實上是CommandQueue(直到遇到Fence會被強制同步),以減少CPU/GPU之間的互相等待
    • Pipeline Throttle,為了更低的延遲,當GPU累積了多幀(往往是3幀,以eglSwapBuffers()Present()來區分幀)的Command時,OS會通過eglSwapBuffers()Present()來阻塞CPU讓其進入idle,從而防止更多後續Command的提交
  • Part 2 - Tile-based Rendering
    • tile-based deferred rendering (WikiPowerVR/Mali/Adreno)是重要的概念。其將Fragment一幀處理多個比如16x16的單元,併為Shader整合一個小但快的cache,從而大幅避免Shader和主記憶體之間頻寬消耗(電量消耗)
  • Part 3 - The Midgard Shader Core
    • GPU包含數個(當前常見為4-8個)Unified Shading Core,可動態分配用於Vertex Shader、Fragment Shader或Compute Kernel
    • 每個Unified Shader Core包含數個(當前常見為2個)用於SIMD計算的運算器Arithmetic Pipeline(A-pipe),1個用於紋理取樣的Texutre Pipeline(T-pipe),1個用於非紋理類的記憶體讀寫的Load/Store Pipeline(LS-pipe)比如頂點屬性寫讀、變數訪問等
    • 會進行Early-ZS測試嘗試減少Overdraw(依賴於渲染物體提交順序由前至後)
    • Arm的Forward Pixel Kill和PowerVR的Hidden Surface Removal做到畫素級別的Overdraw減少(不用依賴於渲染物體提交順序由前至後)
    • 當Shader使用discardclip、在Fragment Shader裡修改深度值、半透明,將不能進行Early-ZS,只好使用傳統的Late-ZS

參考資源

  • Youtube:https://www.youtube.com/watch?v=hDJQXzajiPg (包括part1-6)。視訊是最佳的入門方式沒有之一,所以牆裂建議就算不看下文的所有內容,都要去看一下part1。
  • 書籍:《Unity 3D ShaderLab開發實戰詳解》
  • Unity各種官方文件

作者:DonaldW 連結:https://www.jianshu.com/p/7b9498e58659 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。