從0開始的OpenGL學習(十四)-3種光源模型

本文主要解決一個問題:

如何在OpenGL中模擬三種光源類型沉颂?

引言

之前的文章中纯露,我們把光源定義成空間中的一點。效果確實不錯,但是還不足以模擬現(xiàn)實世界中的大部分光源痊臭。一個簡單的例子睛约,它無法模擬太陽光。在本章中馋辈,我們會介紹3中模擬真實世界中光源的模型,使用這三種模型我們可以模擬絕大部分的光源。這三種光源模型是:方向光锰什、點光源、聚光燈丁逝。

我們先從方向光開始汁胆,然后是點光源,最后是聚光燈霜幼。

方向光(Directional Light)

方向光模型模擬的是一個非常遠的地方發(fā)射出來的光嫩码。在非常遠的距離上,到物體上就近似于平行罪既。想想太陽光铸题,太陽距離地球大約1.5億公里,地球的半徑是6378公里琢感,算起來丢间,太陽光照射的角度范圍大約只有0.0024度,照到地球的時候和平行也沒什么區(qū)別了驹针。

方向光模型

由于光線都是平行照射烘挫,光照效果也就和光源位置無關(guān)。所以柬甥,方向光的模型需要的是一個方向參數(shù)而不是位置饮六,著色器在計算的時候也幾乎相同其垄。我們來改一下光源結(jié)構(gòu):

struct Light{
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
void main(){
...
    vec3 lightDir = normalize(-light.direction);  
...
}

注意我們要光線方向的反方向用于計算角度。那為啥不直接指定反方向呢卤橄?這是一個習慣的問題绿满,說到方向光窟扑,我們最直接的反應(yīng)就是光線方向棒口,這最符合我們的邏輯認識。

要觀察方向光的效果辜膝,我們需要在之前顯示3D盒子章節(jié)中的10個盒子无牵。回憶一下之前的章節(jié)厂抖,首先我們要需要10個不同的位置:

glm::vec3 cubePositions[] = {
  glm::vec3( 0.0f,  0.0f,  0.0f), 
  glm::vec3( 2.0f,  5.0f, -15.0f), 
  glm::vec3(-1.5f, -2.2f, -2.5f),  
  glm::vec3(-3.8f, -2.0f, -12.3f),  
  glm::vec3( 2.4f, -0.4f, -3.5f),  
  glm::vec3(-1.7f,  3.0f, -7.5f),  
  glm::vec3( 1.3f, -2.0f, -2.5f),  
  glm::vec3( 1.5f,  2.0f, -2.5f), 
  glm::vec3( 1.5f,  0.2f, -1.5f), 
  glm::vec3(-1.3f,  1.0f, -1.5f)  
};

還需要10個把模型從局部空間轉(zhuǎn)換到世界空間的模型矩陣:

for(unsigned int i = 0; i < 10; i++)
{
    glm::mat4 model;
    model = glm::translate(model, cubePositions[i]);
    float angle = 20.0f * i;
    model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
    lightingShader.setMat4("model", model);

    glDrawArrays(GL_TRIANGLES, 0, 36);
}

最后茎毁,別忘了設(shè)置方向光,你可以在主循環(huán)外面忱辅,也可以在主循環(huán)里面設(shè)置七蜘。當然,之前對光源位置的引用也都需要刪除墙懂。

lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f); 

編譯運行橡卤,如果程序沒有問題,你看到的場景應(yīng)該是類似這樣的:

運行效果

看上去像是從天上有光照下來把這些箱子照亮了损搬。如果你顯示的不對碧库,完整的源代碼在這里。

點光源(Point Light)

方向光通常用來模擬整個場景接收的全局光照巧勤,但是除了全局光照之外嵌灰,我們通常還需要一些小的光源,例如一個燈泡之類的颅悉。這些光源就是點光源沽瞭。點光源常常被設(shè)置在某個位置上,然后隨著離距離的變遠光照強度變小剩瓶。

點光源效果

之前的章節(jié)里驹溃,我們用到了最簡單的點光源。這個點光源有個缺點延曙,就是光照不會隨著距離減弱豌鹤,反而好像是越來越強了,這顯然是不符合常理的搂鲫。在大多數(shù)3D場景中傍药,我們希望的點光源是像現(xiàn)實生活中那樣磺平,只能照亮周圍一小片區(qū)域魂仍。

如果實現(xiàn)過之前章節(jié)中的10個盒子拐辽,你可能會注意到盒子背面的亮度和前面的亮度是一致的,因為我們沒有對光照的強度進行衰減擦酌。你是對的俱诸!光照應(yīng)該是隨著距離越遠越弱。

衰減(Attenuation)

隨著距離的變遠光照強度減弱的過程我們稱之為衰減赊舶。一個簡單的方法是直接采用線性衰減:設(shè)定一個衰減比例睁搭,隨著距離減少強度。但是這種衰減不符合現(xiàn)實的情況笼平,現(xiàn)實情況是光照會在短距離之內(nèi)迅速衰減园骆,然后緩慢衰減直至消失。沒錯寓调,這更像是一種二次衰減模型锌唾。

幸運的是,有一些聰明的前輩高人已經(jīng)將這個衰減公式給計算出來了夺英。我們直接就能使用:

衰減公式

這里的d表示距離(distance)晌涕,是片元到光源的距離。公式中包含了3個常數(shù)因子痛悯,分別是Kc, Kl和Kq余黎。這三個因子分別是常數(shù)衰減指數(shù)、線性衰減指數(shù)载萌、二次項衰減指數(shù)惧财。

因為二次項的衰減會比前面的線性和常數(shù)衰減快很多,造成的結(jié)果就是在離光源近的地方會很亮扭仁,然后離開光源可缚,亮度迅速衰減,到一定程度后衰減又會減慢斋枢。整個過程看起來就是像是這個樣子:

衰減圖

3個衰減因子到底應(yīng)該選多少呢帘靡?

衰減因子取決于很多因素:環(huán)境、你期望光源覆蓋的范圍瓤帚、光的類型等等描姚。大多數(shù)情況下,這是一個經(jīng)驗和微調(diào)的問題戈次。下面一張表里給出了覆蓋范圍和衰減因子的取值關(guān)系轩勘,這些值是非常好的微調(diào)基準值。

范圍和衰減因子關(guān)系表

就像你看到的這樣怯邪,Kc永遠是1绊寻,Kl隨著覆蓋范圍增大變得非常小,而Kq變小的就更快了。有時間試試這些值澄步,對渲染場景的影響冰蘑。在本文中,我們選擇覆蓋范圍是50村缸。

實現(xiàn)點光源效果祠肥。光源的位置屬性,再往Light結(jié)構(gòu)中添加三個float變量表示3個不同的衰減因子梯皿,這些因子可以通過主函數(shù)設(shè)置仇箱。

struct Light{
    //vec3 direction;
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};

然后我們就可以在主函數(shù)中設(shè)置這些值了。對照上面的表东羹,我們的3個衰減因子分別設(shè)置為:1.0f, 0.09f, 0.032f剂桥。

lightingShader.setFloat("light.constant", 1.0f);
lightingShader.setFloat("light.linear", 0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);

將衰減值應(yīng)用到光照中去也非常簡單,只要計算出衰減值属提,然后乘上ambient渊额,diffuse和specular分量就行了。

先計算衰減值垒拢。我們要用到片元距離光源的距離旬迹,這就要用到GLSL內(nèi)置的length函數(shù)了,這個函數(shù)作用是計算一個向量的長度求类,我們把光源位置和片元位置做減法就可以得到這個向量奔垦。

float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
顯示效果

如果你顯示的結(jié)果不對,請下載源碼進行對比尸疆。

聚光燈(Spot Light)

最后一種常見的光源類型是聚光燈椿猎。聚光燈模型,模擬的是手電筒寿弱,探照燈之類可以把光匯聚到一個方向的光源犯眠。它用到了平行光和點光源的部分內(nèi)容,我們在設(shè)置聚光燈的時候症革,需要設(shè)置其位置和朝向筐咧,并且光照強度會隨著距離而減小。特別的地方是噪矛,聚光燈的光只會對某個方向上的有限圓錐角的物體有照亮效果量蕊,如下圖所示:

聚光燈
  • LightDir(光照方向):表示光源到片元的方向
  • SpotDir(聚光燈朝向):表示聚光燈前方的方向,也就是影響方向艇挨。
  • Phi ?:范圍角度残炮,所有在這個角度范圍之外的物體都不會被照亮
  • Theta θ:光照方向和聚光燈朝向的夾角,用來計算光照強度
實現(xiàn)一個手電筒

聚光燈需要位置缩滨、朝向和范圍角度势就,因此泉瞻,我們要在光源結(jié)構(gòu)體中添加這些成員。

struct Light {
    vec3  position;
    vec3  direction;
    float cutOff;
    ...
};  

設(shè)置這些值:

lightingShader.setVec3("light.position",camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));

可以看到苞冯,我們是用cos角度值來代替原本的角度袖牙,因為這樣比較簡單。我們可以直接計算光照方向和聚光燈方向的點積抱完,然后和這個數(shù)值進行比較從而得出該點是否接收光照這個結(jié)論贼陶。

現(xiàn)在刃泡,我們在片元著色器里計算片元和光源之間的方向與聚光燈朝向之間的夾角是否超過了照射范圍:

float theta = dot(lightDir, normalize(-light.direction));
if (theta > light.cutOff) { //在照射范圍內(nèi)
}
else
        FragColor = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);

注意巧娱,cos的值隨著角度變大而逐漸變小,所以判斷的方式是theta>light.cutOff表示在照射范圍內(nèi)烘贴。

編譯運行禁添,你會看到類似這樣的效果:

運行效果

如果不對,請下載源碼進行比對桨踪。

看上去有點假老翘,有沒有這感覺?

平滑邊緣

為了創(chuàng)建一個平滑的邊緣效果锻离,我們需要改變一下聚光燈的模型铺峭,模擬聚光燈的內(nèi)錐角和外錐角。計算方式也會有所改變汽纠。

假設(shè)內(nèi)錐角為?卫键,外錐角為γ, 光照角度為θ虱朵,我們的光照強度的計算公式就是:

計算公式

其中:?為(內(nèi)錐角-外錐角)的cos值莉炉。結(jié)果I 就表示當前片元的光照強度。

讓我們來看計算代碼:

//在光源結(jié)構(gòu)體中添加外錐角成員
struct Light{
...
float outerCutOff;
};

//聚光燈
float theta = dot(lightDir, normalize(-light.direction));   //計算片元角度的cos值
float epsilon = light.cutOff - light.outerCutOff;   //計算epsilon的值碴犬,用內(nèi)錐角的cos值減去外錐角的cos值
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);   //根據(jù)公式計算光照強度絮宁,并限制結(jié)果的范圍

diffuse *= intensity;
specular *= instensity;

強度被限制在0到1之間,這是必要的服协,因為theta-light.outerCutOff的值可能是負數(shù)绍昂。

最后,設(shè)置內(nèi)錐角度為12.5度偿荷,外錐角度為17.5度治专,設(shè)置代碼如下:

lightingShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
lightingShader.setFloat("light.outerCutOff", glm::cos(glm::radians(17.5f)));

編譯運行,如果運行沒問題遭顶,你所看到的結(jié)果應(yīng)該是這個樣子:

運行結(jié)果

如果你的顯示不正確张峰,歡迎參考源碼

總結(jié)

本章中棒旗,我們學習了3中光照模型喘批,分別是:方向光撩荣,點光源和聚光燈。方向光最簡單饶深,只有一個方向參數(shù)餐曹。點光源稍微復雜點,有位置和衰減度兩個參數(shù)敌厘。最復雜的是聚光燈台猴,不僅有方向、衰減度俱两,還有內(nèi)錐角和外錐角的區(qū)分饱狂。不過,功夫不負有心人宪彩,我們終于弄出點有趣的效果來了休讳。

下一篇
目錄
上一篇

參考資料

www.learnopengl.com(非常好的網(wǎng)站,歡迎學習)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尿孔,一起剝皮案震驚了整個濱河市俊柔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌活合,老刑警劉巖雏婶,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異白指,居然都是意外死亡留晚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門侵续,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倔丈,“玉大人,你說我怎么就攤上這事状蜗⌒栉澹” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵轧坎,是天一觀的道長宏邮。 經(jīng)常有香客問我,道長缸血,這世上最難降的妖魔是什么蜜氨? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮捎泻,結(jié)果婚禮上飒炎,老公的妹妹穿的比我還像新娘。我一直安慰自己笆豁,他們只是感情好郎汪,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布赤赊。 她就那樣靜靜地躺著,像睡著了一般煞赢。 火紅的嫁衣襯著肌膚如雪抛计。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天照筑,我揣著相機與錄音吹截,去河邊找鬼。 笑死凝危,一個胖子當著我的面吹牛波俄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播媒抠,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼弟断,長吁一口氣:“原來是場噩夢啊……” “哼咏花!你這毒婦竟也來了趴生?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昏翰,失蹤者是張志新(化名)和其女友劉穎苍匆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棚菊,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡浸踩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了统求。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片检碗。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖码邻,靈堂內(nèi)的尸體忽然破棺而出折剃,到底是詐尸還是另有隱情,我是刑警寧澤像屋,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布怕犁,位于F島的核電站,受9級特大地震影響己莺,放射性物質(zhì)發(fā)生泄漏奏甫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一凌受、第九天 我趴在偏房一處隱蔽的房頂上張望阵子。 院中可真熱鬧,春花似錦胜蛉、人聲如沸挠进。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈梳。三九已至杈湾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間攘须,已是汗流浹背漆撞。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留于宙,地道東北人浮驳。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像捞魁,于是被迫代替她去往敵國和親至会。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容