簡單的說,Shader是爲渲染管線中的特定處理階段提供算法的一段代碼。Shader是伴隨着可編程渲染管線出現的,從而能夠對渲染過程加以控制。算法
1. Unity提供了不少內建的Shader,這些能夠從官網下載,打開looking for older version的連接就能看到Build-in shaders。選擇合適的Shader很重要,如下是開銷從低到高的排序:編程
(1)Unlit:僅使用紋理顏色,不受光照影響app
(2)VertexLit:頂點光照dom
(3)Diffuse:漫反射函數
(4)Specular:在漫反射基礎上增長高光計算優化
(5)Normal mapped:法線貼圖,增長了一張法線貼圖和幾個着色器指令ui
(6)Normal Mapped Specular:帶高光法線貼圖spa
(7)Parallax Normal Mapped:視差法線貼圖,增長了視差貼圖的計算開銷code
(8)Parallax Normal Mapped Specular:帶高光視差法線貼圖orm
對於如今流行的移動平臺遊戲,Unity提供了幾種專門的着色器放在Shader->Mobile下,它們是專門優化過的。
2. 在Unity中,能夠編寫3種類型的Shader:
表面着色器(Surface Shaders):最經常使用的Shader,能夠與燈光、陰影、投影器交互,以Cg/HLSL語言進行編寫,不涉及光照時儘可能不要使用。
頂點和片斷着色器(Vertex and Fragment Shaders):全屏圖像效果,代碼比較多,以Cg/HLSL編寫,難以和光照交互。
固定功能管線着色器(Fixed Function Shaders):遊戲要運行在不支持可編程管線的老舊機器上時,須要用ShaderLab語言來編寫。
不管編寫哪一種Shader,實際的Shader代碼都須要嵌入ShaderLab代碼中,Unity經過ShaderLab代碼來組織Shader結構。
下面是我新建的一個Shader的默認內容:
Shader "Custom/TestShader" { 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" }
Properties:用來定義着色器中使用的貼圖資源或者數值參數等,這裏定義了一個Base (RGB)的2D紋理。
SubShader:一個着色器包含的一個或多個子着色器。Unity從上到下遍歷子着色器,找到第一個能被設備支持的着色器。
FallBack:備用着色器,一個對硬件要求最低的Shader名字。
(1)Properties定義的屬性
名稱("顯示名稱", Vector) = 默認向量值,一個四維向量 名稱("顯示名稱", Color) = 默認顏色值,一個顏色(取值0~1的四維向量)屬性 名稱("顯示名稱", Float) = 默認浮點數值,一個浮點數 名稱("顯示名稱", Range(min,max)) = 默認浮點數值,一個浮點數,取值min~max 名稱("顯示名稱", 2D) = 默認貼圖名稱{選項},一個2D紋理屬性 名稱("顯示名稱", Rect) = 默認貼圖名稱{選項},一個矩形紋理屬性(非2的n次冪) 名稱("顯示名稱", Cube) = 默認貼圖名稱{選項},一個立方體紋理屬性
選項指的是一些紋理的可選參數,包括:
TexGen:紋理生成模式,能夠是ObjectLinear、EyeLinear、SphereMap、CubeReflect、CubeNormal中的一種。若是使用了自定義的頂點程序,這些參數會被忽略。
LightmapMode:紋理將受渲染器的光照貼圖參數影響。紋理將不會從材質中獲取,而是取自渲染器的設置。
示例以下:
Properties { _RefDis ("Reflect Distance", Range(0, 1)) = 0.3 //範圍數值 _Color ("Reflect Color", Color) = (.34, .85, .92, 1) //顏色 _MainTex ("Reflect Color", 2D) = "white"{} //紋理 }
經常使用的變量類型以下:
顏色和向量:float4, half4, fixed4 範圍和浮點數:float, half, fixed 2D紋理貼圖:sampler2D Cubemap:samplerCUBE 3D紋理貼圖:sampler3D
(2)SubShader,子着色器由標籤(可選)、通用狀態(可選)、Pass列表組成。使用子着色器渲染時,每一個pass都會渲染一次對象,因此應儘可能減小Pass數量。
(3)Category,分類用於提供讓子着色器繼承的命令。
3. 表面着色器,使用Cg/HLSL編寫,而後嵌在ShaderLab的結構代碼中使用。僅需編寫最關鍵的表面函數,其他代碼由Unity生成,包括適配各類光源類型、渲染實時陰影以及集成到前向/延遲渲染管線中。若是你須要的效果與光照無關,最好不要使用表面着色器,不然會進行不少沒必要要的光照計算。使用#pragma surface...來指明是一個表面着色器。輸入結構體Input通常包含必須的紋理座標,還能夠在輸入結構中加入一些附加數據。
CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; fixed4 _Color; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG
SurfaceOutpu描述了表面的各類參數,它的標準結構以下:
struct SurfaceOutput{ half3 Albedo; //反射光
half3 Normal; //法線
half3 Emission; //自發光
half Specular; //高光
half Gloss; //光澤度
half Alpha; //透明度
}
4. 頂點和片斷着色器,運行於具備可編程渲染管線的硬件上,它包括頂點程序和片斷程序。使用該着色器渲染時,固定功能管線將會關閉,即編寫好的頂點程序替代原有的3D變換、光照、紋理座標生成等功能,片斷程序會替換掉SetTexture命令中的紋理混合模式。代碼使用Cg/HLSL編寫,放在Pass命令中,格式以下:
SubShader{ Pass{ CGPROGRAM #pragma vertex vert
#pragma fragment frag
//cg code
ENDCG } }
編譯命令說明以下:
#pragma vertex name-----------------------------將函數name的代碼編譯成頂點程序
#pragma fragment name---------------------------將函數name的代碼編譯成片斷程序
#pragma geometry name---------------------------將函數name的代碼編譯成DX10的幾何着色器
#pragma hull name-------------------------------將函數name的代碼編譯成DX11的hull着色器
#pragma domain name-----------------------------將函數name的代碼編譯成DX11的domain着色器
#pragma fragmentoption option-------------------添加選項到編譯的OpenGL片斷程序,對於頂點程序或編譯目標不是OpenGL的無效
#pragma target name-----------------------------設置着色器的編譯目標
#pragma only_renderers space separated names----僅編譯到指定的渲染平臺
#pragma exclude_renderers space separated names-不編譯到指定的渲染平臺
#pragma glsl------------------------------------爲桌面系統的OpenGL進行編譯時,將Cg/HLSL代碼轉換成GLSL代碼
#pragma glsl_no_auto_normalization--------------編譯到移動平臺GLSL時,關閉頂點着色器中對法線和切線進行自動規範化
示例代碼,使用命令:
Shader "Custom/Shader1" { Properties { _Color("Main Color", Color) = (1,1,1, 0.5) _SpecColor("Spec Color", Color) = (1,1,1,1) _Emission("Emmisive Color", Color) = (0,0,0,0) _Shininess("Shininess", Range(0.01, 1)) = 0.7 _MainTex("Base (RGB)", 2D) = "white" {} } SubShader { Pass{ Material{ Diffuse[_Color] Ambient[_Color] Shininess[_Shininess] Specular[_SpecColor] Emission[_Emission] } Lighting On SeparateSpecular On SetTexture[_MainTex]{ constantColor[_Color] Combine texture * primary DOUBLE, texture * constant } } } FallBack "Diffuse" }
示例代碼,使用Cg。其中的包含文件能夠在
Shader "Custom/Shader2" { //定義屬性(變量)
Properties { _MainTex ("Texture", 2D) = "white" {} //紋理
_Color ("Main Color", Color) = (1,1,1,0.5) //顏色
} //子着色器
SubShader { //每一個Pass中,對象幾何體都被渲染一次
Pass{ CGPROGRAM //Cg代碼開始
#pragma vertex vert //將函數vert編譯爲頂點程序
#pragma fragment frag //將函數frag編譯爲片斷程序
//包含一個內置的cg文件,提供了經常使用的聲明和函數,好比appdata_base結構
#include "UnityCG.cginc" float4 _Color; //變量,顏色的向量表示
sampler2D _MainTex; float4 _MainTex_ST; //定義一個結構體v2f
struct v2f{ float4 pos:SV_POSITION; float2 uv:TEXCOORD0; }; //頂點處理程序
v2f vert(appdata_base v) { v2f o; //3D座標被投影到2D窗口中,與矩陣Model-View-Projection相乘
o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); return o; } //片斷處理程序
half4 frag(v2f i):COLOR { half4 texcol = tex2D(_MainTex, i.uv); //自定義顏色_Color與紋理的融合
return texcol * _Color; } ENDCG //Cg代碼結束
} } //備用着色器
FallBack "VertexLit" }
上面的例子用到了一些內置的變量,有下面這些:
UNITY_MATRIX_MVP--------------------------當前的model*view*projection矩陣
UNITY_MATRIX_MV---------------------------當前的model*view矩陣
UNITY_MATRIX_V----------------------------當前的view矩陣
UNITY_MATRIX_P----------------------------當前的projection矩陣
UNITY_MATRIX_VP---------------------------當前的view*projection矩陣
UNITY_MATRIX_T_MV-------------------------model*view矩陣的轉置矩陣
UNITY_MATRIX_IT_MV------------------------model*view矩陣的轉置逆矩陣
UNITY_MATRIX_TEXTURE0
UNITY_MATRIX_TEXTURE1
UNITY_MATRIX_TEXTURE2
UNITY_MATRIX_TEXTURE3---------------------紋理變換矩陣
UNITY_LIGHTMODEL_AMBIENT------------------當前的環境光顏色
下面是官方文檔中的一個例子,能夠產生不一樣顏色交錯的效果:
Shader "Custom/Bars" { SubShader { Pass { CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc"
struct vertOut { float4 pos:SV_POSITION; float4 scrPos; }; vertOut vert(appdata_base v) { vertOut o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); //ComputeScreenPos將返回片斷着色器的屏幕位置scrPos
o.scrPos = ComputeScreenPos(o.pos); return o; } fixed4 frag(vertOut i) : COLOR0 { float2 wcoord = (i.scrPos.xy/i.scrPos.w); fixed4 color; //改變50能夠調整間距
if (fmod(50.0*wcoord.x,2.0)<1.0) { color = fixed4(wcoord.xy,0.6,1.0);//這裏能夠改變顏色
} else { color = fixed4(0.1,0.3,0.7,1.0);//這裏能夠改變顏色
} return color; } ENDCG } } }
下面的例子來自官方手冊,棋盤格效果:
Shader "Custom/Chess" { SubShader { Pass { CGPROGRAM #pragma vertex vert
#pragma fragment frag #include "UnityCG.cginc"
//輸入頂點結構體,包含位置和顏色
struct vertexInput { float4 vertex : POSITION; float4 texcoord0 : TEXCOORD0; }; //片斷結構體,包含位置和顏色
struct fragmentInput{ float4 position : SV_POSITION; float4 texcoord0 : TEXCOORD0; }; //頂點處理
fragmentInput vert(vertexInput i){ fragmentInput o; o.position = mul (UNITY_MATRIX_MVP, i.vertex); o.texcoord0 = i.texcoord0; return o; } //片斷處理
float4 frag(fragmentInput i) : COLOR { float4 color; //fmod用來取餘數,物體表面X方向被分紅了8/2=4個區間 //X座標對2求餘,因此這裏用1來做爲比較,黑、白各佔一半
if ( fmod(i.texcoord0.x*8.0,2.0) < 1.0 ){ if ( fmod(i.texcoord0.y*8.0,2.0) < 1.0 ) { color = float4(1.0,1.0,1.0,1.0);//白色
} else { color = float4(0.0,0.0,0.0,1.0);//黑色
} } else { if ( fmod(i.texcoord0.y*8.0,2.0) > 1.0 ) { color = float4(1.0,1.0,1.0,1.0);//白色
} else { color = float4(0.0,0.0,0.0,1.0);//黑色
} } return color; } ENDCG } } FallBack "Diffuse" }
相同效果的簡化代碼:
Shader "Custom/ChessOpt" { SubShader { Pass { CGPROGRAM #pragma vertex vert_img
#pragma fragment frag #include "UnityCG.cginc" float4 frag(v2f_img i) : COLOR { bool p = fmod(i.uv.x*8.0,2.0) < 1.0; bool q = fmod(i.uv.y*8.0,2.0) > 1.0; return float4(float3((p && q) || !(p || q)),1.0); } ENDCG } } }
上一個例子中有個texcoord0變量,它的x和y的值都是從0到1的,恰好映射到一張特殊的紋理上。