作者:i_dovelemon
日期:2016 / 07 / 24
主題:HDR, Bloom, Tone mapping, Post-process, Blur
引言
HDR
HDR實現原理
具體實現(OpenGL)
離屏渲染
int32_t tex_id = 0;
glGenTextures(1, reinterpret_cast<GLuint*>(&tex_id));
glBindTexture(GL_TEXTURE_2D, tex_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL);
glGenerateMipmap(GL_TEXTURE_2D);
你可以看到,和傳統的紋理創建基本一致,只是在調用glTexImage2D的時候,需要傳遞不一樣的參數,告知顯卡,我們要保存16位的浮點數據了。
glDrawBuffer(GL_COLOR_ATTACHMENTX);
Tone mapping
Bloom
實現步驟及結果
glReadBuffer(GL_COLOR_ATTACHMENT0);
GLvoid* pixel = new GLfloat[4 * g_WindowWidth * g_WindowHeight];
glReadPixels(0, 0, g_WindowWidth, g_WindowHeight, GL_RGBA, GL_FLOAT, pixel);
指定將要讀取的COLOR_BUFFER,然後調用glReadPixel來獲取紋理數據。獲取了整個紋理數據之後,我們就可以通過使用前面的公式來計算整個場景的平均亮度了。只有在實驗之後,我才知道調用ln和exp這樣的函數,是多麼的慢。對整張貼圖進行這樣的對數,指數運算,的確非常的卡,所以你看,明白了性能的損失了。所以,我就換了另外一種方法來計算平均亮度,也就是簡單的將所有亮度值相加,然後除以像素數,這樣也能夠得到一個平均亮度值,而且這樣的計算,似乎速度還可以,至少能夠在本實例的情況下,滿幀運行。下面就是計算這個平均亮度的完整代碼:
glReadBuffer(GL_COLOR_ATTACHMENT0);
GLvoid* pixel = new GLfloat[4 * g_WindowWidth * g_WindowHeight];
glReadPixels(0, 0, g_WindowWidth, g_WindowHeight, GL_RGBA, GL_FLOAT, pixel);
GLenum error = glGetError();
GLfloat* buffer = reinterpret_cast<GLfloat*>(pixel);
float lum = 0.0f;
for (int32_t i = 0; i < g_WindowHeight; i++) {
for (int32_t j = 0; j < g_WindowWidth; j++) {
float r = buffer[i * g_WindowWidth * 4 + j * 4 + 0];
float g = buffer[i * g_WindowWidth * 4 + j * 4 + 1];
float b = buffer[i * g_WindowWidth * 4 + j * 4 + 2];
float cur_lum = r * 0.27f + g * 0.67f + b * 0.06f;
lum += cur_lum;
}
}
g_RenderTarget->DisableRenderTarget();
g_AverageLum = lum / (g_WindowWidth * g_WindowHeight);
delete[] pixel;
pixel = NULL;
是不是很簡單粗暴易於理解了???
gethighlight.vs
//-----------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 07 / 24
// Brief: Get high light pass through shader
//-----------------------------------------------------------
#version 330
in vec2 vertex;
in vec2 texcoord;
out vec2 vs_texcoord;
void main() {
gl_Position = vec4(vertex, 0.0, 1.0);
vs_texcoord = texcoord;
}
gethgihlight.ps
//-----------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 07 / 24
// Brief: Get high light
//-----------------------------------------------------------
#version 330
in vec2 vs_texcoord;
out vec4 color;
uniform float lum_average;
uniform sampler2D hdr_tex;
const float kKey = 0.5;
const float kHighLight = 0.80;
void main() {
vec3 hdr_color = texture2D(hdr_tex, vs_texcoord).xyz;
float lum = hdr_color.x * 0.27 + hdr_color.y * 0.67 + hdr_color.z * 0.06;
float lum_after_tonemapping = (kKey * lum) / lum_average;
lum_after_tonemapping = lum_after_tonemapping / (1.0 + lum_after_tonemapping);
if (lum_after_tonemapping > kHighLight) {
color = vec4(hdr_color, 0.5);
} else {
color = vec4(0.0, 0.0, 0.0, 0.0);
}
}
這裏要解釋下,我在提取高亮部分的時候,實際上是檢測進行Tone mapping之後的像素數據的亮度。這樣做是爲了我們能夠通過0.0-1.0這個範圍裏面,來設定高亮閥值,否則的話,我可不知道該設置成什麼樣的值,才能夠比較準確的提取出來高亮部分。同時你還可能注意到,我這裏手動的爲每一個高亮的顏色值設置了0.5的alpha值,而爲所有的非高亮像素設置了0.0的alpha值。這麼做是爲了在後面進行blend的時候,能夠準確的只和高亮的部分進行blend,而不需要和非高亮的像素進行混合。
blur.vs
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur pass through vertex shader
//--------------------------------------------------------------------
#version 330
in vec3 vertex;
in vec2 texcoord;
out vec2 vs_texcoord;
void main() {
gl_Position = vec4(vertex, 1.0);
vs_texcoord = texcoord;
}
blurh.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur horizontal pass shader
//--------------------------------------------------------------------
#version 330
in vec2 vs_texcoord;
out vec4 color;
uniform sampler2D tex;
uniform float tex_width;
uniform float gauss_num[21];
void main() {
color = texture2D(tex, vs_texcoord) * gauss_num[0];
float step = 1.0 / tex_width;
for (int i = 1; i < 21; i++) {
if (vs_texcoord.x - i * step >= 0.0) {
color += texture2D(tex, vec2(vs_texcoord.x - i * step, vs_texcoord.y)) * gauss_num[i];
}
if (vs_texcoord.x + i * step <= 1.0) {
color += texture2D(tex, vec2(vs_texcoord.x + i * step, vs_texcoord.y)) * gauss_num[i];
}
}
}
blurv.ps
//--------------------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 06 / 29
// Brief: Gauss blur vertical pass shader
//--------------------------------------------------------------------
#version 330
in vec2 vs_texcoord;
out vec4 color;
uniform sampler2D tex;
uniform float tex_height;
uniform float gauss_num[21];
void main() {
color = texture2D(tex, vs_texcoord) * gauss_num[0];
float step = 1.0 / tex_height;
for (int i = 0; i <21; i++) {
if (vs_texcoord.y - i * step >= 0.0) {
color += texture2D(tex, vec2(vs_texcoord.x, vs_texcoord.y - i * step)) * gauss_num[i];
}
if (vs_texcoord.y + i * step <= 1.0) {
color += texture2D(tex, vec2(vs_texcoord.x, vs_texcoord.y + i * step)) * gauss_num[i];
}
}
}
這個階段得到的是如下的一張貼圖:
tonemap.vs
//-----------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 07 / 24
// Brief: Tone map pass through shader
//-----------------------------------------------------------
#version 330
in vec2 vertex;
in vec2 texcoord;
out vec2 vs_texcoord;
void main() {
gl_Position = vec4(vertex, 0.0, 1.0);
vs_texcoord = texcoord;
}
//--------------------------------------------------------
// Declaration: Copyright (c), by i_dovelemon, 2016. All right reserved.
// Author: i_dovelemon[[email protected]]
// Date: 2016 / 07 / 24
// Brief: Tone mapping the HDR scene
//--------------------------------------------------------
#version 330
in vec2 vs_texcoord;
out vec4 color;
uniform float lum_average;
uniform sampler2D hdr_tex;
uniform sampler2D bloom_tex;
const float key = 0.5;
void main() {
vec4 hdr_color = texture2D(hdr_tex, vs_texcoord);
vec4 bloom_color = texture2D(bloom_tex, vs_texcoord);
float lum = hdr_color.x * 0.27 + hdr_color.y * 0.67 + hdr_color.z * 0.06;
float lum_after_tonemapping = (key * lum) / lum_average;
vec4 blend_color = hdr_color * (1.0 - bloom_color.w) + bloom_color * bloom_color.w;
color = blend_color * lum_after_tonemapping;
color /= vec4(1.0 + color.x, 1.0 + color.y, 1.0 + color.z, 1.0);
}
最終的結果如下所示:
一個有趣的效果
static float time = 0.0f;
float ratio = std::sin(3.1415f * 0.5f * (time / 120.0f));
g_UsedLum = 1.0f + (g_AverageLum - 1.0f) * ratio;
time = time + 1.0f;
if (time > 120.0f) {
time = 120.0f;
}
if (GetKeyState('F') & 0x8000) {
time = 0;
}
是不是很簡單?
整體流程代碼一覽
void glb_display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glb_draw_scene_to_texture();
glb_calc_average_lum();
glb_draw_to_get_highlight_texture();
glb_draw_hpass_bloom_highlight_to_texture();
glb_draw_vpass_bloom_highlight_to_texture();
glb_draw_hdr_scene();
glutSwapBuffers();
}