如何利用Shader来渲染游戏中的3D角色
楊航最近在學Unity3D
本文主要介紹一下如何利用Shader來渲染游戲中的3D角色,以及如何利用Unity提供的Surface Shader來書寫自定義Shader。
一、從Shader開始
1、通過Assets->Create->Shader來創建一個默認的Shader,并取名“MyShader”。
Unity3D教程:3D角色的渲染
2、將MyShader打開即可看見Unity默認的Shader代碼
| ? | ? | ? |
| 01 | Shader "Custom/MyShader" { |
| 02 | Properties { |
| 03 | _MainTex ("Base (RGB)", 2D) = "white" {} |
| 04 | } |
| 05 | SubShader { |
| 06 | Tags { "RenderType"="Opaque" } |
| 07 | LOD 200 |
| 08 | CGPROGRAM |
| 09 | #pragma surface surf Lambert |
| 10 | sampler2D _MainTex; |
| 11 | struct Input { |
| 12 | float2 uv_MainTex; |
| 13 | }; |
| 14 | void surf (Input IN, inout SurfaceOutput o) { |
| 15 | half4 c = tex2D (_MainTex, IN.uv_MainTex); |
| 16 | o.Albedo = c.rgb; |
| 17 | o.Alpha = c.a; |
| 18 | } |
| 19 | ENDCG |
| 20 | } |
| 21 | FallBack "Diffuse" |
| 22 | } |
3、將該Shader賦給一個角色,就可以看到該Shader所能表達出的Diffuse渲染效果。
Unity3D教程:3D角色的渲染
4、接來我們將以此默認Shader作為藍本,編寫出自定義的Shader。另外,該Shader所用到的參數,我們將在下一章節進行說明。
二、實現多種自定義渲染效果
1、 BumpMap效果
如果想實現Bump Map效果,可對上述的Shader做如下修改:
1.1 在屬性Properties中加入:
| ? | ? | ? |
| 1 | Properties { |
| 2 | _MainTex ("Base (RGB)", 2D) = "white" {} |
| 3 | _BumpMap("Bumpmap", 2D) = "bump" {} |
| 4 | } |
1.2 在SubShader的變量中也進行相應修改:
| ? | ? | ? |
| 1 | sampler2D _MainTex; |
| 2 | sampler2D _BumpMap; |
| 3 | struct Input { |
| 4 | float2 uv_MainTex; |
| 5 | float2 uv_BumpMap; |
| 6 | }; |
1.3 最后修改surf函數,加入對Normal分量的計算:
| ? | ? | ? |
| 1 | void surf (Input IN, inout SurfaceOutput o) { |
| 2 | "white-space: pre;"> half4 c = tex2D (_MainTex, IN.uv_MainTex); |
| 3 | o.Albedo = c.rgb; |
| 4 | o.Alpha = c.a; |
| 5 | o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
| 6 | } |
這樣,角色的材質部分即可變為如下形式(暫定BumpMap的Shader名為“MyShader1”):
Unity3D教程:3D角色的渲染
然后,根據Base圖來創建其Normal Map圖,并拖入到BumpMap中即可。BumpMap的效果顯示如下:
Unity3D教程:3D角色的渲染
說明:
(1)首先是title的解釋
| ? | ? | ? |
| 1 | Shader "Custom/MyShader1" |
這種表示表明了該Shader在編輯器中的顯示位置,例如我們可在如下地方找到該Shader。
Unity3D教程:3D角色的渲染
(2)其次是Properties
| ? | ? | ? |
| 1 | Properties { |
| 2 | _MainTex ("Base (RGB)", 2D) = "white" {} |
| 3 | _BumpMap("Bumpmap", 2D) = "bump" {} |
| 4 | } |
Properties可通過如下語義進行聲明:
name ("displayname", property type) = default value
“name” 是與Shader腳本中對應的名字
“display name”是在材質視圖中所顯示的名字
“propertytype”是指該property的類型,一般可有如下幾種類型:Range,Color,2D,Rect,Cube,Float和Vector
“defaultvalue”是指該property的默認值
這里需要注意的是,如果你在Properties中加入了新的屬性,那么你需要在CGPROGRAM中的SubShader中加入同樣名字的參數。
(3)接下來是“LOD”語義詞的解釋。
這里的“LOD”主要是指Shader的LOD程度,即對于超出該范圍的物體將不再通過該Shader進行渲染,具體的Shader LOD說明可以參見:Unity3D翻譯——Shader Level of Detail
(4)我們在SubShader中還加入了
| ? | ? | ? |
| 1 | sampler2D _BumpMap; |
| 2 | float2 uv_BumpMap; |
其中,_BumpMap是為了關聯Properties中的_BumpMap屬性。
而uv_BumpMap,是為了獲取BumpMap圖中的uv坐標。
(5)最后,我們在surf函數中獲取每個頂點的紋理信息以及法線信息,這些信息將被應用于接下來的Vertex Fragment和Pixel Fragment。
| ? | ? | ? |
| 1 | void surf (Input IN, inout SurfaceOutput o) { |
| 2 | half4 c = tex2D (_MainTex, IN.uv_MainTex); |
| 3 | o.Albedo = c.rgb; |
| 4 | o.Alpha = c.a; |
| 5 | o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
| 6 | } |
其中,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中加入:
| ? | ? | ? |
| 1 | _AmbientColor ("Ambient Color", Color) = (0.1, 0.1, 0.1, 1.0) |
| 2 | _SpecularColor ("Specular Color", Color) = (0.12, 0.31, 0.47, 1.0) |
| 3 | _Glossiness ("Gloss", Range(1.0,512.0)) = 80.0 |
2.2??在SubShader的變量中也加入相應修改:
| ? | ? | ? |
| 1 | fixed4 _AmbientColor; |
| 2 | fixed4 _SpecularColor; |
| 3 | half _Glossiness; |
2.3??最后修改surf函數,進行如下修改:
| ? | ? | ? |
| 1 | fixed4 c = tex2D (_MainTex, IN.uv_MainTex); |
這里將原有的half4替換為fixed4,這樣做是為了提高渲染的性能,因為fixed的精度較之half要低,更高的精度意味著更大的計算量,而這里fixed的精度已經足夠,所以使用fixed替代half4,從而來降低計算消耗,增加渲染性能。
2.4??將“#pragma surface surf Lamber”改成“#pragma surfacesurf CustomBlinnPhong”,同時加入與其對應的LightingCustomBlinnPhong函數來計算頂點光照。
| ? | ? | ? |
| 01 | inline fixed4 LightingCustomBlinnPhong (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten) |
| 02 | { |
| 03 | fixed3 ambient = s.Albedo * _AmbientColor.rgb; |
| 04 | ? |
| 05 | fixed NdotL = saturate(dot (s.Normal, lightDir)); |
| 06 | fixed3 diffuse = s.Albedo * _LightColor0.rgb * NdotL; |
| 07 | ? |
| 08 | fixed3 h = normalize (lightDir + viewDir); |
| 09 | float nh = saturate(dot (s.Normal, h)); |
| 10 | float specPower = pow (nh, _Glossiness); |
| 11 | fixed3 specular = _LightColor0.rgb * specPower * _SpecularColor.rgb; |
| 12 | ? |
| 13 | fixed4 c; |
| 14 | c.rgb = (ambient + diffuse + specular) * (atten * 2); |
| 15 | c.a = s.Alpha + (_LightColor0.a * _SpecularColor.a * specPower * atten); |
| 16 | return c; |
| 17 | } |
該函數的名稱為什么不是“CustomBlinnPhong”呢?這是因為該函數雖然是由“#pragma surface surf CustomBlinnPhong”來調用,但是為了讓該函數可以正常工作,我們需要在其名稱前加入“Lighting”關鍵字,這樣Unity才能識別出這是一個自定義的光照函數。
通過以上設置,角色的材質部分即可變為如下形式(暫定該Shader名為“MyShader2”):
Unity3D教程:3D角色的渲染
其顯示效果如下:
Unity3D教程:3D角色的渲染
3、? 邊緣光照(Rim Light)和卡通渲染(Toon Shading)
可以通過對上述Shader做以下改進,來達到這種效果:
3.1??在屬性Properties中加入:
| ? | ? | ? |
| 1 | _RimColor ("Rim Color", Color) = (0.12, 0.31, 0.47, 1.0) |
| 2 | _RimPower ("Rim Power", Range(0.5, 8.0)) = 3.0 |
| 3 | _Ramp ("Shading Ramp", 2D) = "gray" {} |
3.2??在SubShader的變量中也加入相應修改:
| ? | ? | ? |
| 01 | sampler2D _MainTex; |
| 02 | sampler2D _BumpMap; |
| 03 | sampler2D _Ramp; |
| 04 | ? |
| 05 | fixed4 _AmbientColor; |
| 06 | fixed4 _SpecularColor; |
| 07 | half _Glossiness; |
| 08 | ? |
| 09 | fixed4 _RimColor; |
| 10 | half _RimPower; |
| 11 | ? |
| 12 | struct Input { |
| 13 | float2 uv_MainTex; |
| 14 | float2 uv_BumpMap; |
| 15 | half3 viewDir; |
| 16 | }; |
3.3??修改surf函數,進行如下修改:
| ? | ? | ? |
| 1 | void surf (Input IN, inout SurfaceOutput o) { |
| 2 | fixed4 c = tex2D (_MainTex, IN.uv_MainTex); |
| 3 | o.Albedo = c.rgb; |
| 4 | o.Alpha = c.a; |
| 5 | o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap)); |
| 6 | fixed rim = 1.0 - saturate (dot (normalize(IN.viewDir), o.Normal)); |
| 7 | o.Emission = (_RimColor.rgb * pow (rim, _RimPower)); |
| 8 | } |
這里主要是用來計算邊緣光照的,首先通過視線與法線的夾角來找到模型的邊緣,然后再根據距離的遠近來控制發射光的強度。
3.4??將“#pragma surface surf CustomBlinnPhong”改成“#pragma surfacesurf CustomBlinnPhong exclude_path:prepass”,同時在LightingCustomBlinnPhong函數來修改漫反射光的計算,來達到卡通渲染的效果。
| ? | ? | ? |
| 1 | fixed NdotL = saturate(dot (s.Normal, lightDir)); |
| 2 | fixed diff = NdotL * 0.5 + 0.5; |
| 3 | fixed3 ramp = tex2D (_Ramp, float2(diff, diff)).rgb; |
| 4 | fixed diffuse = s.Albedo * LightColor0.rgb * ramp; |
通過以上設置,角色的材質部分即可變為如下形式(暫定該Shader名為“MyShader3”):
Unity3D教程:3D角色的渲染
其顯示效果如下:
Unity3D教程:3D角色的渲染
可以看出邊緣光照的效果,同時還可以看出明顯的明暗變化的卡通渲染效果。
三、???????小結
綜上所述,本文已經給出了人物的幾種基本渲染方法及其Shader實現,在這里我并沒有去分析每種渲染效果的原理,而僅是從實際出發,直接給出對應的簡單實現方法。如果想要對光照模型進行深入理解,可以Google搜索其原理進行了解。最后,給出各種渲染方法的對比圖,顯示如下:
Unity3D教程:3D角色的渲染
本系列文章由?Unity公司開發支持工程師Amazonzx?編寫,
總結
以上是生活随笔為你收集整理的如何利用Shader来渲染游戏中的3D角色的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3D场景中选取场景中的物体。
- 下一篇: 一个透明的shader