OpenGL 圖形庫(kù)的使用(三十三)—— 高級(jí)光照之Gamma校正Gamma Correction

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.01.19

前言

OpenGL 圖形庫(kù)項(xiàng)目中一直也沒用過,最近也想學(xué)著使用這個(gè)圖形庫(kù),感覺還是很有意思,也就自然想著好好的總結(jié)一下,希望對(duì)大家能有所幫助。下面內(nèi)容來自歡迎來到OpenGL的世界
1. OpenGL 圖形庫(kù)使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫(kù)使用(二) —— 渲染模式、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫(kù)使用(三) —— 著色器、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫(kù)使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫(kù)使用(五) —— 紋理
6. OpenGL 圖形庫(kù)使用(六) —— 變換
7. OpenGL 圖形庫(kù)的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫(kù)的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫(kù)的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫(kù)的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫(kù)的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫(kù)的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫(kù)的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫(kù)的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫(kù)的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫(kù)的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫(kù)的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫(kù)的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫(kù)的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫(kù)的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫(kù)的使用(二十一)—— 高級(jí)OpenGL之深度測(cè)試
22. OpenGL 圖形庫(kù)的使用(二十二)—— 高級(jí)OpenGL之模板測(cè)試Stencil testing
23. OpenGL 圖形庫(kù)的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫(kù)的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
25. OpenGL 圖形庫(kù)的使用(二十五)—— 高級(jí)OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫(kù)的使用(二十六)—— 高級(jí)OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫(kù)的使用(二十七)—— 高級(jí)OpenGL之高級(jí)數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫(kù)的使用(二十八)—— 高級(jí)OpenGL之高級(jí)GLSL Advanced GLSL
29. OpenGL 圖形庫(kù)的使用(二十九)—— 高級(jí)OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫(kù)的使用(三十)—— 高級(jí)OpenGL之實(shí)例化Instancing
31. OpenGL 圖形庫(kù)的使用(三十一)—— 高級(jí)OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫(kù)的使用(三十二)—— 高級(jí)光照之高級(jí)光照Advanced Lighting

Gamma校正前注

當(dāng)我們計(jì)算出場(chǎng)景中所有像素的最終顏色以后,我們就必須把它們顯示在監(jiān)視器上。過去,大多數(shù)監(jiān)視器是陰極射線管顯示器(CRT)。這些監(jiān)視器有一個(gè)物理特性就是兩倍的輸入電壓產(chǎn)生的不是兩倍的亮度。輸入電壓產(chǎn)生約為輸入電壓的2.2次冪的亮度,這叫做監(jiān)視器Gamma。

Gamma也叫灰度系數(shù),每種顯示設(shè)備都有自己的Gamma值,都不相同,有一個(gè)公式:設(shè)備輸出亮度 = 電壓的Gamma次冪,任何設(shè)備Gamma基本上都不會(huì)等于1,等于1是一種理想的線性狀態(tài),這種理想狀態(tài)是:如果電壓和亮度都是在0到1的區(qū)間,那么多少電壓就等于多少亮度。對(duì)于CRT,Gamma通常為2.2,因而,輸出亮度 = 輸入電壓的2.2次冪,你可以從本節(jié)第二張圖中看到Gamma2.2實(shí)際顯示出來的總會(huì)比預(yù)期暗,相反Gamma0.45就會(huì)比理想預(yù)期亮,如果你講Gamma0.45疊加到Gamma2.2的顯示設(shè)備上,便會(huì)對(duì)偏暗的顯示效果做到校正,這個(gè)簡(jiǎn)單的思路就是本節(jié)的核心。

人類所感知的亮度恰好和CRT所顯示出來相似的指數(shù)關(guān)系非常匹配。為了更好的理解所有含義,請(qǐng)看下面的圖片:

第一行是人眼所感知到的正常的灰階,亮度要增加一倍(比如從0.1到0.2)你才會(huì)感覺比原來變亮了一倍(譯注:這里的意思是說比如一個(gè)東西的亮度0.3,讓人感覺它比原來變亮一倍,那么現(xiàn)在這個(gè)亮度應(yīng)該成為0.6,而不是0.4,也就是說人眼感知到的亮度的變化并非線性均勻分布的。問題的關(guān)鍵在于這樣的一倍相當(dāng)于一個(gè)亮度級(jí),例如假設(shè)0.1、0.2、0.4、0.8是我們定義的四個(gè)亮度級(jí)別,在0.1和0.2之間人眼只能識(shí)別出0.15這個(gè)中間級(jí),而雖然0.4到0.8之間的差距更大,這個(gè)區(qū)間人眼也只能識(shí)別出一個(gè)顏色)。然而,當(dāng)我們談?wù)摴獾奈锢砹炼?,比如光源發(fā)射光子的數(shù)量的時(shí)候,底部(第二行)的灰階顯示出的才是物理世界真實(shí)的亮度。如底部的灰階顯示,亮度加倍時(shí)返回的也是真實(shí)的物理亮度(譯注:這里亮度是指光子數(shù)量和正相關(guān)的亮度,即物理亮度,前面討論的是人的感知亮度;物理亮度和感知亮度的區(qū)別在于,物理亮度基于光子數(shù)量,感知亮度基于人的感覺,比如第二個(gè)灰階里亮度0.1的光子數(shù)量是0.2的二分之一),但是由于這與我們的眼睛感知亮度不完全一致(對(duì)比較暗的顏色變化更敏感),所以它看起來有差異。

因?yàn)槿搜劭吹筋伾牧炼雀鼉A向于頂部的灰階,監(jiān)視器使用的也是一種指數(shù)關(guān)系(電壓的2.2次冪),所以物理亮度通過監(jiān)視器能夠被映射到頂部的非線性亮度;因此看起來效果不錯(cuò)(譯注:CRT亮度是是電壓的2.2次冪而人眼相當(dāng)于2次冪,因此CRT這個(gè)缺陷正好能滿足人的需要)。

監(jiān)視器的這個(gè)非線性映射的確可以讓亮度在我們眼中看起來更好,但當(dāng)渲染圖像時(shí),會(huì)產(chǎn)生一個(gè)問題:我們?cè)趹?yīng)用中配置的亮度和顏色是基于監(jiān)視器所看到的,這樣所有的配置實(shí)際上是非線性的亮度/顏色配置。請(qǐng)看下圖:

點(diǎn)線代表線性顏色/亮度值(譯注:這表示的是理想狀態(tài),Gamma為1),實(shí)線代表監(jiān)視器顯示的顏色。如果我們把一個(gè)點(diǎn)線線性的顏色翻一倍,結(jié)果就是這個(gè)值的兩倍。比如,光的顏色向量Lˉ=(0.5,0.0,0.0)代表的是暗紅色。如果我們?cè)诰€性空間中把它翻倍,就會(huì)變成(1.0,0.0,0.0),就像你在圖中看到的那樣。然而,由于我們定義的顏色仍然需要輸出的監(jiān)視器上,監(jiān)視器上顯示的實(shí)際顏色就會(huì)是(0.218,0.0,0.0)。在這兒?jiǎn)栴}就出現(xiàn)了:當(dāng)我們將理想中直線上的那個(gè)暗紅色翻一倍時(shí),在監(jiān)視器上實(shí)際上亮度翻了4.5倍以上!

直到現(xiàn)在,我們還一直假設(shè)我們所有的工作都是在線性空間中進(jìn)行的(譯注:Gamma為1),但最終還是要把所有的顏色輸出到監(jiān)視器上,所以我們配置的所有顏色和光照變量從物理角度來看都是不正確的,在我們的監(jiān)視器上很少能夠正確地顯示。出于這個(gè)原因,我們(以及藝術(shù)家)通常將光照值設(shè)置得比本來更亮一些(由于監(jiān)視器會(huì)將其亮度顯示的更暗一些),如果不是這樣,在線性空間里計(jì)算出來的光照就會(huì)不正確。同時(shí),還要記住,監(jiān)視器所顯示出來的圖像和線性圖像的最小亮度是相同的,它們最大的亮度也是相同的;只是中間亮度部分會(huì)被壓暗。

因?yàn)樗兄虚g亮度都是線性空間計(jì)算出來的(譯注:計(jì)算的時(shí)候假設(shè)Gamma為1)監(jiān)視器顯以后,實(shí)際上都會(huì)不正確。當(dāng)使用更高級(jí)的光照算法時(shí),這個(gè)問題會(huì)變得越來越明顯,你可以看看下圖:


Gamma校正

Gamma校正(Gamma Correction)的思路是在最終的顏色輸出上應(yīng)用監(jiān)視器Gamma的倒數(shù)。回頭看前面的Gamma曲線圖,你會(huì)有一個(gè)短劃線,它是監(jiān)視器Gamma曲線的翻轉(zhuǎn)曲線。我們?cè)陬伾@示到監(jiān)視器的時(shí)候把每個(gè)顏色輸出都加上這個(gè)翻轉(zhuǎn)的Gamma曲線,這樣應(yīng)用了監(jiān)視器Gamma以后最終的顏色將會(huì)變?yōu)榫€性的。我們所得到的中間色調(diào)就會(huì)更亮,所以雖然監(jiān)視器使它們變暗,但是我們又將其平衡回來了。

我們來看另一個(gè)例子。還是那個(gè)暗紅色(0.5,0.0,0.0)。在將顏色顯示到監(jiān)視器之前,我們先對(duì)顏色應(yīng)用Gamma校正曲線。線性的顏色顯示在監(jiān)視器上相當(dāng)于降低了2.2次冪的亮度,所以倒數(shù)就是1/2.2次冪。Gamma校正后的暗紅色就會(huì)成為(0.5,0.0,0.0)1/2.2 = (0.5,0.0,0.0)0.45 = (0.73,0.0,0.0)。校正后的顏色接著被發(fā)送給監(jiān)視器,最終顯示出來的顏色是(0.73,0.0,0.0)2.2 = (0.5,0.0,0.0)。你會(huì)發(fā)現(xiàn)使用了Gamma校正,監(jiān)視器最終會(huì)顯示出我們?cè)趹?yīng)用中設(shè)置的那種線性的顏色。

2.2通常是是大多數(shù)顯示設(shè)備的大概平均gamma值。基于gamma2.2的顏色空間叫做sRGB顏色空間。每個(gè)監(jiān)視器的gamma曲線都有所不同,但是gamma2.2在大多數(shù)監(jiān)視器上表現(xiàn)都不錯(cuò)。出于這個(gè)原因,游戲經(jīng)常都會(huì)為玩家提供改變游戲gamma設(shè)置的選項(xiàng),以適應(yīng)每個(gè)監(jiān)視器(譯注:現(xiàn)在Gamma2.2相當(dāng)于一個(gè)標(biāo)準(zhǔn),后文中你會(huì)看到。但現(xiàn)在你可能會(huì)問,前面不是說Gamma2.2看起來不是正好適合人眼么,為何還需要校正。這是因?yàn)槟阍诔绦蛑性O(shè)置的顏色,比如光照都是基于線性Gamma,即Gamma1,所以你理想中的亮度和實(shí)際表達(dá)出的不一樣,如果要表達(dá)出你理想中的亮度就要對(duì)這個(gè)光照進(jìn)行校正)。

有兩種在你的場(chǎng)景中應(yīng)用gamma校正的方式:

使用OpenGL內(nèi)建的sRGB幀緩沖。 自己在像素著色器中進(jìn)行g(shù)amma校正。 第一個(gè)選項(xiàng)也許是最簡(jiǎn)單的方式,但是我們也會(huì)喪失一些控制權(quán)。開啟GL_FRAMEBUFFER_SRGB,可以告訴OpenGL每個(gè)后續(xù)的繪制命令里,在顏色儲(chǔ)存到顏色緩沖之前先校正sRGB顏色。sRGB這個(gè)顏色空間大致對(duì)應(yīng)于gamma2.2,它也是家用設(shè)備的一個(gè)標(biāo)準(zhǔn)。開啟GL_FRAMEBUFFER_SRGB以后,每次像素著色器運(yùn)行后續(xù)幀緩沖,OpenGL將自動(dòng)執(zhí)行g(shù)amma校正,包括默認(rèn)幀緩沖。

開啟GL_FRAMEBUFFER_SRGB簡(jiǎn)單的調(diào)用glEnable就行:

glEnable(GL_FRAMEBUFFER_SRGB);

自此,你渲染的圖像就被進(jìn)行g(shù)amma校正處理,你不需要做任何事情硬件就幫你處理了。有時(shí)候,你應(yīng)該記得這個(gè)建議:gamma校正將把線性顏色空間轉(zhuǎn)變?yōu)榉蔷€性空間,所以在最后一步進(jìn)行g(shù)amma校正是極其重要的。如果你在最后輸出之前就進(jìn)行g(shù)amma校正,所有的后續(xù)操作都是在操作不正確的顏色值。例如,如果你使用多個(gè)幀緩沖,你可能打算讓兩個(gè)幀緩沖之間傳遞的中間結(jié)果仍然保持線性空間顏色,只是給發(fā)送給監(jiān)視器的最后的那個(gè)幀緩沖應(yīng)用gamma校正。

第二個(gè)方法稍微復(fù)雜點(diǎn),但同時(shí)也是我們對(duì)gamma操作有完全的控制權(quán)。我們?cè)诿總€(gè)相關(guān)像素著色器運(yùn)行的最后應(yīng)用gamma校正,所以在發(fā)送到幀緩沖前,顏色就被校正了。

void main()
{
    // do super fancy lighting 
    [...]
    // apply gamma correction
    float gamma = 2.2;
    fragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}

最后一行代碼,將fragColor的每個(gè)顏色元素應(yīng)用有一個(gè)1.0/gamma的冪運(yùn)算,校正像素著色器的顏色輸出。

這個(gè)方法有個(gè)問題就是為了保持一致,你必須在像素著色器里加上這個(gè)gamma校正,所以如果你有很多像素著色器,它們可能分別用于不同物體,那么你就必須在每個(gè)著色器里都加上gamma校正了。一個(gè)更簡(jiǎn)單的方案是在你的渲染循環(huán)中引入后處理階段,在后處理四邊形上應(yīng)用gamma校正,這樣你只要做一次就好了。

這些單行代碼代表了gamma校正的實(shí)現(xiàn)。不太令人印象深刻,但當(dāng)你進(jìn)行g(shù)amma校正的時(shí)候有一些額外的事情別忘了考慮。

1. sRGB紋理

因?yàn)楸O(jiān)視器總是在sRGB空間中顯示應(yīng)用了gamma的顏色,無(wú)論什么時(shí)候當(dāng)你在計(jì)算機(jī)上繪制、編輯或者畫出一個(gè)圖片的時(shí)候,你所選的顏色都是根據(jù)你在監(jiān)視器上看到的那種。這實(shí)際意味著所有你創(chuàng)建或編輯的圖片并不是在線性空間,而是在sRGB空間中(譯注:sRGB空間定義的gamma接近于2.2),假如在你的屏幕上對(duì)暗紅色翻一倍,便是根據(jù)你所感知到的亮度進(jìn)行的,并不等于將紅色元素加倍。

結(jié)果就是紋理編輯者,所創(chuàng)建的所有紋理都是在sRGB空間中的紋理,所以如果我們?cè)阡秩緫?yīng)用中使用這些紋理,我們必須考慮到這點(diǎn)。在我們應(yīng)用gamma校正之前,這不是個(gè)問題,因?yàn)榧y理在sRGB空間創(chuàng)建和展示,同樣我們還是在sRGB空間中使用,從而不必gamma校正紋理顯示也沒問題。然而,現(xiàn)在我們是把所有東西都放在線性空間中展示的,紋理顏色就會(huì)變壞,如下圖展示的那樣:

紋理圖像實(shí)在太亮了,發(fā)生這種情況是因?yàn)椋鼈儗?shí)際上進(jìn)行了兩次gamma校正!想一想,當(dāng)我們基于監(jiān)視器上看到的情況創(chuàng)建一個(gè)圖像,我們就已經(jīng)對(duì)顏色值進(jìn)行了gamma校正,所以再次顯示在監(jiān)視器上就沒錯(cuò)。由于我們?cè)阡秩局杏诌M(jìn)行了一次gamma校正,圖片就實(shí)在太亮了。

為了修復(fù)這個(gè)問題,我們得確保紋理制作者是在線性空間中進(jìn)行創(chuàng)作的。但是,由于大多數(shù)紋理制作者并不知道什么是gamma校正,并且在sRGB空間中進(jìn)行創(chuàng)作更簡(jiǎn)單,這也許不是一個(gè)好辦法。

另一個(gè)解決方案是重校,或把這些sRGB紋理在進(jìn)行任何顏色值的計(jì)算前變回線性空間。我們可以這樣做:

float gamma = 2.2;
vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));

為每個(gè)sRGB空間的紋理做這件事非常煩人。幸好,OpenGL給我們提供了另一個(gè)方案來解決我們的麻煩,這就是GL_SRGB和GL_SRGB_ALPHA內(nèi)部紋理格式。

如果我們?cè)贠penGL中創(chuàng)建了一個(gè)紋理,把它指定為以上兩種sRGB紋理格式其中之一,OpenGL將自動(dòng)把顏色校正到線性空間中,這樣我們所使用的所有顏色值都是在線性空間中的了。我們可以這樣把一個(gè)紋理指定為一個(gè)sRGB紋理:

glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

如果你還打算在你的紋理中引入alpha元素,必究必須將紋理的內(nèi)部格式指定為GL_SRGB_ALPHA。

因?yàn)椴皇撬屑y理都是在sRGB空間中的所以當(dāng)你把紋理指定為sRGB紋理時(shí)要格外小心。比如diffuse紋理,這種為物體上色的紋理幾乎都是在sRGB空間中的。而為了獲取光照參數(shù)的紋理,像specular貼圖和法線貼圖幾乎都在線性空間中,所以如果你把它們也配置為sRGB紋理的話,光照就壞掉了。指定sRGB紋理時(shí)要當(dāng)心。

將diffuse紋理定義為sRGB紋理之后,你將獲得你所期望的視覺輸出,但這次每個(gè)物體都會(huì)只進(jìn)行一次gamma校正。


衰減

在使用了gamma校正之后,另一個(gè)不同之處是光照衰減(Attenuation)。真實(shí)的物理世界中,光照的衰減和光源的距離的平方成反比。

float attenuation = 1.0 / (distance * distance);

然而,當(dāng)我們使用這個(gè)衰減公式的時(shí)候,衰減效果總是過于強(qiáng)烈,光只能照亮一小圈,看起來并不真實(shí)。出于這個(gè)原因,我們使用在基本光照教程中所討論的那種衰減方程,它給了我們更大的控制權(quán),此外我們還可以使用雙曲線函數(shù):

float attenuation = 1.0 / distance;

雙曲線比使用二次函數(shù)變體在不用gamma校正的時(shí)候看起來更真實(shí),不過但我們開啟gamma校正以后線性衰減看起來太弱了,符合物理的二次函數(shù)突然出現(xiàn)了更好的效果。下圖顯示了其中的不同:

這種差異產(chǎn)生的原因是,光的衰減方程改變了亮度值,而且屏幕上顯示出來的也不是線性空間,在監(jiān)視器上效果最好的衰減方程,并不是符合物理的。想想平方衰減方程,如果我們使用這個(gè)方程,而且不進(jìn)行g(shù)amma校正,顯示在監(jiān)視器上的衰減方程實(shí)際上將變成(1.0/distance2)2.2。若不進(jìn)行g(shù)amma校正,將產(chǎn)生更強(qiáng)烈的衰減。這也解釋了為什么雙曲線不用gamma校正時(shí)看起來更真實(shí),因?yàn)樗鼘?shí)際變成了(1.0/distance)2.2=1.0/distance2.2。這和物理公式是很相似的。

我們?cè)诨A(chǔ)光照教程中討論的更高級(jí)的那個(gè)衰減方程在有g(shù)amma校正的場(chǎng)景中也仍然有用,因?yàn)樗梢宰屛覀儗?duì)衰減擁有更多準(zhǔn)確的控制權(quán)(不過,在進(jìn)行g(shù)amma校正的場(chǎng)景中當(dāng)然需要不同的參數(shù))。

我創(chuàng)建的這個(gè)簡(jiǎn)單的demo場(chǎng)景,你可以在這里找到源碼以及頂點(diǎn)和像素著色器。按下空格就能在有g(shù)amma校正和無(wú)gamma校正的場(chǎng)景進(jìn)行切換,兩個(gè)場(chǎng)景使用的是相同的紋理和衰減。這不是效果最好的demo,不過它能展示出如何應(yīng)用所有這些技術(shù)。

總而言之,gamma校正使你可以在線性空間中進(jìn)行操作。因?yàn)榫€性空間更符合物理世界,大多數(shù)物理公式現(xiàn)在都可以獲得較好效果,比如真實(shí)的光的衰減。你的光照越真實(shí),使用gamma校正獲得漂亮的效果就越容易。這也正是為什么當(dāng)引進(jìn)gamma校正時(shí),建議只去調(diào)整光照參數(shù)的原因。


附加資源

  • cambridgeincolour.com:更多關(guān)于gamma和gamma校正的內(nèi)容。
  • wolfire.com: David Rosen關(guān)于在渲染領(lǐng)域使用gamma校正的好處。
  • renderwonk.com: 一些額外的實(shí)踐上的思考。

后記

本篇已結(jié)束,下一篇關(guān)于陰影。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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