天天看點

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

【Unity Shader】(三) ---------------- 光照模型原理及漫反射和高光反射的實作

本文主要參考了馮樂樂老師的《Unity Shader入門精要 》一書,再加上網上一些參考資料而寫。

筆者使用的是 Unity 2018.2.0f2 + VS2017,書中使用的是 Unity 5.2.1 ,由于版本更新,是以本文的一些shader代碼會與原文中有一些差異。建議讀者使用與 Unity 2018 相近的版本,避免一些因為版本不一緻而出現的問題。

從宏觀上說,渲染包含了兩個部分:①決定一個像素的可見性 ②決定一個像素上的光照計算。 而為了友善的計算光照計算,我們會采用光照模型

本文着重記錄的是光照模型的作用原理,是以本文中實作的Shader均不可直接運用于項目之中,而在往後的篇章,我會給出包含了完整光照模型的,可真正使用的Shader

目錄

一. 我們如何看見事物 1.1 光源 1.2 吸收和散射 1.3 着色 1.4 BRDF 光照模型 二. 标準光照模型 2.1 自發光 2.2 高光發射 2.3 漫反射 2.4 環境光 2.5 Blinn-Phong模型 2.6 逐像素光照與逐頂點光照  三. 在Shader中實作漫反射 3.1 逐頂點光照 3.2 逐像素光照 3.3 半蘭伯特模型 四. 在Shader中實作高光反射 4.1 逐頂點光照 4.2 逐像素光照 #%C2%A0 4.3 Blinn-Phong 光照模型  五. 便利的内置函數 六. 總結

我們在描述一個物體外貌時時常會說 “這物體是什麼什麼顔色的”,而在中學時,我們學的實體知識告訴我們,如果你看到這個物體是紅色的,那就說明這個物體反射了很多紅色光的波長,而吸收了其它顔色的波長。如果一個物體是黑色的,則說明這個物體吸收了大部分的波長。而這種實體現象也是我們在Shader要處理的問題。簡單的說,可以分成這3個現象。
  • 光源發射出光線
  • 光線與物體相交,部分光線被吸收,部分光線被散射
  • 錄影機吸收了部分光線,産生圖像  

既然有光,那就必定有光源。在渲染中,我們通常把光源當做一個點,用  
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
  表示光源方向。同時,為了量化光源發射出來的光,我們使用 輻照度(irradiance)來量化光。一般對于平行光,它的輻照度可以通過計算在垂直于 
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

  的機關面積上機關時間内穿過的能量來得到。

在計算光照模型時,我們需要知道一個物體表面的輻照度,但是物體表面往往不是和 

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
  垂直的,是以為了得到這種情況下輻照度,我們可以通過計算光源方向 
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
  和表面法線 
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
  之間的夾角的餘弦值來得到。而且,這裡預設方向矢量的摸都為1
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

 左圖中,光垂直照射到物體表面,是以光線之間的垂直距離保持不變;右圖中,光是斜照到物體表面,是以光線間距離為 d /

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

θ ,是以機關面積上接收到的光線數目比左圖少。

因為輻照度是和照射到物體表面時光線之間的距離 d /

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
θ 成反比,即與
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
θ 成正比。
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
θ 可以用光源方向  
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
  和表面法線  
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
點積得到。

光源與物體相交一般會有兩個結果:散射(scattering)和 吸收(absorption)。

散射隻改變光線方向,不改變光線密度和顔色。而吸收隻改變光線密度和顔色,不改變其方向。光想在散射後一般有兩種方向:

① 散射到物體内部,這種現象稱為 折射(refraction)或 透射(transmission)

② 散射到外部,這種現象稱為 反射(reflection),對于不透明物體,折射進入物體内部的光線還會繼續與内部的顆粒相交,同樣會發生折射和反射現象,而此時從物體表面重新射出的光線将具有和入射光線不同的方向分布和顔色。

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

                                          折射的光線會在物體内部傳播,有一部分會重新從表面發射出去

是以就産生了兩種散射方向。為了區分這兩種方向,我們在光照模型中使用了不同的部分來計算:

I . 高光反射(specular)部分表示物體表面是如何反射光線的

II . 漫反射(diffuse)部分表示有多少光線會被折射,吸收和散射出表面

我們引入 出射度(exitance)這個概念來描述出射度,而輻照度和出射度是滿足線性關系的,而它們的比值就是材質的漫反射和高光反射屬性。

在本文中,我們是假設漫反射部分是沒有方向性的,即光線在所有方向上是平均分布的。同時,我們也隻考慮某一個特定方向上的高光發射。

根據 ① 材質屬性(如漫反射,高光反射等屬性) ② 光源資訊(光源方向,光照強度等),使用一個等式去計算沿某個觀察方向的出射度的過程,這個過程就叫做着色(shading)。而這個等式也稱為  光照模型  (Light Model),而光照模型也這是本文所重點探讨的。光照模型不止一種,不同的光照模型對應着不同的作用。

當光線從某個方向照射到一個物體表面時,有多少光線被反射?反射方向有哪些?而 BRDF(Bidirectional Reflectance Distribution Funtion)回答了這些問題。當給定模型表面上的一個點時,BRDF 包含了對該點外觀的完整描述。簡單的說,BRDF可以從給定的入射光線的方向和輻照度計算出某個出射方向上的光照能量分布。本文涉及的光照模型均是經驗模型,并不能真實地反映物體和光線之間的互動。但即便如此,經驗模型也被應用了多年。

1973年,著名學者Bui Tuong Phong提出了标準光照模型的基本概念 https://en.wikipedia.org/wiki/Bui_Tuong_Phong 标準光照模型隻關注直接光照,簡單得說就是 直接從光源發射出來照射到物體表面後,經過一次反射後直接進入錄影機的光線。基本方法是把進入錄影機的光線分成4個部分,每個部分分别有一種分發來計算其貢獻度
  •  自發光    用 
    【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
    表示,意義為:當給定一個方向時,物體表面會往這個方向發射多少輻射量。 在沒有全局光照的情況下,自發光不會照亮周圍的物體,而僅僅是自身看起來更亮了。
  •  高光反射  用 
    【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
    表示,意義為:物體表面完全鏡面反射方向散射多少輻射量。
  •  漫反射   用
    【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
    表示,意義為:物體表面會向每個方向散射多少輻射量
  •  環境光  用
    【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
    表示,意義為:描述其它所有間接光照

标準光照模型中,自發光的計算可以直接使用該材質的自發光顔色 
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
=
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

這裡所說的高光反射是一種經驗模型,并不完全符合現實中的高光發射現象。可以讓物體看起來比較有光澤,金屬材質是其一。要計算高光發射需要4個參數:①表面法線 ②視角方向 ③光源方向 ④反射方向 。

而反射方向可以通過一下公式計算

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
下圖給出了以上4個參數的關系
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 利用Phong模型可以計算出高光發射部分
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 其中
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
是材質的 光澤度(gloss)或者稱為 反光度(shininess)。而Max函數是為了防止點乘的結果為負數,是以将之截取至0。

在漫反射中,視角的位置并不重要,因為反射是完全随機的,是以可以認為在任何反射方向上的分布都是一樣的,但是入射光線的角度是重要的。

漫反射光照符合 蘭伯特定律(Lambert's law):反射光線的強度與表面法線和光源方向間的夾角的餘弦值成正比

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
這裡同樣要防止點乘為負數的情況,這樣可以防止物體被從後面的光源照亮

環境光通常隻是一個全局變量,所有物體都使用這個環境光 
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

和上面的Phong模型相比,Blinn模型避免了反射方向
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
的計算,而是引入了一個新的矢量
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
然後使用
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
之間的夾角進行計算,而不是
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
之間的夾角。如 圖
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 Blinn模型 和 Phong模型都是經驗模型,在實際情況中,有時候Blinn模型更加合适。

上面給出了光照模型的計算公式,而計算光照模型一般有兩種方法。

① 在片元着色器中計算。此方法稱為 逐像素光照 或 Phong着色(Phong shading),以每個像素為基礎,得到它的法線(可以對頂點法線插值得到,也可以從法線紋理中采樣得到)

② 在頂點着色器中計算。 此方法稱為 逐頂點光照 或 高洛德着色(Gouraud shading), 在每個頂點上計算光照,然後在渲染圖元内部進行線性插值,輸出成像素顔色。而頂點數目通常會遠小于像素數目,是以逐頂點光照的計算量往往更小。不過,由于逐頂點計算依賴于線性插值,如果光照模型中存在非線性插值時(如計算高光反射)就會出現問題。而且,渲染圖元内部的顔色總是暗于頂點處的最高顔色值。我們稍後會看到這些情況。

看了這麼久,大家都累了,先休息一下吧

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

好了,看了那麼多理論,估計各位都差不多有些煩躁了。現在我們來聊聊代碼了。

(再啰嗦一點點 O(∩_∩)O)在上面 2.3 中我們給出了漫反射光照模型的計算方式

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
其中max函數是為了防止點乘結果為負數,而Cg中提供了這個函數
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
這是MSDN上對Unity Shader内置函數staurate的解釋。好了,理論知識準備完了,我們現在開始搞代碼了 !!!

(1)在Unity中建立一個場景,該場景預設有一個錄影機和一個平行光,把天空盒置空。

 Window -> Rendering -> LightingSetting

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

(2)建立一個Material,命名為DiffuseVertexLevel;建立一個Shader,命名同上,并把它賦給材質;建立一個Capsule,把材質賦給Capsule。

(3)儲存場景,如圖。

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

打開建立的shader,删除裡面的代碼,并按以下步驟書寫代碼

I.  首先給這個shader命名

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
II.  然後再 Properties 語塊中聲明了一個Color類型的屬性,以得到和控制材質的漫反射顔色
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
III. 再在 SubShader 語塊中定義一個Pass語義塊,這是因為頂點/片元着色器的代碼需要寫在Pass語義塊裡面,而不是SubShader裡面。并且,在第一行指明了該Pass的光照模式為向前渲染。LightMode 标簽是 Pass 标簽中的一種,用于定義該Pass在Unity的光照流水線的角色。而且隻有定義了正确的 LightMode ,才能使用一些Unity的内置光照變量。
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
IV. 然後 ,書寫CG代碼片。先定義頂點着色器和片元着色器,并且為了使用一些内置變量,包含一個内置檔案
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

Lighting.cginc檔案可以在   你的Unity安裝目錄/Editor/Data/CGIncludes下找到

V. 為了使用 Properties 語塊中定義的 _Diffuse ,需要定義一個和該類型比對的變量。

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

這樣就得到了計算公式中的參數之一 : 漫反射屬性

VI. 接着定義輸入輸出結構體

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

a2v中,vertex變量可以通路頂點資訊;normal變量可以通路頂點法線資訊

v2f中,pos變量負責儲存片元着色器中的頂點在裁剪空間中的坐标;color變量負責接收片元着色器中計算出來的光照顔色,不過,color 變量并非必須使用COLOR語義,部分資料也會使用TEXCOORD0語義

VII. 由于實作的是逐頂點光照,是以,光照計算會放在頂點着色器中

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

 需要注意的是:

① 書中對頂點資訊轉換至裁剪空間用的是以下代碼

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
如果你使用這行代碼,Unity會提示你以下資訊
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
并自動幫你更改為
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
版本更新之後,Unity提供了一個内置函數來進行轉換空間,而 UnityObjectToClipPos 的函數原型如圖
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

事實上,Unity也提供了一些其它轉換的内置函數,稍後會列來部分常用内置函數

② 這裡對光源方向的計算并不具備通用性,當場景中存在多個光源時,直接使用 _WoldSpaceLightPos() 并不能得到正确的結果。稍後會給出處理複雜的光源類型的方法。

VIII. 再加上一個簡單的片元着色器

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
IX. 最後再加上一個“小備胎”,就OK了
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
完整的代碼:
Shader "Unity/Custom/01-Diffuse Vertex-Level"
{
	Properties{
		_Diffuse ("Diffuse",Color) = (1,1,1,1)
	}

	SubShader
	{
		Pass
		{
			Tags {"LightModel" = "ForwardBase"}
	
			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Diffuse;

			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				fixed3 color : COLOR;
			};

			v2f vert(a2v v)
			{
				v2f o;
				//儲存頂點在裁剪空間中的坐标資訊
				o.pos = UnityObjectToClipPos(v.vertex);
				//得到環境光部分
				fixed3 ambient =  UNITY_LIGHTMODEL_AMBIENT.xyz;
				//把頂點法線轉換到世界空間中
				fixed3 worldnormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//光源方向歸一化
				fixed3 worldlight = normalize(_WorldSpaceLightPos0.xyz);
				//光源顔色和強度、漫反射屬性、頂點法線、光源方向,利用公式計算
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldnormal,worldlight));

				//環境光 + 漫反射光 = 最終光照結果
				o.color = ambient + diffuse;
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				return fixed4(i.color,1.0);	
			}


			ENDCG

		}

	}

	FallBack "Diffuse"

}           

 儲存一下,回到Unity檢視效果

效果如圖

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 對于一些細分程度較高的模型,逐頂點光照的效果已經不錯了,不過對于細分程度低的模型就會出現視覺問題。我們可以看到在向光面和背光面交界處的棱角十分明顯。而為了解決這些問題,我們可以使用逐像素光照。

逐像素光照與逐頂點光照的差異主要是計算光照的地方不一樣,是以隻需對上面逐頂點光照的代碼進行部分修改便可。

(1)建立Material和Shader,命名為 DiffusePixelLevel。

(2)将逐頂點的代碼複制進去,然後進行修改。

I. 修改輸出結構體v2f

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

其中,worldnormal 使用 TEXCOORD0,儲存轉換至世界空間的頂點法線資訊。

II. 頂點着色器不需要計算光照

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
III. 片元着色器進行光照計算
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

因為大部分代碼是一樣的,這裡就不貼完整代碼了。

 儲存。回到Unity檢視效果

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
對比可以看到,逐像素光照實作的效果比逐頂點更加平滑。不過即便如此,在光照無法到達的區域依舊是全黑的。如果添加了環境光就可以達到非全黑的效果,但是此時,模型背光面明暗會一樣。而針對這一情況所提出的技術就是 半蘭伯特(Half Lambert)光照模型 

前面使用的光照模型也稱為蘭伯特光照模型。而廣義上的半蘭伯特光照模型的公式如下
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
對點積的結果進行 α 倍的縮放,再加上 β 的旋轉。而絕大數情況下,α 和 β 都是0.5,是以公式也可以寫成
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

 對于背光面,在蘭伯特模型中,點積結果為負數,然後均會被映射到 0 處;而在半蘭伯特模型中,不同的點積結果會映射到不同的值,就會産生明暗變化。

不過需要注意的是,半蘭伯特模型沒有實體根據,僅僅是一個視覺加強技術。

I. 同樣建立Material和Shader,命名為Half-Lambert。

II. 把逐像素光照模型的代碼複制進去,隻進行一下修改

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
儲存。檢視效果
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

                             逐頂點光照模型                      逐像素光照模型                            半蘭伯特光照模型

可以看到,半蘭伯特模型更加平滑,明暗變化也更明顯。

回想一下,上面所說的高光反射部分的計算公式
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

這裡同樣需要4個參數:① 入射光線的顔色和強度 ②材質高光反射系數 ③視角方向 ④反射方向

而反射方向可以由表面法線和光源方向計算得到

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
而 Cg 也提供了計算這條公式的函數
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

I.  建立一個Material 和 Shader,步驟與代碼結構與漫反射相似

II. 定義 Properties 語義塊

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

其中 _Specular 屬性控制高光反射,_Gloss 屬性控制高光區域的大小

III. 為了使用 Propreties 中的屬性,需要定義相比對的變量

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
IV. 輸入輸出結構體與漫反射逐頂點光照模型的一樣
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
V. 在頂點着色器中計算光照,與漫反射類似
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

​ 

需要注意的是:reflect 函數的入射方向是由光源指向交點的,是以這裡的參數要取負

VI. 再加上片元着色器和回調Shader

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
完整代碼:
Shader "Unity/Custom/01-Specular Vectex---Level" 
{
	Properties 
	{

		_Diffuse ("Diffuse",Color) = (1,1,1,1)
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20

	}
	SubShader 
	{
		Pass
		{
			Tags { "LightMode"="ForwardBase" }


			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v{

				float4 vertex : POSITION;
				float3 normal : NORMAL;

			};

			struct v2f{

				float4 pos : SV_POSITION;
				fixed3 color : COLOR;

			};

			v2f vert(a2v v)
			{
				v2f o;
				//頂點資訊轉換至世界空間
				o.pos = UnityObjectToClipPos(v.vertex);
				//得到環境光部分
				fixed3 ambient =  UNITY_LIGHTMODEL_AMBIENT.xyz;
				//頂點法線轉換至世界空間
				fixed3 worldnormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
				//光源方向
				fixed3 worldlight = normalize(_WorldSpaceLightPos0.xyz);
				//漫反射部分
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldnormal,worldlight));
				//計算反射方向
				fixed3 reflectDir = normalize(reflect(-worldlight,worldnormal));
				//計算視角方向
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld,v.vertex).xyz);
				//得到高光發射部分
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
				
				o.color = ambient + diffuse + specular;
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{
				return fixed4(i.color,1.0);	
			}
			ENDCG

		}

	}
	
	FallBack "Specular"
}           
儲存。回到Unity檢視效果
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 可以看到,得到的效果棱角十分明顯。上文也提過這個問題,因為高光發射部分的計算是非線性的,而頂點着色器的計算是線性插值的,是以便會産生這種視覺問題。

和漫反射類似,逐像素光照需要改動的地方并不多,是以這裡隻貼出改動的代碼塊
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 儲存,檢視效果
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作

可看到明顯的平滑了許多。至此,我們就實作了一個完整的 Phong 光照模型。

如果你能從本文開頭心平氣和地看到這裡,并且中途沒有想錘我的想法,那麼恭喜你初步掌握了 Phong 模型。先休息一下,最後還有一點知識點需要你堅持下去

【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
好了,休息回來,我們學習最後一部分。

本文前面已經提出過這個概念,回想一下 Blinn-Phong模型 的計算公式
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 實作這個光照模型同樣不難,步驟同上。取 4.2 中的代碼,然後修改一小部分,這裡隻貼出修改的部分
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
效果對比:
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
 可以看到 Blinn-Phong 光照模型的高光反射區域更大更亮一些。

在前文的代碼中,我們看到了不少求某某方向的代碼實作。而我們的環境中是預設隻有一個平行光的。如果光源類型複雜,我們就需要先判斷光源類型再計算,而且計算過程參數也不少,就容易出現錯誤。所幸的是,Unity提供了許多内置函數來幫我們實作一些複雜的計算。這裡,我列舉出一些常用的函數及其作用,更多的内置函數,大家可以去UnityCG.cgingc 檔案中檢視。
      float3 WorldSpaceViewDIr (float4v)                        輸入一個模型空間的頂點位置,傳回世界空間中從該點到錄影機的觀察方向
      float3 WorldSpaceLightDir (float 4 v) 僅可用于前向渲染。輸入一個模型空間中的頂點位置,傳回世界空間中從該點到光源的光照方向
float 3 UnityObjectToWorldNormal (float3 norm)                                           把法線方向從模型空間轉換到世界空間
    float 3 UnityObjectToWorldDir (float3 norm)                                            把方向矢量從模型空間轉換到世界空間
 現在我們來改寫 Blinn-Phong 中的代碼
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
得到正常效果:
【Unity Shader】(三) ------ 光照模型原理及漫反射和高光反射的實作
其他的還有許多便利的内置函數這裡就不一 一列舉出來了,大家可以在UnityCG.cginc中看到。

本文列出的Shader均是不完整的Shader,不可以直接運用于項目之中,會缺乏光照衰減等現象,隻是為了學習光照模型的原理。且本文所列出的光照模型都是經驗模型,并不能真正代表現實中的光照情況,但其仍在實時渲染領域被引用多年。

因為計算機圖形學第一定律:如果它看起來是對的,那麼它就是對的。

最後說一下,可能有不少人會覺得本文啰嗦。不過為了學習shader,我們必須要學會相關的理論知識,我本人也不過是一名初學者,能提煉的東西實在有限,不過它的确可以幫助你了解 shader 中的光照計算。

碼字不易,累死爸爸了~ o(* ̄︶ ̄*)o

本文實作的 Shaders

繼續閱讀