干貨| 學習 HDR 和 Bloom 技術
HDR 和 Bloom 技術
面對一項新技術,我們首先要解決的一個問題是,我們?yōu)槭惨盟?,它有什么好處?在真實世界中,我們的光照強度范圍非常廣,近乎是無限的。
但在計算機中,我們只能把強度范圍壓縮到[0,1]之間,這對我們來說就非常不公平,憑什么像太陽光那種比手電筒要強上幾十萬倍的強度和手電筒一樣要被限制到1,這太扯淡了。要是手電筒是1的話,那么太陽光就是要幾十萬,這才能有接近真實世界的效果。
于是,HDR就粉墨登場了。
HDR(High Dynamic Range,高動態(tài)范圍)技術,讓我們能用超過1.0的數據表示顏色值。我們可以把顏色的格式設置成16位浮點數或者32位浮點數的方式來擴大顏色值的范圍。
到目前為止,我們用的都是LDR(低動態(tài)范圍),所以我們所有的渲染都被限制了。
HDR技術對我們來說非常有用,舉個簡單的例子,如果場景中有很多光源,每個都照在同一個物體上,這個物體的亮度很快就會超過1.0。
如果我們強制將它限制到1.0,我們就會失去這個物體上的細節(jié)信息,例如紋理狀況。所以,為了配合HDR,我們還需要獨特的色調映射(tone mapping)算法來有選擇地顯示物體高光部分的細節(jié),低光部分的細節(jié),還是都顯示,達到類似這樣的效果:

隨著曝光度的增加,原本場景中高光部分(窗戶玻璃)的細節(jié)逐漸消失,低光部分(樓梯扶手背部)的細節(jié)逐漸顯現(xiàn)。這就是色調映射算法的威力。
Bloom,中文名叫泛光(或者眩光,怎么順怎么來),是用來模擬光源那種發(fā)光或發(fā)熱感覺的技術。舉個例子:

有Bloom技術的支持,場景的顯示效果提升了一個檔次。
發(fā)光發(fā)熱就是要有這種效果,這種一眼看上去就是光源的效果,不像左邊的圖那么干巴巴的,像是沒電了一樣。
之所以把Bloom和HDR放在一起講,是因為很多人都把HDR和Bloom搞混了,認為他們就是一個東西,其實不然。我可以只使用HDR來渲染場景,也可以只使用Bloom來渲染,兩者是互相獨立互不影響的。當然,只使用一種技術產生的效果不好,這個回頭再說。
這次我們會先創(chuàng)建一個只使用HDR的場景,然后創(chuàng)建一個將HDR和Bloom融合起來的場景看效果。
使用HDR
要使用HDR非常簡單,我們只需要將幀緩存的顏色緩存格式設置成16或者32位浮點數就行。
這樣,當我們渲染場景到幀緩存時,OpenGL就不會把顏色值截斷到1.0,我們也就保留了場景的HDR信息。
你可以設置默認幀緩存的顏色緩存,也可以像我一樣新建一個幀緩存,附加一個16位浮點數格式的顏色緩存。當然了,推薦使用自己新建幀緩存,后面我們有大用。具體的設置代碼你可以參考這里:
//顏色緩存
unsigned int colorBuffer;
glGenTextures(1, &colorBuffer);
glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//深度緩存
unsigned int rboDepth;
glGenRenderbuffers(1, &rboDepth);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
//幀緩存
unsigned int hdrFBO;
glGenFramebuffers(1, &hdrFBO);
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "幀緩存未初始化完畢!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
上述代碼中,需要重點關注的只有一行,就是
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);。
你可以看到,我們對紋理的內部格式做了一些小改動,把原本我們熟悉的GL_RGBA改成了GL_RGBA16F,這就是告訴OpenGL我們的紋理內部格式要16位浮點數,你不要把它截斷了,我有大用。就是這么簡單!
構造一個場景
為了看看HDR對場景的影響,我們先要來創(chuàng)造一個可以看出不同的場景。
這里,我們假想自己處于一個隧道之中,隧道一端有一盞非常亮的燈(亮度大約是200),然后,在隧道里面零星地放著幾盞小燈,這些燈遠沒有隧道盡頭的燈亮(亮度大約是0.1),我們就在隧道的另一端看這個隧道的樣子。
要模擬隧道,我們可以把一個標準的立方體在某一個軸(比如說z軸)上進行放大,再設置一些光源的位置和顏色:
//光源信息
//位置
std::vector<glm::vec3> lightPositions;
lightPositions.push_back(glm::vec3(0.0f, 0.0f, 49.5f)); // 后面的光
lightPositions.push_back(glm::vec3(-1.4f, -1.9f, 9.0f));
lightPositions.push_back(glm::vec3(0.0f, -1.8f, 4.0f));
lightPositions.push_back(glm::vec3(0.8f, -1.7f, 6.0f));
//顏色
std::vector<glm::vec3> lightColors;
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));
完成之后,添加一些代碼,將場景渲染到之前我們創(chuàng)建的幀緩存上,然后顯示出來,代碼結構大概是這個樣子:
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (GLfloat)SCR_WIDTH / (GLfloat)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
shader.use();
shader.setMat4("projection", glm::value_ptr(projection));
shader.setMat4("view", glm::value_ptr(view));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
for (int i = 0; i < lightPositions.size(); ++i) {
shader.setVec3("lights[" + std::to_string(i) + "].Position", lightPositions[i]);
shader.setVec3("lights[" + std::to_string(i) + "].Color", lightColors[i]);
}
shader.setVec3("viewPos", camera.Position);
//渲染隧道
glm::mat4 model = glm::mat4();
model = glm::translate(model, glm::vec3(0.0f, 0.0f, 25.0));
model = glm::scale(model, glm::vec3(2.5f, 2.5f, 27.5f));
shader.setMat4("model", glm::value_ptr(model));
shader.setInt("inverse_normals", true);
renderCube();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
//2、渲染到四邊形中
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
hdrShader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, colorBuffer);
renderQuad();
沒啥好說的,代碼都非常直觀,接下來是著色器部分。頂點著色器非常簡單,只需要接收兩個值的輸入,再將這兩個值輸出就行了,這兩個值分別是位置和紋理坐標。
重點在片元著色器!片元著色器的框架是這樣:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D hdrBuffer;
//一些設置
void main()
{
const float gamma = 2.2;
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;
//一些其他的操作
...
vec3 result = pow(hdrColor, vec3(1.0 / gamma));
FragColor = vec4(result, 1.0);
}
這是一個簡單的片元著色器,非常簡單。說實話,你可以直接用這個著色器看看沒有經過特殊的色調映射算法時顯示的效果,效果也不會差。
這里要注意的是,在計算顏色的時候,我們運用了gamma校正。這就要求我們必須在加載圖片的時候指定SRGB格式,然后,在渲染場景時使用二次項的衰減系數。
接下來,我們來到HDR技術最重要的一個環(huán)節(jié):色調映射(tone mapping)。
色調映射(tone Mapping)
對HDR技術來說,最重要的就是色調映射算法。
當我們有了一個場景的HDR信息后,我們就無可避免地面臨這樣一個選擇,是更多地表現(xiàn)高光部分的細節(jié),還是低光部分的細節(jié),或者是兩個部分細節(jié)都表現(xiàn)出來?
你可能覺得,這還用想嗎,當然是兩個部分的細節(jié)都要。這個你還真不能急,因為如果你把兩個部分的細節(jié)都顯示出來了,整個場景就會顯的非常不真實,有一種置身魔法世界的玄幻感,如果你要顯示的是人的話,可能會有一種頂著光環(huán)的上帝的既視感,很詭異。
所以,我們基本上都是在高光和低光之間做出選擇,這就衍生出我們最常用的一個映射算法:曝光度運算算法。

上面這個公式就是算法計算公式,其中hdr表示我們得到的hdr顏色信息,1.0表示LDR的亮度上限值,e是自然底數。將這個公式翻譯成代碼就是:
result = vec3(1.0) - exp(-hdrColor * exposure);
加上這行核心代碼之后,就可以運行了。且慢動手,我們還沒對這個exposure變量賦值呢。為了方便觀察不同exposure值的效果,我們要將這個變量設置成uniform,然后main文件中添加按鍵控制功能,具體的代碼不用我多說,相信各位都已經非常熟悉了。
好了,我們來運行一下看看效果:

可以看到,隨著曝光度越來越低,高光部分的細節(jié)越來越清晰,當然,低光部分的細節(jié)就越來越模糊了。如果這是一個真實的游戲場景,這個效果會更加震撼!
Bloom(泛光)
要實現(xiàn)Bloom特效,我們首先就要知道需要在哪些地方實現(xiàn),換句話說,我們需要知道光源的位置。這個步驟,配合上HDR技術,我們可以非常容易的實現(xiàn)。
通常,光源都是場景中最亮的東西,我們可以把它設置成一個大于1.0的數值,然后,在渲染場景的時候,在片元著色器中添加判斷:如果當前片元的顏色值大于1.0,我們就認為它是光源,將其渲染出來;如果不是,則把它的顏色設置成黑色。
這樣,我們就能得到一個張只有光源的渲染圖,接著就可以通過這張圖來實現(xiàn)模糊效果進而實現(xiàn)Bloom特效。
高斯模糊
實現(xiàn)Bloom特效中最難也是最重要的一個步驟,就是對光源場景進行高斯模糊。之前我們也實現(xiàn)過高斯模糊效果,是在幀緩存的文章中,我們使用了一個核矩陣來實現(xiàn)整個場景的模糊效果,但是這種模糊效果太low了,這次我們會用另一種方法,叫做雙通道高斯模糊(two-pass Gaussian blur)。
從模糊的原理角度來講,所有的模糊都基于這樣一點事實:當前片元的最終顏色值是當前片元的顏色以及周圍的顏色共同作用的結果。各種模糊效果不同點僅僅是所有這些起作用的片元對最終顏色值起多大的作用而已。
在幀緩存一文中,我們認為,當前片元顏色權重為4/16=0.25,與之相鄰的四個片元顏色的權重為2/16=0.125,角落里的四個片元是1/16=0.0625。將這些顏色值合并起來之后就是最終的顏色值。雙通道高斯模糊采樣的權重與計算方式和這個不同,來看下面這張圖:

我們認為,當前片元顏色值是由橫豎兩條線上的片元顏色值決定的,其權重由近到遠分別是:0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162。并且,為了得到更好的模糊效果,我們會對同一張圖進行不止一次的雙通道高斯模糊操作。
好,原理的內容已經弄清了,下面就實現(xiàn)這個效果。
實現(xiàn)Bloom特效
先來整理一下實現(xiàn)的步驟:
使用兩個幀緩存或者用給一個幀緩存附加兩個顏色緩存用來分別渲染場景和光源。 對光源圖進行5次雙通道高斯模糊操作,將結果保存到一個新的幀緩存中 將場景和經過高斯模糊操作的光源圖進行合并 對合并的圖進行色調映射處理,形成最終的效果圖輸出
流程很明確,關鍵在于如何實現(xiàn)!
1.渲染場景和光源
用兩個幀緩存渲染場景和光源這種操作實在是太low了,我們怎么能如此不思進取,當然要選擇后一種方法:用一個幀緩存的兩個不同的顏色緩存來渲染。
要做到這一點,我們就需要對原本綁定顏色緩存的流程做一點改動:
/** 物體本身和光源場景幀緩存 */
unsigned int hdrFBO;
glGenFramebuffers(1, &hdrFBO);
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
//創(chuàng)建兩個顏色緩存
unsigned int colorBuffers[2];
glGenTextures(2, colorBuffers);
for (int i = 0; i < 2; ++i) {
glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // 這里我們把紋理截斷到邊緣,因為不希望超出的部分會再重復采樣
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//附加到幀緩存
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0);
}
//創(chuàng)建深度緩存(渲染緩存)
unsigned int rboDepth;
glGenRenderbuffers(1, &rboDepth);
glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
//告訴OpenGL這個幀緩存要用到哪些顏色緩存
unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, attachments);
//檢查幀緩存是否設置成功
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "幀緩存設置失敗,請檢查代碼!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
/** 物體本身和光源場景幀緩存-結束 */
使用2個顏色緩存的時候要注意兩點:
創(chuàng)建兩個顏色緩存,附加到幀緩存的時候分別指定成GL_COLOR_ATTACHMENT0和GL_COLOR_ATTACHMENT1。這一步,上面的代碼通過一個循環(huán),然后GL_COLOR_ATTACHMENT0+i的方式來設置。 告訴OpenGL渲染這個幀緩存的時候,需要渲染2個顏色緩存。這一步,是通過調用glDrawBuffers函數,指定數量并且指定要渲染的顏色緩存數組來實現(xiàn)的。剩下的代碼都是平常的創(chuàng)建幀緩存的代碼,不多解釋。
在片元著色器中,我們也要指定兩個緩存輸出的顏色是什么,這個操作和指定頂點著色器輸入的操作非常類似,我們來看:
//片元著色器
#version 330 core
layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 BrightColor;
...
void main() {
// 檢查結果值是否高于某個門檻,如果高于就渲染到光源顏色緩存中
float brightness = dot(result, vec3(0.2126, 0.7152, 0.0722));
if(brightness > 1.0)
BrightColor = vec4(result, 1.0);
else
BrightColor = vec4(0.0, 0.0, 0.0, 1.0);
FragColor = vec4(result, 1.0);
}
如你所見,我們要用layout的方法指定緩存0和緩存1對應的顏色,然后在主函數中對這些顏色賦值,這樣我們就能得到相應的場景效果了,是不是很簡單?我們把門檻值設置成1.0,凡是超過這個值的我們都認為是發(fā)光區(qū)域(不僅僅是光源才超過,有些亮的部分也會超過),將顏色輸出到緩存1中,正常的場景輸出到緩存0中。
編譯運行之后,你應該看到這樣的場景:

2.進行高斯模糊操作
按照我們之前分析的原理,我們可以使用一些小技巧巧妙地實現(xiàn)高斯模糊。
我們先對整個場景進行橫向的高斯模糊渲染,然后再進行縱向的高斯模糊渲染,重復5次達到效果。
為了能實現(xiàn)這個目標,我們要創(chuàng)建兩個幀緩存來回渲染。將進行橫向渲染的場景保存到緩存1中,再用1進行縱向的渲染到緩存2,這才算是完成了一次高斯模糊渲染。然后,繼續(xù)用緩存2中的圖片渲染到緩存1中,一直這樣來回渲染,直到完成5次完整的雙通道高斯模糊渲染。
聽上去很帶勁是不是,實現(xiàn)起來也很帶勁:
/** 乒乓?guī)彺?nbsp;*/
unsigned int pingpongFBO[2];
unsigned int pingpongColorBuffer[2];
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongColorBuffer);
for (int i = 0; i < 2; ++i) {
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
glBindTexture(GL_TEXTURE_2D, pingpongColorBuffer[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorBuffer[i], 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cout << "幀緩存準備失??!" << std::endl;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
/** 乒乓?guī)彺?結束 */
創(chuàng)建兩個幀緩存,來回渲染的過程很像打乒乓球,所以我們形象地將它命名為乒乓?guī)彺?。由于我們不需要深度信息,所以兩個幀緩存只要附加上顏色緩存就可以了。
//進行高斯模糊
bool horizontal = true, first_iteration = true;
shaderBlur.use();
for (int i = 0; i < two_passGaussianBlurCount * 2; ++i) {
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
shaderBlur.setInt("horizontal", horizontal);
glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorBuffer[!horizontal]);
renderQuad();
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
上面這段代碼是用來進行來回渲染用的。首先,在第一次迭代的時候,我們要用光源場景圖進行首次渲染。然后,在之后的迭代中用的都是上一次完成高斯模糊渲染之后的圖。直到我們進行了10次循環(huán)。horizontal變量用來設置當前是進行水平模糊計算還是垂直模糊計算,在片元著色器中有這個uniform接受主函數的控制:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[](0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162);
void main() {
vec2 tex_offset = 1.0 / textureSize(image, 0); //每個像素的尺寸
vec3 result = texture(image, TexCoords).rgb * weight[0];
if (horizontal) {
for (int i = 0; i < 5; ++i) {
//左右兩個方向上都要進行采樣
result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
}
else {
for (int i = 0; i < 5; ++i) {
//上下兩個方向上都要進行采樣
result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
}
FragColor = vec4 (result, 1.0);
}
片元著色器的代碼應當說是相當直觀的:我們首先計算了每個片元的位置偏移值,采用的方式是獲取當前紋理圖尺寸然后取倒數。
接著,result變量初始化為當前片元顏色乘上權重數組中的首元素。當然這不是必須的,你也可以直接初始化為0,或者乘上其他的數值當成是權重。
接著,接收horizontal變量的控制,如果是true則進行水平方向的模糊計算,要注意當前片元的左右兩邊都需要計算,所以每次循環(huán)需要進行兩次計算。
實現(xiàn)之后,我們就可以來看看高斯模糊之后的效果了:

這個效果就非常明顯了,不像之前的核效果那樣,就是稍微模糊了一點,不仔細看還看不出來。
合并場景和模糊圖,進行色調映射
合并場景和模糊圖的方式也非常簡單,進行色調映射的方式更加簡單,把前面HDR例子里色調映射的算法復制過來就好了。
先來看主函數中合并的代碼:
shaderBloomFinal.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, colorBuffers[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, pingpongColorBuffer[!horizontal]);
shaderBloomFinal.setInt("bloom", 1);
shaderBloomFinal.setFloat("exposure", 1.0);
renderQuad();
就是用一個新的著色器來處理兩張圖,然后輸出,沒啥花頭。片元著色器的代碼也很簡單:
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D image;
uniform sampler2D imageBlur;
uniform bool bloom;
uniform float exposure;
void main() {
const float gamma = 2.2;
vec3 hdrColor = texture(image, TexCoord).rgb;
vec3 bloomColor = texture(imageBlur, TexCoord).rgb;
if (bloom)
hdrColor += bloomColor; //添加融合
//色調映射
vec3 result = vec3 (1.0) - exp(-hdrColor * exposure);
//進行gamma校正
result = pow(result, vec3 (1.0 / gamma));
FragColor = vec4(result, 1.0);
}
如果啟用了Bloom,就將兩張圖的對應片元顏色相加,然后進行色調映射,再加上gamma校正,形成最終的顏色值輸出。最終的效果如下所示:

總結
好,來個總結吧。這次我們學了HDR和Bloom的知識,HDR本質上是用一個更廣的范圍表示顏色值,然后通過色調映射算法顯示出來。Bloom則是通過高斯模糊的方法將場景中發(fā)光物體表現(xiàn)地像是在發(fā)光發(fā)熱的樣子。
HDR的核心在于色調映射算法,這次我們只介紹了曝光度的算法,公式是:

高斯模糊的算法是對某一個片元進行水平和垂直的兩次權重計算,重復幾次,得到最終的結果。原理上非常簡單,記住也非常容易,經常使用就可以了。
原文鏈接:https://www.jianshu.com/p/6206707c265d

技術交流,歡迎加我微信:ezglumes ,拉你入技術交流群。
推薦閱讀:
覺得不錯,點個在看唄~

