OpenGL Android課程三:使用每片段照明

翻譯文

原文標(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
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
Per vertex lighting

每頂點(diǎn)照明;
在正方形四個(gè)頂點(diǎn)為中心
Per fragment lighting
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
Per vertex lighting

每頂點(diǎn)照明;
在正方形角落
Per fragment lighting
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ò)線性插值傳入片段著色器嗎?

教程目錄

打包教材

可以在Github下載本課程源代碼:下載項(xiàng)目
本課的編譯版本也可以再Android市場(chǎng)下:google play 下載apk
“我”也編譯了個(gè)apk,方便大家下載:github download

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容