1.先來一段單張紋理貼圖的shader示例代碼:html
1 // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 3 Shader "Custom/MyShader"{ 4 Properties{ 5 //外部可調屬性 6 _MainTex ("Main Tex", 2D) = "white" {} 7 } 8 SubShader 9 { 10 //設置使用本Subshader所須要知足的渲染路徑等參數,若是知足,即便用本shader 11 Tags {"Queue"="Transparent""RenderType"="Transparent"} 12 Pass 13 { 14 //設置該pass 的渲染狀態和標籤 15 Tags { "LightMode"="ForwardBase" } 16 //開始cg代碼片斷 17 CGPROGRAM 18 //該代碼的編譯指令, 19 #pragma vertex vert//設置頂點着色器的函數名稱 20 #pragma fragment frag//設置片斷着色器的函數名稱 21 #include "Lighting.cginc"//包含庫文件 22 23 struct a2v { 24 float4 vertex : POSITION;//當前要渲染的頂點座標 25 float4 texcoord : TEXCOORD0;//當前要渲染的頂點的第一套紋理座標 26 }; 27 28 struct v2f { 29 float4 pos : SV_POSITION;// 30 float2 uv : TEXCOORD0; 31 }; 32 sampler2D _MainTex; 33 float4 _MainTex_ST; 34 35 v2f vert(a2v v) 36 { 37 v2f o; 38 o.pos=UnityObjectToClipPos(v.vertex);//將頂點座標變換到裁剪空間中 39 o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);//變換uv座標 40 return o; 41 } 42 43 fixed4 frag(v2f i):SV_Target 44 { 45 fixed4 c = tex2D (_MainTex, i.uv);//對紋理座標進行採樣 46 return c; 47 } 48 ENDCG 49 } 50 //SubShader 51 //{ 52 // //若是不知足第一個subshader所須要的條件,那麼再來檢測是否知足第二subshader 的條件。 53 //} 54 } 55 FallBack "Diffuse" 56 }
Shader "ShaderLab Tutorials/TestShader" { SubShader { //... } }
一個Shader有多個SubShader。一個SubShader可理解爲一個Shader的一個渲染方案。即SubShader是爲了針對不一樣的渲染狀況而編寫的。每一個Shader至少1個SubShader、理論能夠無限多個,但每每兩三個就足夠。
一個時刻只會選取一個SubShader進行渲染,具體SubShader的選取規則包括:android
按此規則第一個被選取的SubShader將會用於渲染,未被選取的SubShader在此次渲染將被忽略。編程
Shader "ShaderLab Tutorials/TestShader" { SubShader { Tags { "Queue"="Geometry+10" "RenderType"="Opaque" } //... } }
shader內部能夠有標籤(Tags)的定義。Tag指定了這個SubShader的渲染順序(時機),以及其餘的一些設置。windows
"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。"Distort"="On"
,而後將"Distort"
做爲參數replacementTag
傳給函數。此時,做爲replacementShader
實參的ShaderB.SubShaderB1中如有也有如出一轍的"Distort"="On"
,則此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的非官方經驗總結是,一幀的渲染隊列的生成,依次決定於每一個渲染物體的:sass
這個渲染隊列決定了以後(可能有dirty flag的機制?)渲染器再依次遍歷這個渲染隊列,「同一種」材質的渲染物體合到一個Batch裏。數據結構
Shader "ShaderLab Tutorials/TestShader" { SubShader { Pass { //... } } }
一個SubShader(渲染方案)是由一個個Pass塊來執行的。每一個Pass都會消耗對應的一個DrawCall。在知足渲染效果的狀況下儘量地減小Pass的數量。架構
Shader "ShaderLab Tutorials/TestShader" { SubShader { Pass { Tags{ "LightMode"="ForwardBase" } //... } } }
shader有本身專屬的Tag相似,Pass也有Pass專屬的Tag。
其中最重要Tag是 "LightMode"
,指定Pass和Unity的哪種渲染路徑(「Rendering Path」)搭配使用。除最重要的ForwardBase
、ForwardAdd
外,這裏需額外提醒的Tag取值可包括:app
Always
,永遠都渲染,但不處理光照ShadowCaster
,用於渲染產生陰影的物體ShadowCollector
,用於收集物體陰影到屏幕座標Buff裏。其餘渲染路徑相關的Tag詳見下面章節「Unity渲染路徑種類」。
具體全部Tag取值,可參考ShaderLab syntax: Pass Tags。編輯器
Shader "ShaderLab Tutorials/TestShader"{ SubShader { Pass {} } FallBack "Diffuse" // "Diffuse"即Unity預製的固有Shader // FallBack Off //將關閉FallBack }
當本Shader的全部SubShader都不支持當前顯卡,就會使用FallBack語句指定的另外一個Shader。FallBack最好指定Unity本身預製的Shader實現,因其通常可以在當前全部顯卡運行。ide
1 Shader "ShaderLab Tutorials/TestShader" 2 { 3 Properties { 4 _Range ("My Range", Range (0.02,0.15)) = 0.07 // sliders 5 _Color ("My Color", Color) = (.34, .85, .92, 1) // color 6 _2D ("My Texture 2D", 2D) = "" {} // textures 7 _Rect("My Rectangle", Rect) = "name" { } 8 _Cube ("My Cubemap", Cube) = "name" { } 9 _Float ("My Float", Float) = 1 10 _Vector ("My Vector", Vector) = (1,2,3,4) 11 12 // Display as a toggle. 13 [Toggle] _Invert ("Invert color?", Float) = 0 14 // Blend mode values 15 [Enum(UnityEngine.Rendering.BlendMode)] _Blend ("Blend mode", Float) = 1 16 //setup corresponding shader keywords. 17 [KeywordEnum(Off, On)] _UseSpecular ("Use Specular", Float) = 0 18 } 19 20 // Shader 21 SubShader{ 22 Pass{ 23 //... 24 uniform float4 _Color; 25 //... 26 float4 frag() : COLOR{ return fixed4(_Color); } 27 //... 28 #pragma multi_compile __ _USESPECULAR_ON 29 } 30 } 31 32 //fixed pipeline 33 SubShader { 34 Pass{ 35 Color[_Color] 36 } 37 } 38 }
Material
的接口(好比SetFloat
、SetTexture
編輯)[name]
(固定管線)或直接name
(可編程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
。當提到「Row-Major」、「Column-Major」,根據不一樣的場合,它們可能指不一樣的意思:
上述狀況,互不相干。
而後,ShaderLab中,數學上是Column Vector、訪問接口上是Row-Major、存儲上是(還沒有查明)。
通常狀況下,從Vertex Buff輸入頂點到Vertex Shader,
vInModel
,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)
;xc=(2nx+rz+lz)/(r-l)
;yc=(2ny+tz+bz)/(t-b)
;zc=(-fz-nz-2nf)/(f-n)
;wc=-z
;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)
。xn
和yn
的取值範圍爲[-1,1]。
zn=zc/wc=(fz+nz+2nf)/((f-n)z)
;zn=zc/wc=(fz+nf)/((f-n)z)
;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]1 v2f vert (appdata v) 2 { 3 v2f o; 4 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); 5 // 1 、二、3是等價的,和4是不等價的 6 // 由於是M在左、V在右,因此是Column Vector 7 // 由於是HLSL/CG語言,因此是訪問方式是Row-Major 8 o.rootInView = mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1)); // 1 9 o.rootInView = float4(UNITY_MATRIX_MV[0].w, UNITY_MATRIX_MV[1].w, UNITY_MATRIX_MV[2].w, 1); // 2 10 o.rootInView = UNITY_MATRIX_MV._m03_m13_m23_m33; // 3 11 //o.rootInView = UNITY_MATRIX_MV[3]; // 4 12 13 return o; 14 } 15 16 fixed4 frag (v2f i) : SV_Target 17 { 18 // 由於是ViewSpace是右手座標系,因此當root在view前面的時候,z是負數,因此須要-z才能正確顯示顏色 19 fixed4 col = fixed4(i.rootInView.x, i.rootInView.y, -i.rootInView.z, 1); 20 return col; 21 } 22 23 struct appdata 24 { 25 float4 vertex : POSITION; 26 }; 27 struct v2f 28 { 29 float4 rootInView : TEXCOORD0; 30 float4 vertex : SV_POSITION; 31 };
固定管線是爲了兼容老式顯卡。都是頂點光照。以後固定管線多是被Unity拋棄的功能,因此最好不學它、當它不存在。特徵是裏面出現了形以下面Material
塊、沒有CGPROGRAM
和ENDCG
塊。
1 Shader "ShaderLab Tutorials/TestShader" 2 { 3 Properties { 4 _Color ("My Color", Color) = (.34, .85, .92, 1) // color 5 } 6 7 // Fixed Pipeline 8 SubShader 9 { 10 Pass 11 { 12 Material{ 13 Diffuse [_Color] 14 Ambient [_Color] 15 } 16 17 Lighting On 18 } 19 } 20 }
1 Shader "ShaderLab Tutorials/TestShader" 2 { 3 Properties {} 4 5 SubShader 6 { 7 Pass 8 { 9 // ... the usual pass state setup ... 10 11 CGPROGRAM 12 // compilation directives for this snippet, e.g.: 13 #pragma vertex vert 14 #pragma fragment frag 15 16 // the Cg/HLSL code itself 17 float4 vert(float4 v:POSITION) : SV_POSITION{ 18 return mul(UNITY_MATRIX_MVP, v); 19 } 20 float4 frag() : COLOR{ 21 return fixed4(1.0, 0.0, 0.0, 1.0); 22 } 23 ENDCG 24 // ... the rest of pass setup ... 25 } 26 } 27 }
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。UNITY_MATRIX_MVP
就表明了這個時刻的MVP矩陣。詳見官網ShaderLab built-in values。COLOR
、SV_Position
、TEXCOORD[n]
。完整的參數語義可見HLSL Semantic(因爲是HLSL的鏈接,因此可能不徹底在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. |
1 Shader "ShaderLab Tutorials/TestShader" 2 { 3 Properties { } 4 5 // Surface Shader 6 SubShader { 7 Tags { "RenderType" = "Opaque" } 8 CGPROGRAM 9 #pragma surface surf Lambert 10 struct Input { 11 float4 color : COLOR; 12 }; 13 void surf (Input IN, inout SurfaceOutput o) { 14 o.Albedo = 1; 15 } 16 ENDCG 17 } 18 FallBack "Diffuse" 19 }
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
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; // 不透明度 };
點擊a.shader
文件的「Compile and show code」,能夠看到該文件的「編譯」事後的ShaderLab shader文件,文件名形如Compiled-a.shader
。
其依然是ShaderLab文件,其包含最終提交給GPU的shader代碼字符串。
先就其結構進行簡述以下,會發現和上述的編譯前ShaderLab結構很類似。
1 // Compiled shader for iPhone, iPod Touch and iPad, uncompressed size: 36.5KB 2 // Skipping shader variants that would not be included into build of current scene. 3 Shader "ShaderLab Tutorials/TestShader" 4 { 5 Properties {...} 6 SubShader { 7 // Stats for Vertex shader: 8 // gles : 14 avg math (11..19), 1 avg texture (1..2) 9 // metal : 14 avg math (11..17) 10 // Stats for Fragment shader: 11 // metal : 14 avg math (11..19), 1 avg texture (1..2) 12 Pass { 13 Program "vp" // vertex program 14 { 15 SubProgram "gles" { 16 // Stats: 11 math, 1 textures 17 Keywords{...} // keywords for shader variants ("uber shader") 18 19 //shader codes in string 20 " 21 #ifdef VERTEX 22 vertex shader codes 23 #endif 24 25 // Note, on gles, fragment shader stays here inside Program "vp" 26 #ifdef FRAGMENT 27 fragment shader codes 28 #endif 29 " 30 } 31 32 SubProgram "metal" { 33 some setup 34 Keywords{...} 35 36 //vertex shader codes in string 37 "..." 38 } 39 } 40 41 Program "fp" // fragment program 42 { 43 SubProgram "gles" { 44 Keywords{...} 45 "// shader disassembly not supported on gles" //(because gles fragment shader codes are in Program "vp") 46 } 47 48 SubProgram "metal" { 49 common setup 50 Keywords{...} 51 52 //fragment shader codes in string 53 "..." 54 } 55 } 56 } 57 } 58 59 ... 60 }
開發者能夠在Unity工程的PlayerSettings設置對渲染路徑進行3選1:
每一個渲染路徑的內部會再分爲幾個階段。
而後,Shader裏的每一個Pass,均可以指定爲不一樣的LightMode。而LightMode實際就是說:「我但願這個Pass在這個XXX渲染路徑的這個YYY子階段被執行」。
渲染路徑內部子階段 | 對應的LightMode | 描述 |
---|---|---|
Base Pass | "PrepassBase" |
渲染物體信息。即把法向量、高光度到一張ARGB32的物體信息紋理上,把深度信息保存在Z-Buff上。 |
Lighting Pass | 無對應可編程Pass | 根據Base Pass得出的物體信息,在屏幕座標系下,使用BlinnPhong光照模式,把光照信息渲染到ARGB32的光照信息紋理上(RGB表示diffuse顏色值、A表示高光度) |
Final Pass | "PrepassFinal" |
根據光照信息紋理,物體再渲染一次,將光照信息、紋理信息和自發光信息最終混合。LightMap也在這個Pass進行。 |
渲染路徑內部子階段 | 對應的LightMode | 描述 |
---|---|---|
Base Pass | "ForwardBase" |
渲染:最亮一個的方向光光源(像素級)和對應的陰影、全部頂點級光源、LightMap、全部LightProbe的SH光源(Sphere Harmonic,球諧函數,效率超高的低頻光)、環境光、自發光。 |
Additional Passes | "ForwardAdd" |
其餘須要像素級渲染的的光源 |
注意到的是,在Forward Rendering中,光源多是像素級光源、頂點級光源或SH光源。其判斷標準是:
另外,配置成「Auto」的光源有更復雜的判斷標註,截圖以下:
具體可參考Forward Rendering Path Details。
渲染路徑內部子階段 | 對應的LightMode | 描述 |
---|---|---|
Vertex | "Vertex" |
渲染無LightMap物體 |
VertexLMRGBM | "VertexLMRGBM" |
渲染有RGBM編碼的LightMap物體 |
VertexLM | "VertexLM" |
渲染有雙LDR編碼的LightMap物體 |
一個工程的渲染路徑是惟一的,但一個工程裏的Shader是容許配有不一樣LightMode的Pass的。
在Unity,策略是「從工程配置的渲染路徑模式開始,按Deferred、Forward、VertxLit的順序,搜索最匹配的LightMode的一個Pass」。
好比,在配置成Deferred路徑時,優先選有Deferred相關LightMode的Pass;找不到纔會選Forward相關的Pass;還找不到,纔會選VertexLit相關的Pass。
再好比,在配置成Forward路徑時,優先選Forward相關的Pass;找不到纔會選VertexLit相關的Pass。
《The Mali GPU: An Abstract Machine》系列以Arm Mali GPU爲例子給出了全面的討論,現簡述以下:
eglSwapBuffers()
或Present()
來區分幀)的Command時,OS會經過eglSwapBuffers()
或Present()
來阻塞CPU讓其進入idle,從而防止更多後續Command的提交discard
或clip
、在Fragment Shader裏修改深度值、半透明,將不能進行Early-ZS,只好使用傳統的Late-ZS