本系列文章由 Amazonzx 編寫,歡迎轉載,轉載請註明出處。
http://blog.csdn.net/amazonzx/article/details/7935341
本文主要介紹一下如何利用Shader來渲染遊戲中的3D角色,以及如何利用Unity提供的Surface Shader來書寫自定義Shader。
一、 從Shader開始
1、通過Assets->Create->Shader來創建一個默認的Shader,並取名“MyShader”。
2、將MyShader打開即可看見Unity默認的Shader代碼
Shader "Custom/MyShader" {
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"
}
3、將該Shader賦給一個角色,就可以看到該Shader所能表達出的Diffuse渲染效果。
4、接下來我們將以此默認Shader作爲藍本,編寫出自定義的Shader。另外,該Shader所用到的參數,我們將在下一章節進行說明。
二、 實現多種自定義渲染效果
1、 BumpMap效果
如果想實現Bump Map效果,可對上述的Shader做如下修改:
1.1 在屬性Properties中加入:
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap("Bumpmap", 2D) = "bump" {}
}
1.2 在SubShader的變量中也進行相應修改:
sampler2D _MainTex;
sampler2D _BumpMap;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
1.3 最後修改surf函數,加入對Normal分量的計算:
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}
這樣,角色的材質部分即可變爲如下形式(暫定BumpMap的Shader名爲“MyShader1”):
然後,根據Base圖來創建其Normal Map圖,並拖入到BumpMap中即可。BumpMap的效果顯示如下:
說明:
(1)首先是title的解釋
Shader "Custom/MyShader1"
這種表示表明了該Shader在編輯器中的顯示位置,例如我們可在如下地方找到該Shader。
(2)其次是Properties
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_BumpMap("Bumpmap", 2D) = "bump" {}
}
Properties可通過如下語義進行聲明:
name ("displayname", property type) = default value
l “name” 是與Shader腳本中對應的名字
l “display name”是在材質視圖中所顯示的名字
l “propertytype”是指該property的類型,一般可有如下幾種類型:Range,Color,2D,Rect,Cube,Float和Vector
l “defaultvalue”是指該property的默認值
這裏需要注意的是,如果你在Properties中加入了新的屬性,那麼你需要在CGPROGRAM中的SubShader中加入同樣名字的參數。
(3)接下來是“LOD”語義詞的解釋。
這裏的“LOD”主要是指Shader的LOD程度,即對於超出該範圍的物體將不再通過該Shader進行渲染,具體的Shader LOD說明可以參見:http://blog.csdn.net/amazonzx/article/details/7614399
(4)我們在SubShader中還加入了
sampler2D _BumpMap;
float2 uv_BumpMap;
其中,_BumpMap是爲了關聯Properties中的_BumpMap屬性。
而uv_BumpMap,是爲了獲取BumpMap圖中的uv座標。
(5)最後,我們在surf函數中獲取每個頂點的紋理信息以及法線信息,這些信息將被應用於接下來的Vertex Fragment和Pixel Fragment。
void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
}
其中,tex2D函數可以讀取紋理_MainTex中的IN.uv_MainTex座標位置的像素顏色值。
Albedo和Alpha分別獲取該像素的RGB值和Alpha值,其中“Albedo”是一個漫反射參數,它表示一個表面的漫反射能力,即一個表面上出射光強與入射光強的比值。具體介紹可見:http://en.wikipedia.org/wiki/Albedo。
2、 Blinn-Phong效果
如果想實現Blinn-Phong效果,可對上述的Shader做如下修改:
2.1 在屬性Properties中加入:
_AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0)
_SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0)
_Glossiness ("Gloss", Range(1.0,512.0)) = 80.0
2.2 在SubShader的變量中也加入相應修改:
fixed4 _AmbientColor;
fixed4 _SpecularColor;
half _Glossiness;
2.3 最後修改surf函數,進行如下修改:
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
這裏將原有的half4替換爲fixed4,這樣做是爲了提高渲染的性能,因爲fixed的精度較之half要低,更高的精度意味着更大的計算量,而這裏fixed的精度已經足夠,所以使用fixed替代half4,從而來降低計算消耗,增加渲染性能。
2.4 將“#pragma surface surf Lamber”改成“#pragma surfacesurf CustomBlinnPhong”,同時加入與其對應的LightingCustomBlinnPhong函數來計算頂點光照。
inline fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
{
fixed3 ambient = s.Albedo * _AmbientColor.rgb;
fixed NdotL = saturate(dot (s.Normal, lightDir));
fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL;
fixed3 h = normalize (lightDir + viewDir);
float nh = saturate(dot (s.Normal, h));
float specPower = pow (nh, _Glossiness);
fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb;
fixed4 c;
c.rgb = (ambient + diffuse + specular) * (atten * 2);
c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower * atten);
return c;
}
該函數的名稱爲什麼不是“CustomBlinnPhong”呢?這是因爲該函數雖然是由“#pragma surface surf CustomBlinnPhong”來調用,但是爲了讓該函數可以正常工作,我們需要在其名稱前加入“Lighting”關鍵字,這樣Unity才能識別出這是一個自定義的光照函數。
通過以上設置,角色的材質部分即可變爲如下形式(暫定該Shader名爲“MyShader2”):
其顯示效果如下:
3、 邊緣光照(Rim Light)和卡通渲染(Toon Shading)
可以通過對上述Shader做以下改進,來達到這種效果:
3.1 在屬性Properties中加入:
_RimColor ("Rim Color", Color) = (0.12, 0.31, 0.47, 1.0)
_RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0
_Ramp ("Shading Ramp", 2D) = "gray" {}
3.2 在SubShader的變量中也加入相應修改:
sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _Ramp;
fixed4 _AmbientColor;
fixed4 _SpecularColor;
half _Glossiness;
fixed4 _RimColor;
half _RimPower;
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
half3 viewDir;
};
3.3 修改surf函數,進行如下修改:
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
fixed rim = 1.0 - saturate (dot (normalize(IN.viewDir), o.Normal));
o.Emission = (_RimColor.rgb * pow (rim, _RimPower));
}
這裏主要是用來計算邊緣光照的,首先通過視線與法線的夾角來找到模型的邊緣,然後再根據距離的遠近來控制發射光的強度。
3.4 將“#pragma surface surf CustomBlinnPhong”改成“#pragma surfacesurf CustomBlinnPhong exclude_path:prepass”,同時在LightingCustomBlinnPhong函數來修改漫反射光的計算,來達到卡通渲染的效果。
fixed NdotL = saturate(dot (s.Normal, lightDir));
fixed diff = NdotL * 0.5 + 0.5;
fixed3 ramp = tex2D (_Ramp, float2(diff, diff)).rgb;
fixed diffuse = s.Albedo * LightColor0.rgb * ramp;
通過以上設置,角色的材質部分即可變爲如下形式(暫定該Shader名爲“MyShader3”):其顯示效果如下:
可以看出邊緣光照的效果,同時還可以看出明顯的明暗變化的卡通渲染效果。
三、 小結
綜上所述,本文已經給出了人物的幾種基本渲染方法及其Shader實現,在這裏我並沒有去分析每種渲染效果的原理,而僅是從實際出發,直接給出對應的簡單實現方法。如果想要對光照模型進行深入理解,可以Google搜索其原理進行了解。最後,給出各種渲染方法的對比圖,顯示如下: