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
如上圖,一句話總結:
- GameObject裡有MeshRenderer,
- MeshRenderer裡有Material列表,
- 每個Material裡有且只有一個Shader;
- 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.RenderWithShader
或Camera.SetReplacementShader
配合使用。Unity內建的RenderType包括:"Opaque"
:絕大部分不透明的物體都使用這個;"Transparent"
:絕大部分透明的物體、包括粒子特效都使用這個;"Background"
:天空盒都使用這個;"Overlay"
:GUI、鏡頭光暈都使用這個;- 使用者也可以定義任意自己的
RenderType
這個標籤所取的值。 - 應注意,
Camera.RenderWithShader
或Camera.SetReplacementShader
不要求標籤只能是RenderType
,RenderType
只是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”)搭配使用。除最重要的ForwardBase
、ForwardAdd
外,這裡需額外提醒的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
的介面(比如SetFloat
、SetTexture
編輯) - 之後在Shader程式通過
[name]
(固定管線)或直接name
(可程式設計Shader)訪問這些屬性。 - 在每一個Property前面也能類似C#那樣新增Attribute,以達到額外UI面板功能。詳見MaterialPropertyDrawer.html。
Shader中的資料型別
有3種基本數值型別:float
、half
和fixed
。
這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
;
- GL:
vInNDC = vInClip / vInClip.w
後,得出左手座標系Normalized Device Coordinates中的vInNDC
,其為w=1的Homogenous Cooridniates(故等效於Cartesian Coordinates)vInNDC = float4(xn, yn, zn, 1)
。xn
和yn
的取值範圍為[-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]
- 如果
- 如果
- GL:
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
塊、沒有CGPROGRAM
和ENDCG
塊。
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裡出現
CGPROGRAM
和ENDCG
塊 - 編譯指令
#pragma
。詳見官網Cg snippets。其中重要的包括:
編譯指令 | 示例/含義 |
---|---|
#pragma vertex name #pragma fragment name |
替換name,來指定Vertex Shader函式、Fragment Shader函式。 |
#pragma target name |
替換name(為2.0 、3.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階段之間)的輸入輸出引數,通過語義字串,來指定引數的含義。常用的語義包括:
COLOR
、SV_Position
、TEXCOORD[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裡出現
CGPROGRAM
和ENDCG
塊。(而不是出現在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”)。
- 在Shader裡定義
- 也可以自己定義光照函式。比如編譯指令為
- 你定義輸入資料結構(比如上面的
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
- 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使用
discard
或clip
、在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 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。