翻譯文
原文標(biāo)題:OpenGL Android Lesson One: Getting Started
原文鏈接:http://www.learnopengles.com/android-lesson-two-ambient-and-diffuse-lighting/
環(huán)境光和漫射光
歡迎來到第二課扯键,我們將學(xué)習(xí)如何使用 著色器實現(xiàn)朗伯反射( Lambertian reflectance )板辽,也稱為標(biāo)準(zhǔn)漫射照明梧奢。 在OpengGLES2,我們需要實現(xiàn)我們自己的照明算法穆刻, 因此我們要學(xué)會數(shù)學(xué)如何工作以及如何應(yīng)用到我們的場景中。 |
![]() screenshot
|
閱讀本文前提條件
本系列的每節(jié)課都以前面的課程為基礎(chǔ)杠步。在開始前氢伟,請看第一課,因為本課程將以此為基礎(chǔ)概念介紹幽歼。
什么是光
沒錯朵锣!一個沒有光的世界是昏暗的。沒有[光]甸私,我們甚至不能感知世界或我們周圍的物體诚些,除了聲音和觸摸等其他感官。
光向我們展示了物體是明亮還是昏暗皇型,是遠(yuǎn)還是近诬烹,它的角度是什么。
在現(xiàn)實世界犀被,我們所感知的光實際是數(shù)萬億微小粒子的聚集椅您,稱為光子。它從光源飛出寡键,反彈數(shù)千或數(shù)百萬次掀泳,最終到達我們的眼鏡我們稱之為光。
我們?nèi)绾瓮ㄟ^計算機圖形模擬光的影響西轩?
有兩種流行的方法:光線追蹤和光柵化
光線跟蹤的工作原理是通過數(shù)學(xué)計算跟蹤實際光線并查看它們的最終位置员舵。該技術(shù)可以得到非常精準(zhǔn)和逼真的結(jié)果,但缺點是模擬所有這些光線的計算成本非常高藕畔,并且通常對于實時渲染來說太慢了马僻。
由于這個限制,大多數(shù)實時圖形計算使用光柵化注服,它通過近似值模擬光照韭邓。鑒于當(dāng)前游戲的真實性,光柵化看起來非常好溶弟,即使在手機上也可以快速實現(xiàn)實時圖形女淑。OpengGL ES主要是一個光柵化庫,因此我們主要關(guān)注這個辜御。
不同種類的光
事實證明鸭你,我們可以抽象出光的工作方式,并提出三種基本的光照方式
![]() Ambient
環(huán)境光 |
環(huán)境光 這是基本的照明水平,似乎遍布整個場景袱巨。它似乎不是來自任何 光源的光阁谆,因為它在到達你之前已經(jīng)反彈了很多次。這種類型的光 在戶外的陰天可以體驗愉老,或者在戶內(nèi)作為許多不同光源的積累影響场绿。 我們可以為物體或場景設(shè)置一個基本的亮度,而不是為所有的 光單獨計算嫉入。 |
![]() diffuse
環(huán)境照明和漫射照明 的例子 |
漫射照明 這是直接從一個物體上跳彈后到達您眼睛中的光裳凸,物體的亮度 隨著它與照明的角度而變化,面向燈光的方向比其他角度更加明亮 此外劝贸,無論我們相對于物體的角度怎樣姨谷,我們都覺得物體是相同的 亮度,這也被稱為Lambert的余弦定律映九。漫射照明或朗伯反射率在 日常生活中很常見梦湘,您可以在室內(nèi)燈光照明的白墻上輕松看到。 |
![]() specular
鏡面高光的一個例子 |
鏡面照明 與漫射照明不同件甥,當(dāng)我們相對于物體移動時捌议,鏡面光照也會 發(fā)生改變。這給物體帶來“光澤”引有,并且可以在“更光滑”的表面 上看到瓣颅,例如玻璃和其他有光澤的物體。 |
模擬光
正如3D場景中的3中主要類型的光照一樣譬正,還有三種主要類型的光源:定向光源宫补,點光源,聚光燈曾我,這些也可以在日常生活中輕松看到粉怕。
![]() Directional lighting
一個明亮的風(fēng)景 |
定向光源 定向光照通常來自于一個很遠(yuǎn)的光源,它可以均勻的照亮整個 場景達到相同的亮度抒巢。這種光源是最簡單的類型贫贝,無論您處在 場景哪里,光照都具有相同的強度和方向蛉谜。 |
![]() Point lighting
一個點光源的例子 |
點光源 點光源可以添加到場景中稚晚,以提供更多樣化和逼真的照明。 點光的照射隨著距離而下降型诚,并且它的光線在所有方向上 向外傳播客燕,光源位于中心。 |
![]() Spot lighting
聚光燈 |
聚光燈 除了具有點光源的特性外俺驶,聚光燈也有光哀減的方向幸逆, 通常呈錐形。 |
數(shù)學(xué)
本節(jié)課暮现,我們來看看來自一個點光源的環(huán)境照明和漫射照明还绘。
環(huán)境照明
環(huán)境照明其實是間接漫射照明,但它也可以被認(rèn)為是遍布整個場景的低級光栖袋。如果我們這么想拍顷,那么它將非常好計算:
// 最終顏色 = 材質(zhì)顏色 * 環(huán)境光顏色
final color = material color * ambient light color
例如,我們有個紅色的物體和一個暗白色的環(huán)境照明塘幅。我們假設(shè)三個顏色(紅昔案,綠,藍)的數(shù)組存儲顏色电媳,使用RGB顏色模型:
// 最終顏色 = 紅色 * 暗白色 = 暗紅色
final color = {1, 0, 0} * {0.1, 0.1, 0.1} = {0.1, 0.0, 0.0}
物體的最終顏色將是暗紅色踏揣,如果您有一個被昏暗的白光照明的紅色物體,那么這就是您的預(yù)期匾乓±谈澹基本的環(huán)境光真的沒有比這更多的了,除非您想加入更先進的照明技術(shù)拼缝,如光能傳遞娱局。
漫射照明-點光源
對于漫射照明,我們需要添加哀減和光源位置咧七。光源位置將用來計算光線和表面的角度衰齐,它將影響表面的整體光照水平。它還將用于計算光源到表面的距離继阻,這決定了光在這個點上的強度耻涛。
第一步:計算朗伯因子(lambert factor)
我們最重要的是需要弄清楚表面和光線之間的角度。面向光直射的表面因該全強度照射瘟檩,而傾斜的表面因該得到較少的照射犬第,比較合適的計算方式是使用Lambert的余弦定律。
果我們有兩個向量芒帕,一個是從光到表面上的一個點歉嗓,第二個是表面的法線(如果表面是平面,則表面法線是指向上或垂直于該表面的矢量)背蟆,然后我們可以通過對每個向量進行歸一化來計算余弦鉴分,使其長度為1,然后通過計算兩個向量的點積(數(shù)量積)带膀。
這個操作可以由OpenGL ES 2輕松完成志珍。
我們稱這位朗伯因子,它的取值范圍在0~1之間
// 光線向量 = 光源位置 - 物體位置
light vector = light position - object position
// 余弦 = 物體法線和歸一化后的光線向量的點積
cosine = dot product(object normal, normalize(light vector))
// 朗伯因子 = 取余弦和0中最大的
lambert factor = max(cosine, 0)
首先我們通過光源位置減去物體位置得到光線向量垛叨,然后我們通過物體法線和光向量的點積得到余弦伦糯。我們標(biāo)準(zhǔn)化光向量,這意味著改變它的長度,長度為1敛纲,這個物體的法線長度也是1喂击,兩個歸一化向量的點積得到他們之間的余弦。因為點積的取值范圍是-11淤翔,所以我們將其限制到01翰绊。
這兒有個處在原點的平面,其表面法線指向天空的例子旁壮。
光的位置在{0, 10, -10}监嗜,我們想要計算在原點的光。
// 光線向量
light vector = {0, 10, -10} - {0, 0, 0} = {0, 10, -10}
// 物體法線
object normal = {0, 1, 0}
簡潔的說抡谐,如果們沿著光線矢量走裁奇,我們到達光源的位置。為了歸一化矢量麦撵,我們將每個分量除以矢量長度:
// 光線向量長度 = 平方根(0*0 + 10*10 + (-10 * -10)) = 平方根(200) = 14.14
light vector length = square root(0*0 + 10*10 + (-10 * -10)) = square root(200) = 14.14
// 歸一化光線向量
normalize light vector = {0, 10/14.14, -10/14.14} = {0, 0.707, -0.707}
然后我們計算點積:
// 點積
dot product({0, 1, 0}, {0, 0.707, -0.707}) = (0 * 0) + (1 * 0.707) + (0 * -0.707) = 0.707
最后我們限制范圍:
// 朗伯因子
lambert factor = max(0.707, 0) = 0.707
OpenGL ES 2的著色器語言內(nèi)置了對其中一些函數(shù)的支持框喳,因此我們不需要手動完成所有數(shù)學(xué)運算,但它仍然有助于理解正在發(fā)生的事情厦坛。
第二步:計算哀減系數(shù)
接下來五垮,我們需要計算哀減。來自光源的實際光哀減遵循反平方定律
也可以這樣表示:
// 亮度 = 1 / 距離的平方
luminosity = 1 / (distance * distance)
回到我們的列子杜秸,因為我們有光線長度為14.14放仗,這兒我們最終的亮度:
luminosity = 1 / (14.14 * 14.14) = 1 / 200 = 0.005
正如您所見,反平方定律會導(dǎo)致距離的強烈哀減撬碟。這就是點光源的光在現(xiàn)實世界中的作用诞挨,但是由于我們圖形展示范圍有限,控制這個哀減系數(shù)是非常有用的呢蛤,因此我們?nèi)匀荒塬@得逼真的照明而不會讓其看起來很昏暗惶傻。
第三步:計算最終顏色
現(xiàn)在我們知道了余弦和哀減度,我們可以計算我們最終的亮度:
// 最終顏色 = 材質(zhì)顏色 * (光的顏色 * 朗伯因子 * 亮度)
final color = material color * (light color * lambert factor * luminosity)
繼續(xù)我們之前的紅色物體和白光源的例子其障,這兒計算最終顏色:
final color = {1, 0, 0} * ({1, 1, 1} * 0.707 * 0.005) = {1, 0, 0} * {0.0035, 0.0035, 0.0035} = {0.0035, 0, 0}
回顧一下银室,對于漫射照明,我們需要使用表面和光線之間的角度以及距離励翼,用來計算最終的整體漫射亮度蜈敢。
以下是步驟:
// 第一步
light vector = light position - object position
cosine = dot product(object normal, normalize(light vector))
lambert factor = mac(cosine, 0)
// 第二步
luminosity = 1 / (distance * distance)
// 第三步
final color = material color * (light color * lambert factor * luminosity)
將這一切放到OpenGL ES 2著色器中
頂點著色器
final String vertexShader =
"uniform mat4 u_MVPMatrix; \n" + // 一個表示組合model、view汽抚、projection矩陣的常量
"uniform mat4 u_MVMatrix; \n" + // 一個表示組合model抓狭、view矩陣的常量
"uniform vec3 u_LightPos; \n" + // 光源在眼睛空間(相對于相機視角)的位置
"attribute vec4 a_Position; \n" + // 我們將要傳入的每個頂點的位置信息
"attribute vec4 a_Color; \n" + // 我們將要傳入的每個頂點的顏色信息
"attribute vec3 a_Normal; \n" + // 我們將要傳入的每個頂點的法線信息
"varying vec4 v_Color; \n" + // 這將被傳入片段著色器
"void main() \n" + // 頂點著色器入口
"{ \n" +
// 將頂點轉(zhuǎn)換成眼睛空間(相對于相機視角)
" vec3 modelViewVertex = vec3(u_MVMatrix * a_Position); \n" +
// 將法線的方向轉(zhuǎn)換成眼睛空間(相對于相機視角)
" vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0)); \n" +
// 將用于哀減
" float distance = length(u_LightPos - modelViewVertex); \n" +
// 獲取從光源到頂點方向的光線向量
" vec3 lightVector = normalize(u_LightPos - modelViewVertex); \n" +
// 計算光線矢量和頂點法線的點積,如果法線和光線矢量指向相同的方向造烁,那么它將獲得最大的照明
" float diffuse = max(dot(modelViewNormal, lightVector), 0.1); \n" +
// 根據(jù)距離哀減光線
" diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance))); \n" +
// 將顏色乘以亮度否过,它將被插入三角形中
" v_Color = a_Color * diffuse; \n" +
// gl_Position是一個特殊的變量用來存儲最終的位置
// 將頂點乘以矩陣得到標(biāo)準(zhǔn)化屏幕坐標(biāo)的最終點
" gl_Position = u_MVPMatrix * a_Position; \n" +
"} \n";
這里有相當(dāng)多的事情要做午笛。我們在第一課講到過我們要有一個model/view/projection的組合矩陣,但是我們還要添加了一個model/view矩陣苗桂。為什么药磺?因為我們將需要這個矩陣去計算光源位置到當(dāng)前頂點位置之間的距離。對于漫射照明誉察,無論您使用世界空間(model矩陣)或眼睛空間(model/view矩陣)只要你能計算出合適的距離和角度實際上都沒有問題。
我們傳入頂點的顏色和位置信息惹谐,以及它的法線持偏。我們會將最終的顏色傳入片段著色器,它將在頂點之間插值氨肌,這也被稱為Gouraud著色法鸿秆。
讓我們來看看著色器每一部分的意義:
// 將頂點轉(zhuǎn)換成眼睛空間(相對于相機視角)
" vec3 modelViewVertex = vec3(u_MVMatrix * a_Position); \n"
因為我們是在眼睛空間觀察光源位置,我們轉(zhuǎn)換當(dāng)前的頂點位置到眼睛空間的坐標(biāo)系中怎囚,因此我們能計算出對應(yīng)的距離和角度卿叽。
// 將法線的方向轉(zhuǎn)換成眼睛空間(相對于相機視角)
" vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0)); \n" +
我們也需要轉(zhuǎn)換法線的方向。這里我們只是想上面位置一樣做了個常規(guī)乘法恳守,但是如果model或view矩陣做過旋轉(zhuǎn)或傾斜考婴,那么將不能工作:我們實際上需要通過將法線乘以原始矩陣的反轉(zhuǎn)來消除傾斜或縮放的影響。這個網(wǎng)站很好的解釋了為什么我們必須這么做
// 將用于哀減
" float distance = length(u_LightPos - modelViewVertex); \n"
如前面數(shù)學(xué)部分所示催烘,我們需要這個距離去計算哀減系數(shù)
// 獲取從光源到頂點方向的光線向量
" vec3 lightVector = normalize(u_LightPos - modelViewVertex); \n"
我們也需要光線向量去計算朗伯反射因子
// 計算光線矢量和頂點法線的點積沥阱,如果法線和光線矢量指向相同的方向,那么它將獲得最大的照明
" float diffuse = max(dot(modelViewNormal, lightVector), 0.1); \n"
這與上面的數(shù)學(xué)部分相同伊群,只是在OpenGL ES 2著色器中完成考杉。后面的0.1是一種非常便宜的環(huán)境照明方式(最小值將被限制在0.1)。
// 根據(jù)距離哀減光線
" diffuse = diffuse * (1.0 / (1.0 + (0.25 * distance * distance))); \n"
這里和上面的數(shù)學(xué)部分略有不同舰始。我們將距離的平方縮放0.25以抑制衰減的效應(yīng)崇棠,并且我們還將修改的距離加1,這樣當(dāng)光源非常接近物體時我們就不會過飽和(否則丸卷,當(dāng)距離小于1時枕稀,該等式實際上回照亮光源而不是哀減它)。
// 將顏色乘以亮度谜嫉,它將被插入三角形中
" v_Color = a_Color * diffuse; \n" +
// gl_Position是一個特殊的變量用來存儲最終的位置
// 將頂點乘以矩陣得到標(biāo)準(zhǔn)化屏幕坐標(biāo)的最終點
" gl_Position = u_MVPMatrix * a_Position; \n"
當(dāng)我們有了最終的光色抽莱,我們將它乘以頂點的顏色得到最終輸出的顏色,然后我們將這個頂點的位置投影到屏幕上骄恶。
像素著色器
final String fragmentShader =
"precision mediump float; \n" + // 我們將默認(rèn)精度設(shè)置為中等食铐,我們不需要片段著色器中的高精度
"varying vec4 v_Color; \n" + // 這是從三角形每個片段內(nèi)插的頂點著色器的顏色
"void main() \n" + // 片段著色器入口
"{ \n" +
" gl_FragColor = v_Color; \n" + // 直接將顏色傳遞
"} \n";
因為我們是在每個頂點的基礎(chǔ)上計算光,我們的片段著色器和上節(jié)課一樣僧鲁,我們所做的是將顏色直接傳過去虐呻。在下節(jié)課中象泵,我們將學(xué)習(xí)每像素照明。
每頂點照明和每像素照明
這節(jié)課我們的關(guān)注點在實現(xiàn)每頂點照明斟叼。對于具有光滑表面的物體(如地形)偶惠,或具有許多三角形的物體的漫反射,這通常是足夠了朗涩。然而忽孽,當(dāng)您的物體沒有包含許多頂點時(例如我們的在這個案例中的正方體),或者有尖角谢床,頂點光照可能會導(dǎo)致偽影兄一,因為亮度在多邊形上線性插值;當(dāng)鏡面高光添加到圖像時识腿,這些偽影也會變得更加明顯出革。更多關(guān)于Gouraud著色法的Wiki文章
正方體的構(gòu)造
在第一課中,我們將位置和顏色屬性打包到一個數(shù)組中渡讼,但是OpengGL ES 2也允許讓我們將屬性單獨存放:
//X, Y, Z
final float[] cubePositionData = {
// 在OpenGL骂束,逆時針繞組(下面的點事逆時針順序)是默認(rèn)的。
// 這意味著當(dāng)我們在觀察一個三角形時成箫,如果這些電視逆時針的展箱,那么我們正在看"前面",如果不是我們則正在看背面
// OpenGL有一個優(yōu)化蹬昌,所有背面的三角形都會被剔除析藕,因為它們通常代表一個物體的背面,無論如何都不可見
// 正面
-1.0F, 1.0F, 1.0F,
-1.0F, -1.0F, 1.0F,
1.0F, 1.0F, 1.0F,
-1.0F, -1.0F, 1.0F,
1.0F, -1.0F, 1.0F,
1.0F, 1.0F, 1.0F,
...
};
// R凳厢,G账胧,B,A
final float[] cubeColorData = {
// 正面紅色
1.0F, 0.0F, 0.0F, 1.0F,
1.0F, 0.0F, 0.0F, 1.0F,
1.0F, 0.0F, 0.0F, 1.0F,
1.0F, 0.0F, 0.0F, 1.0F,
1.0F, 0.0F, 0.0F, 1.0F,
1.0F, 0.0F, 0.0F, 1.0F,
...
};
新的OpenGL flag
我們還使用了glEnable()
調(diào)用啟用了剔除和深度緩沖:
// 使用剔除去掉背面
GLES20.glEnable(GLES20.GL_CULL_FACE);
// 啟用深度測試
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
作為優(yōu)化先紫,您可以告訴OpenGL剔除物體背面的三角形治泥。當(dāng)我們定義正方體時,我們還定義了每個三角形的三個點遮精,以便當(dāng)我們在查看正面的時候是逆時針的居夹。當(dāng)我們翻轉(zhuǎn)三角形以便我們到背面時,這些點將會順時針展示本冲。
您只能同時看到一個正方體的三個面准脂,所以這個優(yōu)化告訴OpenGL不要浪費時間去繪制背面的三角形。
之后當(dāng)我們繪制透明的物體時檬洞,我們希望關(guān)閉剔除狸膏,然后物體背面將會變得可見。
我們還開啟了深度測試添怔。如果你總是從后面向前面繪制物體湾戳,那么深度測試絕非必要贤旷,但是通過啟用它您不僅不需要擔(dān)心繪制順序(盡管如果你先畫最近的物體渲染會更快),一些顯卡也將進行優(yōu)化砾脑,通過花費更少的時間繪制像素來加速渲染幼驶。
加載著色器程序的修改
因為在OpenGL中加載著色器程序的步驟大致相同,這些步驟可以很容易的重構(gòu)為一個單獨的方法韧衣。我們還添加了以下調(diào)用來檢索調(diào)試信息盅藻,以防編譯/鏈接失敗:
GLES20.glGetProgramInfoLog(programHandle);
GLES20.glGetShaderInfoLog(shaderHandle);
光點的頂點和著色程序
這個新的頂點和著色器程序繪制在屏幕上代表當(dāng)前光源的位置:
// 定義一個簡單的著色程序
final String pointVertexShader =
"uniform mat4 u_MVPMatrix; \n" +
"attribute vec4 a_Position; \n" +
"void main() \n" +
"{ \n" +
" gl_Position = u_MVPMatrix * a_Position; \n" +
" gl_PointSize = 5.0; \n" +
"} \n";
final String pointFragmentShader =
"precision mediump float; \n" +
"void main() \n" +
"{ \n" +
" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) \n" +
"} \n";
這個著色器類似于第一課的簡單著色器畅铭,這里有個新的成員gl_PointSize
氏淑,直接固定它的值為5.0,這是點的像素尺寸顶瞒。當(dāng)我們使用GLES20.GL_POINTS
模式繪制這個點的時候它會被使用夸政。我們也直接設(shè)置了它的顯示顏色為白色元旬。
進一步練習(xí)
- 嘗試刪除“過渡飽和”看會發(fā)生什么
- 這里的照明方式存在缺陷榴徐,你能發(fā)現(xiàn)是什么嗎?提示:我們做環(huán)境照明的方式的缺點是什么匀归,以及alpha會放生什么坑资?
- 如果將
gl_PointSize
添加到正方體著色器并使用GL_POINTS
繪制它會發(fā)生什么?
進一步閱讀
- Clockworkcoders教程:每片段照明
- Lighthouse3d.com:法線矩陣
- arcsynthesis.org: OpenGL教程:法線轉(zhuǎn)換
- OpenGL編程指南:5章 照明
在編寫本教程時穆端,上面的進一步閱讀部分對我來說是非常寶貴的資源袱贮,因此我強烈建議您閱讀它們以獲得更多的信息和解釋。
教程目錄
- OpenGL Android課程一:入門
- OpenGL Android課程二:環(huán)境光和漫射光
- OpenGL Android課程三:使用每片段照明
- OpenGL Android課程四:介紹紋理基礎(chǔ)
- OpenGL Android課程五:介紹混合(Blending)
- OpenGL Android課程六:介紹紋理過濾
打包教材
可以在Github下載本課程源代碼:下載項目
本課的編譯版本也可以再Android市場下:google play 下載apk
“我”也編譯了個apk体啰,方便大家下載:github download