Unity3D Shader入門指南(二)

Unity Shader教程

關於本系列

這是Unity3D Shader入門指南系列的第二篇,本系列面向的對象是新接觸Shader開發的Unity3D使用者,由於我自己本身也是Shader初學者,所以可能會存在錯誤或者疏漏,若是您在Shader開發上有所心得,很歡迎並懇請您指出文中紕漏,我會盡快改正。在以前的開篇中介紹了一些Shader的基本知識,包括ShaderLab的基本結構和語法,以及簡單逐句地講解了一個基本的shader。在具備這些基礎知識後,閱讀簡單的shader應該不會有太大問題,在繼續教程以前簡單閱讀一下Unity的Surface Shader Example,以檢驗您是否掌握了上一節的內容。若是您對閱讀大部分示例Shader並無太大問題,能夠正確地指出Shader的結構,聲明和使用的話,就說明您已經準備好繼續閱讀本節的內容了。html

法線貼圖(Normal Mapping)

法線貼圖是凸凹貼圖(Bump mapping)的一種常見應用,簡單說就是在不增長模型多邊形數量的前提下,經過渲染暗部和亮部的不一樣顏色深度,來爲原來的貼圖和模型增長視覺細節和真實效果。簡單原理是在普通的貼圖的基礎上,再另外提供一張對應原來貼圖的,能夠表示渲染濃淡的貼圖。經過將這張附加的表示表面凸凹的貼圖的因素於實際的原貼圖進行運算後,能夠獲得新的細節更加豐富富有立體感的渲染效果。在本節中,咱們將首先實現一個法線貼圖的Shader,而後對Unity Shader的光照模型進行一些討論,並實現一個自定義的光照模型。最後再經過更改shader模擬一個石頭上的積雪效果,並對模型頂點進行一些修改使積雪效果看起來比較真實。在本節結束的時候,咱們就會有一個比較強大的能夠知足一些真實開發工做時可用的shader了,並且更重要的是,咱們將會掌握它是如何被創造出來的。git

關於法線貼圖的效果圖,能夠對比看看下面。模型面數爲500,左側只使用了簡單的Diffuse着色,右側使用了法線貼圖。比較兩張圖片不難發現,使用了法線貼圖的石頭在暗部和亮部都有着更好的表現。總體來講,凸凹感比Diffuse的結果加強許多,石頭看起來更真實也更具備質感。github

image

本節中須要用到的上面的素材能夠在這裏下載,其中包括上面的石塊的模型,一張貼圖以及對應的法線貼圖。將下載的package導入到工程中,並新建一個material,使用簡單的Diffuse的Shader(好比上一節咱們實現的),再加上一個合適的平行光光源,就能夠獲得咱們左圖的效果。另外,本節以及之後都會涉及到一些Unity內建的Shader的內容,好比一些標準經常使用函數和常量定義等,相關內容能夠在Unity的內建Shader中找到,內建Shader能夠在Unity下載頁面的版本右側找到。bash

接下來咱們實現法線貼圖。在實現以前,咱們先簡單地稍微多瞭解一些法線貼圖的基本知識。大多數法線圖通常都和下面的圖相似,是一張以藍紫色爲主的圖。這張法線圖實際上是一張RGB貼圖,其中紅,綠,藍三個通道分別表示由高度圖轉換而來的該點的法線指向:Nx、Ny、Nz。在其中絕大部分點的法線都指向z方向,所以圖更偏向於藍色。在shader進行處理時,咱們將光照與該點的法線值進行點積後便可獲得在該光線下應有的明暗特性,再將其應用到原圖上,便可反應在必定光照環境下物體的凹凸關係了。關於法向貼圖的更多信息,能夠參考wiki上的相關條目app

一張典型的法線圖

回到正題,咱們如今考慮的主要是Shader入門,而不是圖像學的原理。再上一節咱們寫的Shader的基礎上稍微作一些修改,就能夠獲得適應並完成法線貼圖渲染的新Shader。新加入的部分進行了編號並在以後進行說明。函數

Shader "Custom/Normal Mapping" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} //1 _Bump ("Bump", 2D) = "bump" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert sampler2D _MainTex; //2 sampler2D _Bump; struct Input { float2 uv_MainTex; //3 float2 uv_Bump; }; void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); //4 o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } 
  1. 聲明並加入一個顯示名稱爲Bump的貼圖,用於放置法線圖
  2. 爲了可以在CG程序中使用這張貼圖,必須加入一個sample,但願你還記得~
  3. 獲取Bump的uv信息做爲輸入
  4. 從法線圖中提取法線信息,並將其賦予相應點的輸出的Normal屬性。UnpackNormal是定義在UnityCG.cginc文件中的方法,這個文件中包含了一系列經常使用的CG變量以及方法。UnpackNormal接受一個fixed4的輸入,並將其轉換爲所對應的法線值(fixed3)。在解包獲得這個值以後,將其賦給輸出的Normal,就能夠參與到光線運算中完成接下來的渲染工做了。

如今保存而且編譯這個Shader,建立新的material並使用這個shader,將石頭的材質貼圖和法線圖分別拖放到Base和Bump裏,再將其應用到石頭模型上,應該就能夠看到右側圖的效果了。post

光照模型

在咱們以前的看到的Shader中(其實也就上一節的基本diffuse和這裏的normal mapping),都只使用了Lambert的光照模型(#pragma surface surf Lambert),這是一個很經典的漫反射模型,光強與入射光的方向和反射點處表面法向夾角的餘弦成正比。關於Lambert和漫反射的一些詳細的計算和推論,能夠參看wiki(Lambert漫反射)或者其餘地方的介紹。一句話的簡單解釋就是一個點的反射光強是和該點的法線向量和入射光向量和強度和夾角有關係的,其結果就是這兩個向量的點積。既然已經知道了光照計算的原理,咱們先來看看如何實現一個本身的光照模型吧。spa

在剛纔的Shader上進行以下修改。設計

  • 首先將原來的#pragma行改成這樣
#pragma surface surf CustomDiffuse 
  • 而後在SubShader塊中添加以下代碼
inline float4 LightingCustomDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = max(0, dot (s.Normal, lightDir)); float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2); col.a = s.Alpha; return col; } 
  • 最後保存,回到Unity。Shader將編譯,若是一切正常,你將不會看到新的shader和以前的在材質表現上有任何不一樣。可是事實上咱們如今的shader已經與Unity內建的diffuse光照模型撇清了關係,而在使用咱們本身設定的光照模型了。

喵的,這些代碼都幹了些什麼!相信你必定會有這樣的疑惑...沒問題,沒有疑惑的話那就不叫初學了,仍是一行行講來。首先正像咱們上一篇所說,#pragma語句在這裏聲明瞭接下來的Shader的類型,計算調用的方法名,以及指定光照模型。在以前咱們一直指定Lambert爲光照模型,而如今咱們將其換爲了CustomDiffuse。3d

接下來添加的代碼是計算光照的實現。shader中對於方法的名稱有着比較嚴格的約定,想要建立一個光照模型,首先要作的是按照規則聲明一個光照計算的函數名字,即Lighting<Your Chosen Name>。對於咱們的光照模型CustomDiffuse,其計算函數的名稱天然就是LightingCustomDiffuse了。光照模型的計算是在surf方法的表面顏色以後,根據輸入的光照條件來對原來的顏色在這種光照下的表現進行計算,最後輸出新的顏色值給渲染單元完成在屏幕的繪製。

也許你已經猜到了,咱們以前用的Lambert光照模型是否是也有一個名字叫LightingLambert的光照計算函數呢?Bingo。在Unity的內建Shader中,有一個Lighting.cginc文件,裏面就包含了LightingLambert的實現。也許你也注意到了,咱們所實現的LightingCustomDiffuse的內容如今和Unity內建中的LightingLambert是徹底同樣的,這也就是使用新的shader的原來視覺上沒有區別的緣由,由於實現確實是徹底同樣的。

首先來看輸入量,SurfaceOutput s這個就是通過表面計算函數surf處理後的輸出,咱們講對其上的點根據光線進行處理,fixed3 lightDir是光線的方向,fixed atten表示光衰減的係數。在計算光照的代碼中,咱們先將輸入的s的法線值(在Normal mapping中的話這個值已是法線圖中的對應量了)和輸入光線進行點積(dot函數是CG中內置的數學函數,但願你還記得,能夠參考這裏)。點積的結果在-1至1之間,這個值越大表示法線與光線間夾角越小,這個點也就應該越亮。以後使用max來將這個係數結果限制在0到1之間,是爲了不負數狀況的存在而致使最終計算的顏色變爲負數,輸出一團黑,通常來講這是咱們不肯意看到的。接下來咱們將surf輸出的顏色與光線的顏色_LightColor0.rgb(由Unity根據場景中的光源獲得的,它在Lighting.cginc中有聲明)進行乘積,而後再與剛纔計算的光強係數和輸入的衰減係數相乘,最後獲得在這個光線下的顏色輸出(關於difLight * atten * 2中爲何有個乘2,這是一個歷史遺留問題,主要是爲了進行一些光強補償,能夠參見這裏的討論)。

在瞭解了基本實現方式以後,咱們能夠看看作一些修改玩玩兒。最簡單的好比將這個Lambert模型改亮一些,好比換成Half Lambert模型。Half Lambert是由Valve創造的可使物體在低光線條件下增亮的技術,最先被用於半條命(Half Life)中以免在低光下物體的走形。簡單說就是把光強係數先取一半,而後在加0.5,代碼以下:

inline float4 LightingCustomDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = dot (s.Normal, lightDir); float hLambert = difLight * 0.5 + 0.5; float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2); col.a = s.Alpha; return col; } 

這樣一來,原來光強0的點,如今對應的值變爲了0.5,而原來是1的地方如今將保持爲1。也就是說模型貼圖的暗部被加強變亮了,而亮部基本保持和原來同樣,防止過曝。使用Half Lambert先後的效果圖以下,注意最右側石頭下方的陰影處細節更加明顯了,而這一切都只是視覺效果的改變,不涉及任何貼圖和模型的變化。

Half Lambert下發現貼圖的表現

表面貼圖的追加效果

OK,對於光線和自定義光照模型的討論暫時到此爲止,由於若是展開的話這將會一個龐大的圖形學和經典光學的話題了。咱們回到Shader,而且一塊兒實現一些激動人心的效果吧。好比,在你的遊戲場景中有一幕是雪地場景,而你但願作一些石頭上白雪皚皚的覆蓋效果,應該怎麼辦呢?難道讓你可愛的3D設計師再去出一套覆雪的貼圖而後使用新的貼圖?固然不,不是不能,而是不應。由於新的貼圖不只會增大項目的資源包體積,更會增大以後修改和維護的難度,想一想要是有好多石頭須要實現一樣的覆雪效果,或者是要隨着遊戲時間堆積的雪逐漸變多的話,你應該怎麼辦?難道讓設計師再把全部的石頭貼圖都蓋上雪,而後再按照雪的厚度出5套不一樣的貼圖麼?相信我,他們會瘋的。

因而,咱們考慮用Shader來完成這件工做吧!先考慮下咱們須要什麼,積雪效果的話,咱們須要積雪等級(用來表示積雪量),雪的顏色,以及積雪的方向。基本思路和實現自定義光照模型相似,經過計算原圖的點在世界座標中的法線方向與積雪方向的點積,若是大於設定的積雪等級的閾值的話則表示這個方向與積雪方向是一致的,其上是能夠積雪的,顯示雪的顏色,不然使用原貼圖的顏色。廢話再也不多說,上代碼,在上面的Shader的基礎上,更改Properties裏的內容爲

Properties {  
    _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Snow ("Snow Level", Range(0,1) ) = 0 _SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0) _SnowDirection ("Snow Direction", Vector) = (0,1,0) } 

沒有太多值得說的,惟一要提一下的是_SnowDirection設定的默認值爲(0,1,0),這表示咱們但願雪是垂直落下的。對應地,在CG程序中對這些變量進行聲明:

sampler2D _MainTex;  
sampler2D _Bump;  
float _Snow; float4 _SnowColor; float4 _SnowDirection; 

接下來改變Input的內容:

struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA }; 

相對於上面的Shader輸入來講,加入了一個float3 worldNormal; INTERNAL_DATA,若是SurfaceOutput中設定了Normal值的話,經過worldNormal能夠獲取當前點在世界中的法線值。詳細的解說能夠參見Unity的Shader文檔。接下來能夠改變surf函數,實裝積雪效果了。

void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1,-1,_Snow)) { o.Albedo = _SnowColor.rgb; } else { o.Albedo = c.rgb; } o.Alpha = c.a; } 

和上面相比,加入了一個if…else…的判斷。首先看這個條件的不等式的左側,咱們對雪的方向和和輸入點的世界法線方向進行點積。WorldNormalVector經過輸入的點及這個點的法線值,來計算它在世界座標中的方向;右側的lerp函數相信只要對插值有概念的同窗都不難理解:當Snow取最小值0時,這個函數將返回1,而Snow取最大值時,返回-1。這樣咱們就能夠經過設定Snow的值來控制積雪的閾值,要是積雪等級Snow是0時,不等式左側不可能大於右側,所以徹底沒有積雪;相反要是_Snow取最大值1時,因爲左側一定大於-1,因此全模型積雪。而隨着取中間值的變化,積雪的狀況便會有所不一樣。

應用這個Shader,而且適當地調節一下積雪等級和顏色,能夠獲得以下右邊的效果。

添加了積雪效果的Shader

更改頂點模型

到如今位置,咱們還僅指是在原貼圖上進行操做,無論是用法線圖使模型看起來凸凹有致,仍是加上積雪,全部的計算和顏色的輸出都只是「障眼法」,並無對模型有任何實質的改動。可是對於積雪效果來講,實際上積雪是附加到石頭上面,而不該當簡單替換掉原來的顏色。可是具體實施起來,最簡單的辦法仍是直接替換顏色,可是咱們能夠稍微變動一下模型,使原來的模型在積雪的方向稍微變大一些,這樣來達到一種雪是附加到石頭上的效果。

咱們繼續修改以前的Shader,首先咱們須要告訴surface shadow咱們要改變模型的頂點。首先將#param行改成

#pragma surface surf CustomDiffuse vertex:vert

這告訴Shader咱們想要改變模型頂點,而且咱們會寫一個叫作vert的函數來改變頂點。接下來咱們再添加一個參數,在Properties中聲明一個_SnowDepth變量,表示積雪的厚度,固然咱們也須要在CG段中進行聲明:

//In Properties{…} _SnowDepth ("Snow Depth", Range(0,0.3)) = 0.1 //In CG declare float _SnowDepth; 

接下來實現vert方法,和以前積雪的運算其實比較相似,判斷點積大小來決定是否須要擴大模型以及肯定模型擴大的方向。在CG段中加入如下vert方法

void vert (inout appdata_full v) { float4 sn = mul(transpose(_Object2World) , _SnowDirection); if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow * 2) / 3)) { v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow; } } 

和surf的原理差很少,系統會輸入一個當前的頂點的值,咱們根據須要計算並填上新的值做爲返回便可。上面第一行中使用transpose方法輸出原矩陣的轉置矩陣,在這裏_Object2World是Unity ShaderLab的內建值,它表示將當前模型轉換到世界座標中的矩陣,將其與積雪方向作矩陣乘積獲得積雪方向在物體的世界空間中的投影(把積雪方向轉換到世界座標中)。以後咱們計算了這個世界座標中實際的積雪方向和當前點的法線值的點積,並將結果與使用積雪等級的2/3進行比較lerp後的閾值比較。這樣,當前點若是和積雪方向一致,而且積雪較爲完整的話,將改變該點的模型頂點高度。

加入模型更改先後的效果對好比下圖,加入模型調整的右圖表現要更爲豐滿真實。

image

這節就到這裏吧。本節中實現的Shader能夠在這裏找到完整版本進行參考,但願你們週末愉快~

原文:http://onevcat.com/2013/08/shader-tutorial-2/

相關文章
相關標籤/搜索