產(chǎn)品列表PRODUCTS LIST
一般來(lái)說(shuō),當存儲在幀緩沖(Framebuffer)中時(shí),亮度和顏色的值是默認被限制在0.0到1.0之間的。這個(gè)看起來(lái)無(wú)辜的語(yǔ)句使我們一直將亮度與顏色的值設置在這個(gè)范圍內,嘗試著(zhù)與場(chǎng)景契合。這樣是能夠運行的,也能給出還不錯的效果。但是如果我們遇上了一個(gè)特定的區域,其中有多個(gè)亮光源使這些數值總和超過(guò)了1.0,又會(huì )發(fā)生什么呢?答案是這些片段中超過(guò)1.0的亮度或者顏色值會(huì )被約束在1.0,從而導致場(chǎng)景混成一片,難以分辨:
這是由于大量片段的顏色值都非常接近1.0,在很大一個(gè)區域內每一個(gè)亮的片段都有相同的白色。這損失了很多的細節,使場(chǎng)景看起來(lái)非常假。
解決這個(gè)問(wèn)題的一個(gè)方案是減小光源的強度從而保證場(chǎng)景內沒(méi)有一個(gè)片段亮于1.0。然而這并不是一個(gè)好的方案,因為你需要使用不切實(shí)際的光照參數。一個(gè)更好的方案是讓顏色暫時(shí)超過(guò)1.0,然后將其轉換至0.0到1.0的區間內,從而防止損失細節。
顯示器被限制為只能顯示值為0.0到1.0間的顏色,但是在光照方程中卻沒(méi)有這個(gè)限制。通過(guò)使片段的顏色超過(guò)1.0,我們有了一個(gè)更大的顏色范圍,這也被稱(chēng)作HDR(High Dynamic Range, 高動(dòng)態(tài)范圍)。有了HDR,亮的東西可以變得非常亮,暗的東西可以變得非常暗,而且充滿(mǎn)細節。
HDR原本只是被運用在攝影上,攝影師對同一個(gè)場(chǎng)景采取不同曝光拍多張照片,捕捉大范圍的色彩值。這些圖片被合成為HDR圖片,從而綜合不同的曝光等級使得大范圍的細節可見(jiàn)??聪旅孢@個(gè)例子,左邊這張圖片在被光照亮的區域充滿(mǎn)細節,但是在黑暗的區域就什么都看不見(jiàn)了;但是右邊這張圖的高曝光卻可以讓之前看不出來(lái)的黑暗區域顯現出來(lái)。
這與我們眼睛工作的原理非常相似,也是HDR渲染的基礎。當光線(xiàn)很弱的啥時(shí)候,人眼會(huì )自動(dòng)調整從而使過(guò)暗和過(guò)亮的部分變得更清晰,就像人眼有一個(gè)能自動(dòng)根據場(chǎng)景亮度調整的自動(dòng)曝光滑塊。
HDR渲染和其很相似,我們允許用更大范圍的顏色值渲染從而獲取大范圍的黑暗與明亮的場(chǎng)景細節,zui后將所有HDR值轉換成在[0.0, 1.0]范圍的LDR(Low Dynamic Range,低動(dòng)態(tài)范圍)。轉換HDR值到LDR值得過(guò)程叫做色調映射(Tone Mapping),現在現存有很多的色調映射算法,這些算法致力于在轉換過(guò)程中保留盡可能多的HDR細節。這些色調映射算法經(jīng)常會(huì )包含一個(gè)選擇性?xún)A向黑暗或者明亮區域的參數。
在實(shí)時(shí)渲染中,HDR不僅允許我們超過(guò)LDR的范圍[0.0, 1.0]與保留更多的細節,同時(shí)還讓我們能夠根據光源的真實(shí)強度它的強度。比如太陽(yáng)有比閃光燈之類(lèi)的東西更高的強度,那么我們?yōu)槭裁床贿@樣子設置呢?(比如說(shuō)設置一個(gè)10.0的漫亮度) 這允許我們用更現實(shí)的光照參數恰當地配置一個(gè)場(chǎng)景的光照,而這在LDR渲染中是不能實(shí)現的,因為他們會(huì )被上限約束在1.0。
因為顯示器只能顯示在0.0到1.0范圍之內的顏色,我們肯定要做一些轉換從而使得當前的HDR顏色值符合顯示器的范圍。簡(jiǎn)單地取平均值重新轉換這些顏色值并不能很好的解決這個(gè)問(wèn)題,因為明亮的地方會(huì )顯得更加顯著(zhù)。我們能做的是用一個(gè)不同的方程與/或曲線(xiàn)來(lái)轉換這些HDR值到LDR值,從而給我們對于場(chǎng)景的亮度*掌控,這就是之前說(shuō)的色調變換,也是HDR渲染的zui終步驟。
在實(shí)現HDR渲染之前,我們首先需要一些防止顏色值在每一個(gè)片段著(zhù)色器運行后被限制約束的方法。當幀緩沖使用了一個(gè)標準化的定點(diǎn)格式(像GL_RGB)為其顏色緩沖的內部格式,OpenGL會(huì )在將這些值存入幀緩沖前自動(dòng)將其約束到0.0到1.0之間。這一操作對大部分幀緩沖格式都是成立的,除了專(zhuān)門(mén)用來(lái)存放被拓展范圍值的浮點(diǎn)格式。
當一個(gè)幀緩沖的顏色緩沖的內部格式被設定成了GL_RGB16F, GL_RGBA16F, GL_RGB32F 或者GL_RGBA32F時(shí),這些幀緩沖被叫做浮點(diǎn)幀緩沖(Floating Point Framebuffer),浮點(diǎn)幀緩沖可以存儲超過(guò)0.0到1.0范圍的浮點(diǎn)值,所以非常適合HDR渲染。
想要創(chuàng )建一個(gè)浮點(diǎn)幀緩沖,我們只需要改變顏色緩沖的內部格式參數就行了(注意GL_FLOAT參數):
[cpp] view plain copy
glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
默認的幀緩沖默認一個(gè)顏色分量只占用8位(bits)。當使用一個(gè)使用32位每顏色分量的浮點(diǎn)幀緩沖時(shí)(使用GL_RGB32F 或者GL_RGBA32F),我們需要四倍的內存來(lái)存儲這些顏色。所以除非你需要一個(gè)非常高的度,32位不是必須的,使用GLRGB16F就足夠了。
有了一個(gè)帶有浮點(diǎn)顏色緩沖的幀緩沖,我們可以放心渲染場(chǎng)景到這個(gè)幀緩沖中。在這個(gè)教程的例子當中,我們先渲染一個(gè)光照的場(chǎng)景到浮點(diǎn)幀緩沖中,之后再在一個(gè)鋪屏四邊形(Screen-filling Quad)上應用這個(gè)幀緩沖的顏色緩沖,代碼會(huì )是這樣子:
[cpp] view plain copy
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// [...] 渲染(光照的)場(chǎng)景
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 現在使用一個(gè)不同的著(zhù)色器將HDR顏色緩沖渲染至2D鋪屏四邊形上
hdrShader.Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture);
RenderQuad();
這里場(chǎng)景的顏色值存在一個(gè)可以包含任意顏色值的浮點(diǎn)顏色緩沖中,值可能是超過(guò)1.0的。這個(gè)簡(jiǎn)單的演示中,場(chǎng)景被創(chuàng )建為一個(gè)被拉伸的立方體通道和四個(gè)點(diǎn)光源,其中一個(gè)非常亮的在隧道的盡頭:
[cpp] view plain copy
std::vector
lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));
渲染至浮點(diǎn)幀緩沖和渲染至一個(gè)普通的幀緩沖是一樣的。新的東西就是這個(gè)的hdrShader的片段著(zhù)色器,用來(lái)渲染zui終擁有浮點(diǎn)顏色緩沖紋理的2D四邊形。我們來(lái)定義一個(gè)簡(jiǎn)單的直通片段著(zhù)色器(Pass-through Fragment Shader):
[cpp] view plain copy
#version 330 core
out vec4 color;
in vec2 TexCoords;
uniform sampler2D hdrBuffer;
void main()
{
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
color = vec4(hdrColor, 1.0);
}
這里我們直接采樣了浮點(diǎn)顏色緩沖并將其作為片段著(zhù)色器的輸出。然而,這個(gè)2D四邊形的輸出是被直接渲染到默認的幀緩沖中,導致所有片段著(zhù)色器的輸出值被約束在0.0到1.0間,盡管我們已經(jīng)有了一些存在浮點(diǎn)顏色紋理的值超過(guò)了1.0。
很明顯,在隧道盡頭的強光的值被約束在1.0,因為一大塊區域都是白色的,過(guò)程中超過(guò)1.0的地方損失了所有細節。因為我們直接轉換HDR值到LDR值,這就像我們根本就沒(méi)有應用HDR一樣。為了修復這個(gè)問(wèn)題我們需要做的是無(wú)損轉化所有浮點(diǎn)顏色值回0.0-1.0范圍中。我們需要應用到色調映射。
色調映射(Tone Mapping)是一個(gè)損失很小的轉換浮點(diǎn)顏色值至我們所需的LDR[0.0, 1.0]范圍內的過(guò)程,通常會(huì )伴有特定的風(fēng)格的色平衡(Stylistic Color Balance)。
zui簡(jiǎn)單的色調映射算法是Reinhard色調映射,它涉及到分散整個(gè)HDR顏色值到LDR顏色值上,所有的值都有對應。Reinhard色調映射算法平均得將所有亮度值分散到LDR上。我們將Reinhard色調映射應用到之前的片段著(zhù)色器上,并且為了更好的測量加上一個(gè)Gamma校正過(guò)濾(包括SRGB紋理的使用):
[cpp] view plain copy
void main()
{
const float gamma = 2.2;
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
// Reinhard色調映射
vec3 mapped = hdrColor / (hdrColor + vec3(1.0));
// Gamma校正
mapped = pow(mapped, vec3(1.0 / gamma));
color = vec4(mapped, 1.0);
}
有了Reinhard色調映射的應用,我們不再會(huì )在場(chǎng)景明亮的地方損失細節。當然,這個(gè)算法是傾向明亮的區域的,暗的區域會(huì )不那么精細也不那么有區分度。
現在你可以看到在隧道的盡頭木頭紋理變得可見(jiàn)了。用了這個(gè)非常簡(jiǎn)單地色調映射算法,我們可以合適的看到存在浮點(diǎn)幀緩沖中整個(gè)范圍的HDR值,給我們對于無(wú)損場(chǎng)景光照的控制。
另一個(gè)有趣的色調映射應用是曝光(Exposure)參數的使用。你可能還記得之前我們在介紹里講到的,HDR圖片包含在不同曝光等級的細節。如果我們有一個(gè)場(chǎng)景要展現日夜交替,我們當然會(huì )在白天使用低曝光,在夜間使用高曝光,就像人眼調節方式一樣。有了這個(gè)曝光參數,我們可以去設置可以同時(shí)在白天和夜晚不同光照條件工作的光照參數,我們只需要調整曝光參數就行了。
一個(gè)簡(jiǎn)單的曝光色調映射算法會(huì )像這樣:
[cpp] view plain copy
uniform float exposure;
void main()
{
const float gamma = 2.2;
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
// 曝光色調映射
vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);
// Gamma校正
mapped = pow(mapped, vec3(1.0 / gamma));
color = vec4(mapped, 1.0);
}
在這里我們將exposure定義為默認為1.0的uniform,從而允許我們更加設定我們是要注重黑暗還是明亮的區域的HDR顏色值。舉例來(lái)說(shuō),高曝光值會(huì )使隧道的黑暗部分顯示更多的細節,然而低曝光值會(huì )顯著(zhù)減少黑暗區域的細節,但允許我們看到更多明亮區域的細節。下面這組圖片展示了在不同曝光值下的通道:
這個(gè)圖片清晰地展示了HDR渲染的優(yōu)點(diǎn)。通過(guò)改變曝光等級,我們可以看見(jiàn)場(chǎng)景的很多細節,而這些細節可能在LDR渲染中都被丟失了。比如說(shuō)隧道盡頭,在正常曝光下木頭結構隱約可見(jiàn),但用低曝光木頭的花紋就可以清晰看見(jiàn)了。對于近處的木頭花紋來(lái)說(shuō),在高曝光下會(huì )能更好的看見(jiàn)。
zui后把實(shí)現的源代碼給讀者展示如下,首先展示的是頂點(diǎn)著(zhù)色器代碼:
[cpp] view plain copy
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoords;
out vec2 TexCoords;
void main()
{
gl_Position = vec4(position, 1.0f);
TexCoords = texCoords;
}
片段著(zhù)色器代碼如下所示:
[cpp] view plain copy
#version 330 core
out vec4 color;
in vec2 TexCoords;
uniform sampler2D hdrBuffer;
uniform float exposure;
uniform bool hdr;
void main()
{
const float gamma = 2.2;
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
// reinhard
// vec3 result = hdrColor / (hdrColor + vec3(1.0));
// exposure
vec3 result = vec3(1.0) - exp(-hdrColor * exposure);
// also gamma correct while we're at it
result = pow(result, vec3(1.0 / gamma));
color = vec4(result, 1.0f);
}
在這里展示的兩個(gè)色調映射算法僅僅是大量(更先進(jìn))的色調映射算法中的一小部分,這些算法各有長(cháng)短.一些色調映射算法傾向于特定的某種顏色/強度,也有一些算法同時(shí)顯示低于高曝光顏色從而能夠顯示更加多彩和精細的圖像。也有一些技巧被稱(chēng)作自動(dòng)曝光調整(Automatic Exposure Adjustment)或者叫人眼適應(Eye Adaptation)技術(shù),它能夠檢測前一幀場(chǎng)景的亮度并且緩慢調整曝光參數模仿人眼使得場(chǎng)景在黑暗區域逐漸變亮或者在明亮區域逐漸變暗,
HDR渲染的真正優(yōu)點(diǎn)在龐大和復雜的場(chǎng)景中應用復雜光照算法會(huì )被顯示出來(lái),但是出于教學(xué)目的創(chuàng )建這樣復雜的演示場(chǎng)景是很困難的,這個(gè)教程用的場(chǎng)景是很小的,而且缺乏細節。但是如此簡(jiǎn)單的演示也是能夠顯示出HDR渲染的一些優(yōu)點(diǎn):在明亮和黑暗區域無(wú)細節損失,因為它們可以由色調映射重新獲??;多個(gè)光照的疊加不會(huì )導致亮度被約束的區域;光照可以被設定為他們原來(lái)的亮度而不是被LDR值限定。而且,HDR渲染也使一些有趣的效果更加可行和真實(shí); 其中一個(gè)效果叫做泛光(Bloom)
“文章為轉載,如有版權爭議請管理員,我們將刪除文章!"