Shader(着色器)是一段可以針對3D對象進行操做、並被GPU所執行的程序。Shader並非一個統一的標準,不一樣的圖形接口的Shader並不相同。OpenGL的着色語言是GLSL, NVidia開發了Cg,而微軟的Direct3D使用高級着色器語言(HLSL)。而Unity的Shader 是將傳統的圖形接口的Shader(由 Cg / HLSL編寫)嵌入到獨有的描述性結構中而造成的一種代碼生成框架,最終會自動生成各硬件平臺本身的Shader,從而實現跨平臺。html
Unity Shader 其實並不難,初學者每每很迷惑是由於它有太多固定的命令和結構,而這些命令又須要咱們對3D渲染有必定的瞭解才能知道它們是作什麼的。數組
OpenGL和Direct3D都提供了三類着色器:ruby
Unity Shader 分爲 表面着色器(Surface Shader)和 頂點片斷着色器(Vertex And Fragment Shader)。app
Shader語法:框架
//Shader語法:
Shader "name" { [Properties] Subshaders [Fallback] [CustomEditor] } //Properties 語法 Properties { Property [Property ...] } // Subshader 語法 Subshader { [Tags] [CommonState] Passdef [Passdef ...] } // Pass 語法 Pass { [Name and Tags] [RenderSetup] } // Fallback 語法 Fallback "name"
基本的表面着色器示例:函數
Shader "Custom/NewShader" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
基本的頂點片斷着色器示例:性能
Shader "VertexInputSimple" { SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; fixed4 color : COLOR; }; v2f vert (appdata_base v) { v2f o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.color.xyz = v.normal * 0.5 + 0.5; o.color.w = 1.0; return o; } fixed4 frag (v2f i) : SV_Target { return i.color; } ENDCG } } }
Shader的輸入有兩個來源,一是經過屬性定義,一是經過Shader.SetGlobalXXX方法全局設置。測試
屬性定義變量:屬性定義中的變量是Shader參數的主要設置方式。 它是隨材質變化的,每一個使用該Shader的材質均可以在Inspector或者腳本中設置這些參數。這些參數除了在Shader的Properties段中定義外,還須要在Cg中聲明方可以使用。例如上面表面着色器的例子中咱們定義了_MainTex這個類型爲2D的屬性,還須要在Cg中聲明 sampler2D _MainTex。this
全局變量:Shader有一組SetGlobalXXX方法,能夠對Shader的在Cg中定義而沒有在屬性中定義的uniform變量進行設置。這個設置是全局的,全部定義了該uniform的Shader都會受到影響。例如咱們但願場景隨着時間變化而改變顏色,就能夠給場景所使用到的Shader設置統一的全局顏色變量,而後在腳本中經過設置該顏色來改變場景的顏色。在角色釋放技能時場景變黑也可使用這個方法。編碼
Unity shader 中容許定義的屬性類型有:
關鍵字 | 類型 | 對應Cg類型 | 例 |
---|---|---|---|
Float | 浮點數 | float | _MyFloat (「My float」, Float) = 0.5 |
Range | 浮點數 (在指定範圍內) | float | _MyRange (「My Range」, Range(0.01, 0.5)) = 0.1 |
Color | 浮點四元組 | float4 | _MyColor (「Some Color」, Color) = (1,1,1,1) |
Vector | 浮點四元組 | float4 | _MyVector(「Some Vector」,Vector) = (1,1,1,1) |
2D | 2的階數大小的貼圖 | sampler2D | _MyTexture (「Texture」, 2D) = 「white」 {} |
Rect | 非2的階數大小的貼圖 | sampler2D | _MyRect(「My Rect」, Rect) = 「white」 {} |
CUBE | CubeMap | samplerCUBE | _MyCubemap (「Cubemap」, CUBE) = 「」 {} |
注:CubeMap 是6張有聯繫的2D貼圖的組合主要用來作反射效果(好比天空盒和動態反射)
SubShader中除了Pass,有兩個標籤值得關注:LOD和Tags
LOD是 Level of Detail的簡寫,確切地說是Shader Level of Detail的簡寫,由於Unity中還有一個模型的LOD概念,這是兩個不一樣的東西。咱們這裏只介紹Shader中LOD,模型的LOD請參考這裏。
Shader LOD 就是讓咱們設置一個數值,這個數值決定了咱們能用什麼樣的Shader。能夠經過Shader.maximumLOD或者Shader.globalMaximumLOD 設定容許的最大LOD,當設定的LOD小於SubShader所指定的LOD時,這個SubShader將不可用。經過LOD,咱們就能夠爲某個材質寫一組SubShader,指定不一樣的LOD,LOD越大則渲染效果越好,固然對硬件的要求也可能越高,而後根據不一樣的終端硬件配置來設置 globalMaximumLOD來達到兼顧性能的最佳顯示效果。
Unity內建Shader定義了一組LOD的數值,咱們在實現本身的Shader的時候能夠將其做爲參考來設定本身的LOD數值
SubShader能夠被若干的標籤(tags)所修飾,而硬件將經過斷定這些標籤來決定何時調用該着色器。
比較常見的標籤有:
名字 | 值 | 描述 |
---|---|---|
Background | 1000 | 最先被調用的渲染,用來渲染天空盒或者背景 |
Geometry | 2000 | 這是默認值,用來渲染非透明物體(普通狀況下,場景中的絕大多數物體應該是非透明的) |
AlphaTest | 2450 | 用來渲染通過Alpha Test的像素,單獨爲AlphaTest設定一個Queue是出於對效率的考慮 |
Transparent | 3000 | 以從後往前的順序渲染透明物體 |
Overlay | 4000 | 用來渲染疊加的效果,是渲染的最後階段(好比鏡頭光暈等特效) |
SubShader中能夠定義一組Render State,基本上就是一些渲染的開關選項,他們對該SubShader的全部的Pass都有效,因此稱Common。這些Render State也能夠在每一個Pass中分別定義,將在Pass中詳細介紹。
Render State主要就是控制渲染過程的一些開關選項,例如是否開啓alpha blending ,是否開啓depth testing。
經常使用的Render State有:
Cull
用法:Cull Back | Front | Off
多邊形表面剔除開關。Back表示背面剔除,Front表示正面剔除,Off表示關閉表面剔除即雙面渲染。有時候如裙襬,飄帶之類很薄的東西在建模時會作成一個面片,這就須要設置Cull Off來雙面渲染,不然背面會是黑色。
ZWrite
用法:ZWrite On | Off
控制當前對象的像素是否寫入深度緩衝區(depth buffer),默認是開啓的。通常來講繪製不透明物體的話ZWrite開啓,繪製透明或半透明物體則ZWrite關閉。
深度緩衝區:當圖形處理卡渲染物體的時候,每個所生成的像素的深度(即 z 座標)就保存在一個緩衝區中。這個緩衝區叫做 z 緩衝區或者深度緩衝區,這個緩衝區一般組織成一個保存每一個屏幕像素深度的 x-y 二維數組。若是場景中的另一個物體也在同一個像素生成渲染結果,那麼圖形處理卡就會比較兩者的深度,而且保留距離觀察者較近的物體。而後這個所保留的物體點深度保存到深度緩衝區中。最後,圖形卡就能夠根據深度緩衝區正確地生成一般的深度感知效果:較近的物體遮擋較遠的物體。
理解了深度緩衝區也就理解了爲何繪製透明或半透明物體須要關閉ZWrite, 若是不關閉,透明物體的depth也會被寫入深度緩衝區,從而會剔除掉它後面的物體,後面的物體就不會被渲染,看不見後面的物體還能叫透明嗎?所以咱們使用Alpha blending的時候須要設置ZWrite Off。
ZTest
用法:ZTest (Less | Greater | LEqual | GEqual | Equal | NotEqual | Always)
控制如何進行深度測試,也就是上面說的圖形處理卡比較兩者的深度的比較方法。默認是LEqual。
值得一提的是使用Aplha blending的時候ZWrite須要關閉可是ZTest是要開啓的,由於若是透明物體前面還有不透明物體,透明物體仍是應該被遮擋剔除的。
Blend
混合。控制了每一個Shader的輸出如何和屏幕上已有的顏色混合。
用法:
Blend Off: 關閉混合
Blend SrcFactor DstFactor:最終顏色 = Shader產生的顏色 × SrcFactor + 屏幕上原來的顏色 × DstFactor
Blend SrcFactor DstFactor, SrcFactorA DstFactor:和上面同樣,只是Alpha通道使用後面兩個參數計算
經常使用的Blend模式有:
Blend SrcAlpha OneMinusSrcAlpha // Alpha blending
Blend One One // Additive
Blend OneMinusDstColor One // Soft Additive
Blend DstColor Zero // Multiplicative
Blend DstColor SrcColor // 2x Multiplicative
具體參考這裏
Unity5開始下列固定功能的Shader命令被標記爲過期了,這些命令的功能如今建議在Shader(Cg)中經過代碼來實現,這裏列出是爲了方便閱讀之前寫的Shader:
Surface Shader 隱藏了不少光照處理的細節,它的設計初衷是爲了讓用戶僅僅使用一些指令(#pragma)就能夠完成不少事情,而且封裝了不少經常使用的光照模型和函數。相比底層的Vertex And Fragment Shader,Suface Shader的限制比較多,它只能有一次Pass。若是作一些常規的功能又須要光照,能夠用Surface Shader寫,比較快速便捷。若是要寫比較高級的Shader仍是建議使用Vertex Shader 和 Fragment Shader。
Surface Shader主要有兩部分組成,一個是#pragma後面的指令,一個是surf函數。
pragma的語法是 #pragma surface surfaceFunction lightModel [optionalparams]
- surfaceFunction 一般就是名爲surf的函數, 函數名能夠本身取
surf函數原型是:void surf (Input IN, inout SurfaceOutput o)
- lightModel是Unity內置的光照模型,能夠是Lambert,Blinn-Phong等。
- optionalparams: 包含不少指令 詳細參數參考這裏
surf函數主要有一個Input結構的輸入和SurfaceOutput結構的輸出。
Input 結構須要在Shader中定義。它能夠包含以下字段, 若是你定義了這些字段就能夠在surf函數中使用它們(好神奇的黑科技)
float2 uv_MainTex
SurfaceOutput 描述了表面的特性(光照的顏色反射率、法線、散射、鏡面等),這個結構是固定的,不須要在Shader中再定義。
struct SurfaceOutput { half3 Albedo; //反射率,通常就是在光照以前的原始顏色 half3 Normal; //法線 half3 Emission; //自發光,用於加強物體自身的亮度,使之看起來好像能夠本身發光 half Specular; //鏡面 half Gloss; //光澤 half Alpha; //透明 };
Unity5 因爲引入了基於物理的光照模型,因此新增長了兩個Output
struct SurfaceOutputStandard { fixed3 Albedo; // base (diffuse or specular) color fixed3 Normal; // tangent space normal, if written half3 Emission; half Metallic; // 0=non-metal, 1=metal half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies }; struct SurfaceOutputStandardSpecular { fixed3 Albedo; // diffuse color fixed3 Specular; // specular color fixed3 Normal; // tangent space normal, if written half3 Emission; half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies };
Unity提供了一些基本的SurfaceShader的例子,有助於咱們理解輸入輸出是如何被使用的。
Unity提供的SurfaceShader的例子
若是不想使用Surface Shader而直接編寫opengl和Direct3D中常見的頂點着色器和片斷着色器,能夠經過Cg代碼段嵌入到Pass中:
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 ENDCG // ... the rest of pass setup ... }
其中vert就是頂點着色器函數,frag就是片斷着色器函數。通常來講,能夠在頂點着色器中進行的計算就不該該放到片斷着色器中去算,由於頂點着色器是逐頂點計算的而片斷着色器是逐像素計算的,一個模型頂點總比代表像素少不少吧。
編寫頂點和片斷着色器通常須要包含Unity預約義的一個幫助文件UnityCG.cginc,裏面預約義了一些經常使用的結構和方法。Windows版Unity這個文件位於({unity install path}/Data/CGIncludes/UnityCG.cginc
。 Mac版位於/Applications/Unity/Unity.app/Contents/CGIncludes/UnityCG.cginc
。
在代碼中咱們只須要添加 #include "UnityCG.cginc"
就可使用裏面的結構和方法。
頂點着色器的原型是 v2f vert (appdata v)
appdata 是輸入,能夠本身定義也可使用Unity預約義的。Unity在UnityCG.cginc預約義了三種經常使用的輸入結構:appdata_base,appdata_tan,appdata_full。
struct appdata_base { float4 vertex : POSITION; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; }; struct appdata_tan { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; }; struct appdata_full { float4 vertex : POSITION; float4 tangent : TANGENT; float3 normal : NORMAL; float4 texcoord : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 texcoord2 : TEXCOORD2; float4 texcoord3 : TEXCOORD3; #if defined(SHADER_API_XBOX360) half4 texcoord4 : TEXCOORD4; half4 texcoord5 : TEXCOORD5; #endif fixed4 color : COLOR; };
咱們注意到這些結構的字段和表面着色器中的字段不一樣,後面多了一個冒號和一個標籤。這是該字段的語義,用於告訴GPU這個字段的數據應該去哪裏讀寫。GPU畢竟是爲了圖形計算而特別設計的東西,不少東西都是固定的,咱們只要記得有這麼幾個名字能夠用行了。
類型 | 名字 | 標籤 | 備註 |
---|---|---|---|
float4 | vertex | POSITION | 頂點在模型座標系下的位置 |
float3 | normal | NORMAL | 頂點的法向量 |
float4 | tangent | TANGENT | 頂點的切向量 |
float4 | color | COLOR | 頂點色 |
float4 | texcoord | TEXCOORD0 | 頂點的第一個uv座標 |
float4 | texcoord1 | TEXCOORD1 | 頂點的第二個uv座標,最多能夠到5 |
頂點着色器的輸出是也是一個能夠本身定義的結構,可是結構內容也是比較固定的,通常包含了頂點投影后的位置,uv,頂點色等,也能夠加一些後面片斷着色器須要用到可是須要在頂點着色器中計算的值。這個輸出就是後面片斷着色器的輸入。
struct v2f { float4 pos : SV_POSITION; half2 uv : TEXCOORD0; };
可使用的字段有:
類型 | 標籤 | 描述 |
---|---|---|
float4 | SV_POSITION | 頂點在投影空間下的位置,注意和輸入的模型座標系下的位置不一樣,這個字段必必須設置,這個座標轉換是頂點着色器的重要工做 |
float3 | NORMAL | 頂點在視圖座標系下的法向量 |
float4 | TEXCOORD0 | 第一張貼圖的uv座標 |
float4 | TEXCOORD1 | 第二張貼圖的uv座標 |
float4 | TANGENT | 切向量,主要用來修正法線貼圖Normal Maps |
fixed4 | COLOR | 第一個定點色 |
fixed4 | COLOR1 | 第二個定點色 |
Any | Any | 其餘自定義的字段 |
頂點着色器有一項重要的工做就是進行座標變換。頂點着色器的輸入中的座標是模型座標系(ObjectSpace)下的座標,而最終繪製到屏幕上的是投影座標。
在咱們Shader裏面只須要一句話就能夠完成座標的轉換,這也是最簡單的頂點着色器:
v2f vert(appdata v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
return o; }
用UNITY_MATRIX_MVP矩陣乘以頂點在模型座標系下的座標就獲得投影座標。
UNITY_MATRIX_MVP是Unity內建的模型->視->投影矩陣, Unity內建矩陣以下:
下面簡單介紹一下里面提到的幾個座標系:
模型座標系:也叫物體座標系,3D建模的時候每一個模型都是在本身的座標系下創建的,若是一我的物模型腳底是(0,0,0) 點的話它的身上其它點的座標都是相對腳底這個原點的。
世界座標系:咱們場景是一個世界,有本身的原點,模型放置到場景中後模型上的每一個頂點就有了一個新的世界座標。這個座標能夠經過模型矩陣×模型上頂點的模型座標獲得。
視圖座標系:又叫觀察座標系,是以觀察者(相機)爲原點的座標系。場景中的物體只有被相機觀察到纔會繪製到屏幕上,相機能夠設置視口大小和裁剪平面來控制可視範圍,這些都是相對相機來講的,因此須要把世界座標轉換到視圖座標系來方便處理。
投影座標系:場景是3D的,可是最終繪製到屏幕上是2D,投影座標系完成這個降維的工做,投影變換後3D的座標就變成2D的座標了。投影有平行投影和透視投影兩種,能夠在Unity的相機上設置。
屏幕座標系 : 最終繪製到屏幕上的座標。屏幕的左下角爲原點。
除了內建矩陣,Unity還內建了一些輔助函數也能夠在頂點着色器裏面使用: