
翻譯文
原文標(biāo)題:Android Lesson Three: Moving to Per-Fragment Lighting
原文鏈接:http://www.learnopengles.com/android-lesson-three-moving-to-per-fragment-lighting/
使用每片段照明
| 歡迎來(lái)到第三課!這節(jié)課,我們將會(huì)在第二課的基礎(chǔ)上, 學(xué)習(xí)如何使用每像素技術(shù)來(lái)達(dá)到相同的照明。 簡(jiǎn)單的正方體即使使用標(biāo)準(zhǔn)的漫射照明我們也能看到差異。 |
![]() screenshot
|
前提條件
本系列的每節(jié)課都以前面的課程為基礎(chǔ),本節(jié)課是第二課的補(bǔ)充,因此請(qǐng)務(wù)在閱讀了之前的課程后再來(lái)回顧。
下面是本系列課程的前幾課:
什么是每像素照明
隨著著色器的使用,每像素照明在游戲中是一種相對(duì)較新的現(xiàn)象。許多有名的舊游戲,例如原版的半條命,都是在著色器之前開(kāi)發(fā)出來(lái)的,主要使用靜態(tài)照明,通過(guò)一些技巧模擬動(dòng)態(tài)照明,使用每頂點(diǎn)(也稱為Gouraud陰影)照明或其他技術(shù),如動(dòng)態(tài)光照貼圖。
光照貼圖可以提供非常好的效果,有時(shí)可以比單獨(dú)的著色器提供更好的效果,因?yàn)榭梢灶A(yù)先計(jì)算昂貴的光線計(jì)算。但缺點(diǎn)是它們占用了大量?jī)?nèi)存并使用它們進(jìn)行動(dòng)態(tài)照明僅限于簡(jiǎn)單的計(jì)算。
使用著色器,現(xiàn)在很多這些計(jì)算轉(zhuǎn)給GPU,這可以完成更多實(shí)時(shí)的效果。
從每頂點(diǎn)照明轉(zhuǎn)移到每片段照明
這本課中,我們將針對(duì)每頂點(diǎn)解決方案和每片段解決方案查看相同的照明代碼。盡管我將這種類型稱為每像素,但在OpenGL ES中我們實(shí)際上使用片段,并且?guī)讉€(gè)片段可以貢獻(xiàn)一個(gè)像素的最終值。
手機(jī)的GPU變得越來(lái)越快,但是性能仍然是一個(gè)問(wèn)題。對(duì)于“軟”照明例如地形,每頂點(diǎn)照明可能足夠好。確保您在質(zhì)量和速度之間取得適當(dāng)?shù)钠胶狻?/p>
在某些情況下可以看到兩種類型的照明之間的顯著差異??纯聪旅娴钠聊唤貓D:
![]() Per vertex lighting
每頂點(diǎn)照明; 在正方形四個(gè)頂點(diǎn)為中心 |
![]() Per fragment lighting
每片段照明; 在正方形四個(gè)頂點(diǎn)為中心 |
在左圖的每頂點(diǎn)照明中正方體的 正面看起來(lái)像是平面陰影,不能 表明附近有光源。這是因?yàn)檎?br>的四個(gè)頂點(diǎn)和光源距離差不多相 等,并且四個(gè)點(diǎn)的低光強(qiáng)度被簡(jiǎn) 單的插入兩個(gè)三角形構(gòu)成的正面。 相對(duì)比,每片段照明很好的 顯示了亮點(diǎn)特性 |
![]() Per vertex lighting
每頂點(diǎn)照明; 在正方形角落 |
![]() Per fragment lighting
每片段照明; 在正方形角落 |
左圖顯示了一個(gè)Gouraud陰影 立方體。當(dāng)光源移動(dòng)到立方體正 面角落時(shí),可以看到類似三角形 的效果。這是因?yàn)檎鎸?shí)際上是 由兩個(gè)三角形組成,并且在每個(gè) 三角形不同方向插值,我們能看 到構(gòu)成立方體的基礎(chǔ)幾何圖形。 每片段的版本顯示上沒(méi)有此類插 值的問(wèn)題并且它在邊緣附近顯示 了一個(gè)漂亮的圓形高光。 |
每頂點(diǎn)照明概述
我們來(lái)看看第二課講的著色器;在該課程中可以找到詳細(xì)的著色器說(shuō)明。
頂點(diǎn)著色器
uniform mat4 u_MVPMatrix; // 一個(gè)表示組合model、view、projection矩陣的常量
uniform mat4 u_MVMatrix; // 一個(gè)表示組合model、view矩陣的常量
uniform vec3 u_LightPos; // 光源在眼睛空間的位置
attribute vec4 a_Position; // 我們將要傳入的每個(gè)頂點(diǎn)的位置信息
attribute vec4 a_Color; // 我們將要傳入的每個(gè)頂點(diǎn)的顏色信息
attribute vec3 a_Normal; // 我們將要傳入的每個(gè)頂點(diǎn)的法線信息
varying vec4 v_Color; // 這將被傳入片段著色器
void main() // 頂點(diǎn)著色器入口
{
// 將頂點(diǎn)轉(zhuǎn)換成眼睛空間
vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);
// 將法線的方向轉(zhuǎn)換成眼睛空間
vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// 將用于哀減
float distance = length(u_LightPos - modelViewVertex);
// 獲取從光源到頂點(diǎn)方向的光線向量
vec3 lightVector = normalize(u_LightPos - modelViewVertex);
// 計(jì)算光線矢量和頂點(diǎn)法線的點(diǎn)積,如果法線和光線矢量指向相同的方向,那么它將獲得最大的照明
float diffuse = max(dot(modelViewNormal, lightVector), 0.1);
// 根據(jù)距離哀減光線
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// 將顏色乘以亮度,它將被插入三角形中
v_Color = a_Color * diffuse;
// gl_Position是一個(gè)特殊的變量用來(lái)存儲(chǔ)最終的位置
// 將頂點(diǎn)乘以矩陣得到標(biāo)準(zhǔn)化屏幕坐標(biāo)的最終點(diǎn)
gl_Position = u_MVPMatrix * a_Position;
}
片段著色器
precision mediump float; // 我們將默認(rèn)精度設(shè)置為中等,我們不需要片段著色器中的高精度
varying vec4 v_Color; // 這是從三角形每個(gè)片段內(nèi)插的頂點(diǎn)著色器的顏色
void main() // 片段著色器入口
{
gl_FragColor = v_Color; // 直接將顏色傳遞
}
正如您所見(jiàn),大部分工作都在我們的著色器中做的。轉(zhuǎn)移到每片段著色照明意味著,我們的片段著色器還有更多的工作要做。
實(shí)現(xiàn)每片段照明
以下是移動(dòng)到每片段照明后的代碼的樣子。
頂點(diǎn)著色器 new
uniform mat4 u_MVPMatrix; // 一個(gè)表示組合model、view、projection矩陣的常量
uniform mat4 u_MVMatrix; // 一個(gè)表示組合model、view矩陣的常量
attribute vec4 a_Position; // 我們將要傳入的每個(gè)頂點(diǎn)的位置信息
attribute vec4 a_Color; // 我們將要傳入的每個(gè)頂點(diǎn)的顏色信息
attribute vec3 a_Normal; // 我們將要傳入的每個(gè)頂點(diǎn)的法線信息
varying vec3 v_Position;
varying vec4 v_Color;
varying vec3 v_Normal;
// 頂點(diǎn)著色器入口點(diǎn)
void main()
{
// 將頂點(diǎn)位置轉(zhuǎn)換成眼睛空間的位置
v_Position = vec3(u_MVMatrix * a_Position);
// 傳入顏色
v_Color = a_Color;
// 將法線的方向轉(zhuǎn)換在眼睛空間
v_Normal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));
// gl_Position是一個(gè)特殊的變量用來(lái)存儲(chǔ)最終的位置
// 將頂點(diǎn)乘以矩陣得到標(biāo)準(zhǔn)化屏幕坐標(biāo)的最終點(diǎn)
gl_Position = u_MVPMatrix * a_Position;
}
頂點(diǎn)著色器比之前更加的簡(jiǎn)單。我們添加了兩個(gè)線性插值變量用來(lái)傳入到片段著色器:頂點(diǎn)位置和頂點(diǎn)法線。它們將在片段著色器計(jì)算光亮的時(shí)候被使用。
片段著色器 new
precision mediump float; //我們將默認(rèn)精度設(shè)置為中等,我們不需要片段著色器中的高精度
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);
// 獲取從光源到頂點(diǎn)方向的光線向量
vec3 lightVector = normalize(u_LightPos - v_Position);
// 計(jì)算光線矢量和頂點(diǎn)法線的點(diǎn)積,如果法線和光線矢量指向相同的方向,那么它將獲得最大的照明
float diffuse = max(dot(v_Normal, lightVector), 0.1);
// 根據(jù)距離哀減光線
diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance)));
// 顏色乘以亮度哀減得到最終的顏色
gl_FragColor = v_Color * diffuse;
}
使用每片段照明,我們的片段著色器還有更多的工作要做。我們基本上將朗伯計(jì)算和哀減移到了每像素級(jí)別,這為我們提供了更逼真的照明,而無(wú)需添加更多頂點(diǎn)。
進(jìn)一步練習(xí)
我們可以在頂點(diǎn)著色器中計(jì)算距離,然后賦值給變量通過(guò)線性插值傳入片段著色器嗎?
教程目錄
- OpenGL Android課程一:入門
- OpenGL Android課程二:環(huán)境光和漫射光
- OpenGL Android課程三:使用每片段照明
- OpenGL Android課程四:介紹紋理基礎(chǔ)
- OpenGL Android課程五:介紹混合(Blending)
- OpenGL Android課程六:介紹紋理過(guò)濾
打包教材
可以在Github下載本課程源代碼:下載項(xiàng)目
本課的編譯版本也可以再Android市場(chǎng)下:google play 下載apk
“我”也編譯了個(gè)apk,方便大家下載:github download




