翻譯文
原文標題:Android Lesson Three: Moving to Per-Fragment Lighting 原文連結:www.learnopengles.com/android-les…
使用每片段照明
歡迎來到第三課!這節課,我們將會在第二課的基礎上, 學習如何使用每畫素技術來達到相同的照明。 簡單的正方體即使使用標準的漫射照明我們也能看到差異。 |
前提條件
本系列的每節課都以前面的課程為基礎,本節課是第二課的補充,因此請務在閱讀了之前的課程後再來回顧。
下面是本系列課程的前幾課:
什麼是每畫素照明
隨著著色器的使用,每畫素照明在遊戲中是一種相對較新的現象。許多有名的舊遊戲,例如原版的半條命,都是在著色器之前開發出來的,主要使用靜態照明,通過一些技巧模擬動態照明,使用每頂點(也稱為Gouraud陰影)照明或其他技術,如動態光照貼圖。
光照貼圖可以提供非常好的效果,有時可以比單獨的著色器提供更好的效果,因為可以預先計算昂貴的光線計算。但缺點是它們佔用了大量記憶體並使用它們進行動態照明僅限於簡單的計算。
使用著色器,現在很多這些計算轉給GPU,這可以完成更多實時的效果。
從每頂點照明轉移到每片段照明
這本課中,我們將針對每頂點解決方案和每片段解決方案檢視相同的照明程式碼。儘管我將這種型別稱為每畫素,但在OpenGL ES中我們實際上使用片段,並且幾個片段可以貢獻一個畫素的最終值。
手機的GPU變得越來越快,但是效能仍然是一個問題。對於“軟”照明例如地形,每頂點照明可能足夠好。確保您在質量和速度之間取得適當的平衡。
在某些情況下可以看到兩種型別的照明之間的顯著差異。看看下面的螢幕截圖:
每頂點照明; 在正方形四個頂點為中心 |
每片段照明; 在正方形四個頂點為中心 |
在左圖的每頂點照明中正方體的 正面看起來像是平面陰影,不能 表明附近有光源。這是因為正面 的四個頂點和光源距離差不多相 等,並且四個點的低光強度被簡 單的插入兩個三角形構成的正面。 相對比,每片段照明很好的 顯示了亮點特性 |
每頂點照明; 在正方形角落 |
每片段照明; 在正方形角落 |
左圖顯示了一個Gouraud陰影 立方體。當光源移動到立方體正 面角落時,可以看到類似三角形 的效果。這是因為正面實際上是 由兩個三角形組成,並且在每個 三角形不同方向插值,我們能看 到構成立方體的基礎幾何圖形。 每片段的版本顯示上沒有此類插 值的問題並且它在邊緣附近顯示 了一個漂亮的圓形高光。 |
每頂點照明概述
我們來看看第二課講的著色器;在該課程中可以找到詳細的著色器說明。
頂點著色器
uniform mat4 u_MVPMatrix; // 一個表示組合model、view、projection矩陣的常量
uniform mat4 u_MVMatrix; // 一個表示組合model、view矩陣的常量
uniform vec3 u_LightPos; // 光源在眼睛空間的位置
attribute vec4 a_Position; // 我們將要傳入的每個頂點的位置資訊
attribute vec4 a_Color; // 我們將要傳入的每個頂點的顏色資訊
attribute vec3 a_Normal; // 我們將要傳入的每個頂點的法線資訊
varying vec4 v_Color; // 這將被傳入片段著色器
void main() // 頂點著色器入口
{
// 將頂點轉換成眼睛空間
vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);
// 將法線的方向轉換成眼睛空間
vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// 將用於哀減
float distance = length(u_LightPos - modelViewVertex);
// 獲取從光源到頂點方向的光線向量
vec3 lightVector = normalize(u_LightPos - modelViewVertex);
// 計算光線向量和頂點法線的點積,如果法線和光線向量指向相同的方向,那麼它將獲得最大的照明
float diffuse = max(dot(modelViewNormal, lightVector), 0.1);
// 根據距離哀減光線
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// 將顏色乘以亮度,它將被插入三角形中
v_Color = a_Color * diffuse;
// gl_Position是一個特殊的變數用來儲存最終的位置
// 將頂點乘以矩陣得到標準化螢幕座標的最終點
gl_Position = u_MVPMatrix * a_Position;
}
複製程式碼
片段著色器
precision mediump float; // 我們將預設精度設定為中等,我們不需要片段著色器中的高精度
varying vec4 v_Color; // 這是從三角形每個片段內插的頂點著色器的顏色
void main() // 片段著色器入口
{
gl_FragColor = v_Color; // 直接將顏色傳遞
}
複製程式碼
正如您所見,大部分工作都在我們的著色器中做的。轉移到每片段著色照明意味著,我們的片段著色器還有更多的工作要做。
實現每片段照明
以下是移動到每片段照明後的程式碼的樣子。
頂點著色器 new
uniform mat4 u_MVPMatrix; // 一個表示組合model、view、projection矩陣的常量
uniform mat4 u_MVMatrix; // 一個表示組合model、view矩陣的常量
attribute vec4 a_Position; // 我們將要傳入的每個頂點的位置資訊
attribute vec4 a_Color; // 我們將要傳入的每個頂點的顏色資訊
attribute vec3 a_Normal; // 我們將要傳入的每個頂點的法線資訊
varying vec3 v_Position;
varying vec4 v_Color;
varying vec3 v_Normal;
// 頂點著色器入口點
void main()
{
// 將頂點位置轉換成眼睛空間的位置
v_Position = vec3(u_MVMatrix * a_Position);
// 傳入顏色
v_Color = a_Color;
// 將法線的方向轉換在眼睛空間
v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// gl_Position是一個特殊的變數用來儲存最終的位置
// 將頂點乘以矩陣得到標準化螢幕座標的最終點
gl_Position = u_MVPMatrix * a_Position;
}
複製程式碼
頂點著色器比之前更加的簡單。我們新增了兩個線性插值變數用來傳入到片段著色器:頂點位置和頂點法線。它們將在片段著色器計算光亮的時候被使用。
片段著色器 new
precision mediump float; //我們將預設精度設定為中等,我們不需要片段著色器中的高精度
uniform vec3 u_LightPos; // 光源在眼睛空間的位置
varying vec3 v_Position; // 插入的位置
varying vec4 v_Color; // 插入的位置顏色
varying vec3 v_Normal; // 插入的位置法線
void main() // 片段著色器入口
{
// 將用於哀減
float distance = length(u_LightPos - v_Position);
// 獲取從光源到頂點方向的光線向量
vec3 lightVector = normalize(u_LightPos - v_Position);
// 計算光線向量和頂點法線的點積,如果法線和光線向量指向相同的方向,那麼它將獲得最大的照明
float diffuse = max(dot(v_Normal, lightVector), 0.1);
// 根據距離哀減光線
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// 顏色乘以亮度哀減得到最終的顏色
gl_FragColor = v_Color * diffuse;
}
複製程式碼
使用每片段照明,我們的片段著色器還有更多的工作要做。我們基本上將朗伯計算和哀減移到了每畫素級別,這為我們提供了更逼真的照明,而無需新增更多頂點。
進一步練習
我們可以在頂點著色器中計算距離,然後賦值給變數通過線性插值傳入片段著色器嗎?
教程目錄
- OpenGL Android課程一:入門
- OpenGL Android課程二:環境光和漫射光
- OpenGL Android課程三:使用每片段照明
- OpenGL Android課程四:介紹紋理基礎
- OpenGL Android課程五:介紹混合(Blending)
- OpenGL Android課程六:介紹紋理過濾
打包教材
可以在Github下載本課程原始碼:下載專案
本課的編譯版本也可以再Android市場下:google play 下載apk
“我”也編譯了個apk,方便大家下載:github download