Blinn-Phong反射模型
前言
在這一章中,實現光線與對象表面之間的相互作用。目的是在渲染流水線中增加著色功能,所以這里只討論最基礎的局部光照模型。與全局光照不同,在局部光照模型中,著色點的顏色值只取決于著色點表面的材質屬性、表面的局部幾何性質以及光源的位置與屬性,而與場景中其他的表面無關。
渲染流程與場景定義
因為不考慮全局光照,只考慮從光源發出的光線,具體來說只考慮光源和表面之間的一次單獨的相互作用。所以這個問題可分解為兩個獨立的部分:
定義場景中的光源(這里只介紹點光源)
定義一個描述材質和光線之間相互作用的反射模型(phong與Blinn-Phong反射模型)。
首先從一個點光源發出的光線中,由于觀察者只看到從光源出發后最終到達他眼睛的那些光線。也就是說可以分兩種情況:
要么這條光線從光源出發后直接進入觀察者的眼睛,這時候看到的就是光源的顏色。
要么這條光線經過一條復雜的路線并且與場景中的對象發生多次相互作用(這里只考慮一次),之后進入觀察者的眼睛,這時候看到的是光源與表面材質之間的相互作用。
在webgl中,經常使用投影平面來代替觀察者。從概念上來講,投影平面上位于裁剪窗口內的部分被映射到顯示器上,因此可以把投影平面用直線分割成許多小的矩形,每一個小矩形都對屏幕上的一個像素。也就是說光源和對象表面的顏色決定了一個或多個像素的顏色。
反射光類型
學過初中物理就知道,基本上分為3種不同的反射光類型。
環境光:它的特點是使場景獲得均勻的照明,是光線與場景對象的多次相互作用的結果。這里簡單的設置為一個常數來模擬。
漫反射光:它的特點是把入射光線向各個方向散射,而且向各個方向散射的光線的強度都相等,因此不同位置的觀察者看到的反射光線都是一樣的。
鏡面反射光:它的特點是看起來有光澤,因為被反射出去的大多數光線的方向都是和反射角的方向很接近。反射光線的方向服從入射角等于反射角這一規律。
為了簡單起見,下面說的光照是建立一個點上,這個點就是著色點。而且這個點應該是在物體的表面,所以這點的光照就是光源對這點的著色效果。
如圖所示,首先來定義一些單位向量:
著色點的法線n
光照方向l
攝像機觀測方向v
物體表面的一些參數
光的傳播與能量守恒
光在傳播過程中是會有衰減的。
比如說一開始光集中在一個單位球上,也就是最里面的球,此時球上每個點的接收到的能量就是光的能量除以球的面積。
當光傳播到半徑為r的球上時,也就是最外面的球時。根據能量守恒定律,最外面的球上每個點所接收到的能量要比小,根據公式可得物體表面接收到的能量是與光傳播的距離平方成反比。
也就是說要計算著色點所接收到光的能量,就要計算光到著色點的距離。
Phong反射模型
該反射模型是由Phong首先提出的。實踐上不但計算效率高而且模擬現實的效果非常好,以至于對各種各樣的光照條件和材質屬性都獲得很好渲染效果。
Phong反射模型考慮了上述說明的三種相互作用:環境光、漫反射和鏡面反射。對每個顏色分量來說,都有獨立的環境光分量、漫反射光分量和鏡面反射光分量。最后將這三個分量加起來組成最后的顏色。
使用4個向量來計算著色點p的顏色值。其中未知的是反射向量r,而r可以由n和l確定的
環境光反射
在表面上所有點的環境光強度LaL_{a}La?都是相同的。環境光的一部分被表面吸收,還有一部分被表面反射,被反射的部分的強度由環境光反射系統kak_{a}ka?,所以此時RaR_{a}Ra?=kak_{a}ka?。其中0 <= kak_{a}ka? <= 1。
所以IaI_{a}Ia? = kak_{a}ka? * LaL_{a}La?
代碼實現
在代碼中,添加環境光非常的簡單,只需設置一個很小的常量將其乘上物體顏色即可
漫反射
當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射。
可以看到平面法線與光照方向有一定的夾角,而且根據這個夾角的不同著色點得到的明暗是不一樣的。
假如每根光線的能量是均等的。分析左邊的圖,可以看到當物體表面與光照方向是垂直的話,會接收到所有的6根光線。而中間圖則是旋轉了這個物體到一定的角度,只能接收到3根光線,按理說這個物體表面應該是比左邊要暗的。
所以著色點的明亮程度與光照方向l和物體表面法線n的夾角是有關系的。而在現實中也能觀察到,比如說地球的四季溫度的不同,就是光照方向與地表法線的角度有關。
這個關系就是著名的Lamber’s consine law定律:光照方向與表面法線方向的余弦是成正比的:
cosθ=l?ncosθ = l * ncosθ=l?n所以漫反射又稱為Lambertian Shading:
首先定義光的強度I,當光到達物體表面時的能量就是I/r2I / r^2I/r2。光被吸收多少就是根據夾角余弦來計算,這個余弦根據光照向量與法線向量的點乘計算得出。
Ld=kd(I/r2)max(0,n?l)L_d = k_d(I / r^2)max(0, n * l)Ld?=kd?(I/r2)max(0,n?l)至于max(0, n * l)的意思是,排除余弦是負數的情況。因為在現實生活中(表面不透明的情況下),光是不可能從負角度來照亮物體的表面。出現這種情況,就賦值0.
至于k,它是一個吸收系數。它表示這物體表面吸收了多少光,反射多少光。當kd=1k_d=1kd?=1時,也就是說物體表面完全不吸收光,全部反射出去。
代碼實現
在代碼中,法向量是已知的
由頂點著色器傳給片段著色器中
// 頂點著色器 attribute vec3 aNormal; varying vec3 Normal; void main () {Normal = aNormal;... }// 片段著色器 varying vec3 Normal; void main () {// 進行歸一化vec3 norm = normalize(Normal); } 復制代碼由像素位置與光源方向計算出入射向量,像素的位置就是頂點也是已知的,光源位置是統一的用uniform變量存儲
// 頂點著色器 attribute vec3 aPos; varying vec3 FragPos; void main () {FragPos = aPos;... }// 片段著色器 varying vec3 FragPos; uniform vec3 lightPos; void main () {vec3 lightDir = normalize(lightPos - FragPos); } 復制代碼最后根據公式算出漫反射分量
// 片段著色器 void main () {float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = diff * color; } 復制代碼鏡面反射
當我們觀察有光澤的對象時就會看到高光。這些高光通常表現出與環境光反射和漫反射不同的顏色。而且與漫反射表面是粗糙的相反,鏡面反射表面是光滑的。表面越光滑,就越接近于鏡面,反射出去的光線就越集中在一個角度的附近。
Phong提出了一個近似的模型,在考慮鏡面反射時,把表面看成是光滑的。觀察者所看到的光線強度取決于反射光線的方向r和觀察者的方向v這兩者之間的夾角α。
Ls=ksLscospαL_s = k_sL_scos^pαLs?=ks?Ls?cospα其中系數ksk_sks?(0 <= ksk_sks? <= 1)表示在入射的鏡面反射光中的有多大一部分被反射。指數p是高光系數。從下圖可以看出,當p增加時,反射的光線越來越集中在理想反射器的反射角附近。
當不加p時(即p=1),我們從反射角度是60度的時候看著色點,按理說是離的很偏的了,但反映出來還是能看見高光。這就不合理了,因為在現實中只有非常接近的時候才能看見高光,稍微離遠一點就看不到。所以才加上一個指數讓其正常化。
Phone反射模型優點在于,如果已經把r和v歸一化為單位向量,那么可以像計算漫反射一樣利用點積運算計算鏡面反射分量:
Ls=ksLsmax((r?v)p,0)L_s = k_sL_smax((r * v)^p, 0)Ls?=ks?Ls?max((r?v)p,0)接下來只需計算出反射向量r即可!
反射角計算
法向量是給出的,利用法向量n和入射向量l就可以算出反射角。理想的鏡面反射有一個很好的特征:入射角等于反射角。如圖所示:
入射角是法向量和入射向量的夾角,反射角是法向量和反射光線之間的夾角。在平面內,只有一個反射方向能滿足入射角等于反射角這個條件。但在三維空間中就不行,因為有無數個方向都滿足入射角等于反射角。所以要加上一個條件:在表面上的一點p,入射光線和反射光線必須位于同一個平面內。這兩個條件可以由n和l確定r。
假設l和n已經是單位向量:
|l| = |n| = 1。同時也假定r也是單位向量:
|r| = 1如果θIθ_IθI?=θrθ_rθr?那么
cosθicosθ_icosθi? = cosθrcosθ_rcosθr?利用點積運算可得:
cosθicosθ_icosθi? = l * n = cosθrcosθ_rcosθr? = n * r共面的條件意味著可以把r寫成l和n的線性組合:
r = αl + βn(1)等號兩邊都和n做點積可得等式(2):
n * r = αl * n + β = l * n(2)因為r是單位向量,所以代入等式(1)可得等式(3):
1 = r * r = α2α^2α2 + 2αβl * n + β2β^2β2(3)結合等式(2)和等式(3)可得
r = 2(l * n))n - l代碼實現
上述計算反射角的公式,在glsl中有一個內置函數reflect實現了。所以在代碼中實現非常簡單,視線向量也是已知的。
Phong反射模型結果
將上述3種分量加在一起就是Phong反射模型
效果圖如下
Phong反射模型不僅對真實光照有很好的近似,而且性能也很高。但它的鏡面反射會在一些情況下出現問題,特別是物體反光度很低時,會導致大片高光區域。
出現這個問題的原因是觀察向量和反射向量間的夾角不能大于90度。如果點積結果為負數,鏡面反射會變為0。你可能會覺得,當光線與視線的夾角大于90度時,應該不會接收到任何光才對。這種想法僅僅適合于漫反射。
但在鏡面反射中,測量的角度并不是光源與法線的夾角,而是視線與反射光線向量的夾角,如下圖。
右圖中,視線與反射方向之間的夾角明顯大于90度,這種情況下鏡面光分量為0。這在大多數情況下是沒問題的,因為觀察方向離反射方向都非常遠。然而,當物體的反光度非常小時,它產生的鏡面高光半徑足以讓這些相反反向的光線對亮度產生足夠大的影響,在這種情況下就不能忽略它們對鏡面光分量的貢獻了。
而且反射角也比較難算。
所以在Phong的基礎上,Blinn對此加以拓展,引入了Blinn-Phong反射模型。
Blinn-Phong反射模型
該模型與Phong模型的區別只有在鏡面光分量處理上有一些差別。Blinn-Phong反射模型不再依賴反射角,而是采用半程向量,即光線與視線夾角一半的方向上的單位向量。
半程向量
當觀察方向接近鏡面反射方向時,物體表面法線方向與半程向量接近。
所謂的半程向量就是光照方向向量與觀察方向向量根據平行四邊形法則加起來再除以它的長度就能得到。
h=(v+l)/∣v+l∣h = (v + l) / |v + l|h=(v+l)/∣v+l∣h與n接近就說明了v和r接近,這就是Blinn-Phong模型的特別之處。因為r和v的夾角比較難計算的,用了這個小技巧后,計算便簡單很多。
與漫反射原理幾乎差不多,只不過是將光照方向與法線方向的夾角換成了半程向量與法線方向的夾角而已
Ls=ks(I/r2)max(0,n?h)pL_s = k_s(I / r^2)max(0, n * h)^pLs?=ks?(I/r2)max(0,n?h)p代碼實現
vec3 halfwayDir = normalize(lightDir + viewDir); vec3 specular = vec3(0.3) * pow(max(dot(norm, halfwayDir), 0.0), 32.0); 復制代碼效果圖如下
最后
如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑
如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源項目點點star:http://github.crmeb.net/u/defu不勝感激 !
PHP學習手冊:https://doc.crmeb.com
技術交流論壇:https://q.crmeb.com
總結
以上是生活随笔為你收集整理的Blinn-Phong反射模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电驴服务器搜索文件排序,【图文教程】搜索
- 下一篇: 多轴控制玻璃行业程序 相机 ST LAD