OpenGL 基礎光照詳解

2023-11-09 18:01:09

1. 光照

顯示世界中,光照環境往往是相對複雜的。因為假設太陽作為世界的唯一光源,那麼太陽光照在物體A上A將陽光進行反射後,A又做為一個新的光源共同作用於另一個物體B。所以於B來講光源是複雜的。然而這只是其中一個因素,受制於天氣、溫度等其他情況我們需要考慮的因素更多。在OpenGL中我們僅考慮一些簡單的模型,對現實情況進行一個近似的模擬。這一節中我們主要介紹一下馮氏光照模型。馮氏光照模型中主要有3個分量構成:

  • 環境光照(Ambient Lighting):即使在黑暗的情況下,世界上通常也仍然有一些光亮(月亮、遠處的光),所以物體幾乎永遠不會是完全黑暗的。為了模擬這個,我們會使用一個環境光照常數,它永遠會給物體一些顏色。
  • 漫反射光照(Diffuse Lighting):模擬光源對物體的方向性影響(Directional Impact)。它是馮氏光照模型中視覺上最顯著的分量。物體的某一部分越是正對著光源,它就會越亮。
  • 鏡面光照(Specular Lighting):模擬有光澤物體上面出現的亮點。鏡面光照的顏色相比於物體的顏色會更傾向於光的顏色。

接下來我們將逐一講解這三個分量。

2. 環境光照

上文中提到了,真實世界中的光源情況是複雜的。如果對系統中的每一個光源都進行考慮,這種演演算法叫做全域性照明演演算法。但是這種演演算法既開銷高昂又極其複雜。我們使用環境光照來簡化這個概念,即使用一個很小的常數光照顏色,疊加到實際光照顏色中。

 我們看到此處直接用環境顏色與物體顏色相乘。這是因為當前階段我們還沒有新增光源。

3. 漫反射光照

當光線照射在平面上時會發生鏡面反射,這是一個光學常識。但事實上真實世界中的物體表面一般都不是完全的平面而是凹凸不平的(微觀上的凹凸不平),此時將發生漫反射。漫反射的結果就是,你不僅可以從光源的映象角度可以觀察到物體,從各個角度你都可以觀察得到。不過漫反射的強度區域光源射入平面的夾角有關。通過實驗,我們觀察到當射入角度與平面法線夾角越小時,漫反射強度越強。

我們要在GL中模擬漫反射效果。從上述的敘述中我們知道,漫反射的關鍵因素在於法線夾角。那麼也就是說,我們在GL中需要兩點,第一個是法向量,第二個是光線射入的角度

法向量是相對於平面而言的,於點是沒有意義的。而同一點在不同平面中對應的法向量也是不同的。這就決定了我們需要改造我們的頂點資料,傳一組法向量給頂點著色器,此外繪製VAO時也不能以EBO繪製,而應該以VBO繪製。

雖然對燈的著色器使用不能完全利用的頂點資料看起來不是那麼高效,但這些頂點資料已經從箱子物件載入後開始就儲存在GPU的記憶體裡了,所以我們並不需要儲存新資料到GPU記憶體中。這實際上比給燈專門分配一個新的VBO更高效了。

4. 計算漫反射光照

頂點資料中我們傳入了法向量,我們還需要一個角度。頂點著色器中我們是可以拿到頂點資料的,這時只要我們拿到光源的位置即可以計算出光的方向了,標準化以後就是方向向量了。光源的位置一般是相對固定的,我們可以用uniform變數在外界對著色器進行賦值。記得將光源座標和頂點座標都轉化為世界座標哦,或者保證光源座標、頂點座標和法向量在同一座標系統內也可。法向量和方向向量都記得要標準化,這樣他們點乘的結果才是兩個向量的夾角餘弦值。

最後,如果夾角餘弦值小於零,我們認為這時無意義的,他將造成我們的漫反射分量為負值,而真實世界中,僅可能是無影響,而不會是負影響。所以我們小於0時我們取0。

片段著色器中的程式碼大概是這個樣子的:

還有一件事,之前說過,要保證光源座標、頂點座標和法向量在同一座標系統內。一般情況下我們會將他們轉化為世界座標,因為這更符合我們的直覺。我們要把法向量也轉換到世界空間內。頂點資料中,你所寫的法向量直覺上應該是區域性座標,所以要經過模型矩陣轉換。此外,模型矩陣中我們也可能對物體進行非比例變化,這將導致我們的法向量發現也發生改變。所以在把法向量從區域性座標轉化至世界座標時,我們要經過法線矩陣的轉換。我們可以通過逆矩陣和轉置矩陣來變化模型矩陣。所以頂點著色器中我們的程式碼大概是這個樣子的:

 其中inverse()是求逆矩陣函數,transpose是求轉置矩陣函數。

5. 鏡面光照

那麼,最後我們只要再將鏡面反射疊加進去就好了。

跟漫反射的原理差不多,我們看到物體的顏色跟物體表面鏡面反射的光線與我們觀察的視角的夾角有關。當夾角越小時,那麼鏡面光的影響越大。與我們的效果就是,我們將看到一個高光。

那麼我們還是需要兩個方向向量,第一個是鏡面光的方向向量,第二個是觀察角度的方向向量

鏡面光的方向我們可以根據光源方向及法向量通過反射計算出來。觀察方向則是我們攝像機的位置與觀察點所構成的向量。

所以我們的兩個向量大概是這個樣子的:

我們看到,觀察方向需要我們傳入一個攝像機位置,我們需要在渲染迴圈中將攝像機的世界座標傳給片段著色器。

計算反射光時,我們使用的是reflect函數。這個函數需要的是傳入一個從光源指向平面的向量以及平面的法向量。這裡之所以我們傳入-lightDir是因為我們之前計算的lightDir是從平面指向光源的。

這裡我們最好給一個鏡面強度係數,以免當光源垂直照向平面時,我們的鏡面光分量過於明亮。

兩個向量獲取後我們就可以計算他們的夾角來計算我們的鏡面光分量了。

首先我們看鏡面係數的計算方式,點乘求夾角餘弦值後取大於0的值,然後呼叫pow函數。pow是取這個係數的指定次冪。我們指定了32,代表我們指定了他的反光度是32。一個物體的反光度越高,反射光的能力越強,散射得越少,高光點就會越小。在下面的圖片裡,你會看到不同反光度的視覺效果影響:

至此我們計算出鏡面係數,用光源顏色乘鏡面係數乘鏡面強度係數後即可獲得鏡面分量。

最後的最後,我們將環境光照、漫反射光照及鏡面光照進行疊加後,與物體顏色相乘,即可獲得物體在馮氏光照光源影響下所展示的顏色了。

 在光照著色器的早期,開發者曾經在頂點著色器中實現馮氏光照模型。在頂點著色器中做光照的優勢是,相比片段來說,頂點要少得多,因此會更高效,所以(開銷大的)光照計算頻率會更低。然而,頂點著色器中的最終顏色值是僅僅只是那個頂點的顏色值,片段的顏色值是由插值光照顏色所得來的。結果就是這種光照看起來不會非常真實,除非使用了大量頂點。

 在頂點著色器中實現的馮氏光照模型叫做Gouraud著色(Gouraud Shading),而不是馮氏著色(Phong Shading)。記住,由於插值,這種光照看起來有點遜色。馮氏著色能產生更平滑的光照效果。