HDR渲染器的實現(基於OpenGL)
作者:何詠(歡迎和大家交流,我的QQ:35574585,Email:[email protected])
我的個人網站:http://www.graphixer.com.cn
原文出處:http://www.graphixer.com.cn/ShowWorks.asp?Type=1&ID=48
Demo下載:http://www.graphixer.com.cn/ShowWorks.asp?Type=3&ID=27
如需轉載本文,請聲明作者及出處。
HDR簡介
這篇教程講解了如何實現一個高動態範圍渲染系統。HDR(High Dynamic Range,高動態範圍)是一種圖像後處理技術,是一種表達超過了顯示器所能表現的亮度範圍的圖像映射技術。高動態範圍技術能夠很好地再現現實生活中豐富的亮度級別,產生逼真的效果。HDR已成爲目前遊戲應用不可或缺的一部分。通常,顯示器能夠顯示R、G、B分量在[0,255]之間的像素值。而256個不同的亮度級別顯然不能表示自然界中光線的亮度情況。比如,太陽的亮度可能是一個白熾燈亮度的幾千倍,是一個被白熾燈照亮的桌面的亮度的幾十萬倍,這遠遠超出了顯示器的亮度表示能力。如何在有限的亮度範圍內顯示如此寬廣的亮度範圍,正是HDR技術所要解決的問題。
將一個寬廣的亮度範圍映射到紙張或屏幕能表示的亮度範圍類似於照相機的曝光功能。人眼也有類似的功能。通過照相機的光圈,可以控制進入感光器的光線數量,感光器得到的明暗程度經過一定的處理,就可以得到令人信服的照片。照相機是一個典型的從高動態範圍映射到低動態範圍的例子。如果我們能夠在一定程度上模擬照相機的工作原理,就可以在屏幕上顯示高動態範圍的圖像。對於人眼或鏡頭,過亮的光線射入時會產生光暈效果,這一點也可以通過一些方法模擬。動態曝光控制和光暈效果結合起來,就構成了一個經典的高動態範圍渲染器。
一個運行中的HDR渲染器,背景牆由於過亮而產生了光暈 |
這組圖像演示了動態曝光技術,圖爲從黑暗的隧道走向明亮的房間的一個過程,可以看到動態光線適應的過程:房間從全白色過渡到正常曝光顏色
下面將結合OpenGL詳細地介紹HDR的實現過程。其他圖形API在方法上是一致的,只是具體的實現細節略有差異。
HDR技術原理
我們已經知道,HDR渲染包含兩個步驟,一是曝光控制,即將高動態範圍的圖像映射到一個固定的低範圍中,既屏幕能夠顯示的(0,1)的範圍內。二是對於特別亮的部分實現光暈的效果。其中曝光控制是HDR渲染的核心環節,光暈效果對錶現高亮的像素起了重要的作用。這裏先分別介紹兩個步驟的原理和方法,再介紹如何實現一個完整的HDR渲染器。
在所有步驟開始之前,你必須已經通過某種方法得到了一個高動態範圍的圖像。高動態範圍的圖像每一個像素都由浮點型的R,G,B分量表示,這樣每個分量都可以任意大。對於渲染器而言,這意味着一個浮點紋理。那麼,如何將一個場景渲染到一個高動態範圍的浮點紋理中呢?你可以爲場景中的每個表面創建一張浮點格式的光照貼圖,這張光照貼圖的每個象素代表了表面上相應位置的光強。然後使用OpenGL的FBO(幀緩衝對象)將綁定了浮點光照貼圖的場景渲染到一個同屏幕大小一致的浮點紋理中。關於FBO和浮點紋理的使用請參考《OpenGL中的浮點紋理和幀緩衝對象》 。
好的,先來看看所謂得、的曝光控制。這個步驟在HDR渲染中被稱爲Tone Mapping。翻譯成中文即“調和映射”。Tone Mapping有很多具體的方法,每個方法都是一個從高動態範圍到低範圍的一個映射,我們把這些方法統稱爲 Tone Mapping Operator(TMO),可見,TMO的好壞直接決定了圖像的最終質量。例如,
是一個簡單的TMO,其中Lfinal是映射後的像素亮度值,L是映射前的像素亮度值,alpha是圖像中的最小亮度值,beta是圖像中的最大亮度值。
又如:----(1)
也是一個簡單的TMO。這兩個TMO都可以將高動態的像素值映射到(0,1)上。然而,這些TMO的效果並不令人滿意。人的眼睛往往能適應場景中的光的強度,在一個黑暗的屋子裏,你仍然能看見其中的東西,分辨物體的亮度和顏色,當你從屋子中突然走向明亮的室外時,會有刺眼的感覺,但很快眼睛優惠適應新的環境,從而能夠看清更亮的場景。爲了模擬人眼的這種特性,我們需要計算當前要渲染的高動態範圍圖像的平均亮度,然後根據平均亮度確定一個曝光參數,然後使用這個曝光參數將圖像正確地映射到屏幕能現實的顏色區域內。這裏介紹DirectX 9.0 SDK中所介紹的方法。假設Lumave(稍後介紹)爲計算得到的原始圖像平均亮度,那麼對於原始圖像中的任一像素點Lum(x,y),有下面的映射關係:
其中,Lscaled爲映射後的值,alpha爲一個常數,alpha的大小決定了映射後場景的整體明暗程度,可以根據需要適當調整,這個值在以後的實現中稱爲Key值。經過這樣的映射後,Lscaled並不一定處在(0,1)的範圍中,我們再結合(1)式,使最終的像素值處在(0,1)上:
這樣就完成了最終的映射。 現在討論如何計算原始圖像的平均亮度。平均亮度的計算由下面的公式給出:
式中,δ是一個較小的常數,用於防止求對數的計算結果趨於負無窮的情況。如δ可取0.0001。這個式子的意義是,對於原始圖像每個像素,計算出該像素的亮度值Lum(x,y),然後求出該亮度值的自然對數。接着對所有像素亮度值的對數求平均值,再求平均值的自然指數值。至於爲什麼這樣算出的值能夠合理地表示圖像的平均亮度,這裏就不再詳細說明了,有興趣可以參看相關論文[1]。
那麼,對於一個像素P(r,g,b),如何計算像素的亮度Lum呢?其計算公式爲:
這些RGB分量的權重是根據人眼對不同光的敏感程度得到的。以上是Tone Mapping的基本理論。可能你還未能完全理解某些細節,但沒有關係,在後面的具體實現過程中,將會講解具體的實現方法。
現在再看一下光暈效果是如何實現的。所謂光暈效果,就是抽出場景中色彩比較亮的部分,然後加以模糊,使這些較量的像素擴散到周邊像素中,再把模糊後的圖像疊加在Tone Mapping之後的圖像上。其過程如下圖所示。
|
|
|
|
Tone Mapping之後的圖像 |
取出原始圖像中較亮的部分,並縮小其尺寸 |
進行模糊 |
將模糊後的圖像拉伸疊加到Tone Mapping之後的圖像上 |
實現過程
本文僅詳細介紹如何對渲染得到的高動態範圍浮點紋理進行高動態範圍後處理的過程,不關注場景的渲染過程。我們把渲染器的工作分爲以下幾個函數:
BeginRendering(); 這個函數在一切場景繪製操作被調用之前執行。它負責準備FBO對象,初始化一個渲染到浮點紋理的渲染環境。
EndRendering(); 這個函數在場景繪製操作結束後執行,它負責處理FBO中已得到的高動態範圍數據,並映射到低範圍中,並將最終結果顯示在屏幕上。
PostProcess(); 這個函數被EndRendering()調用,它負責HDR處理的全過程。
MeasureLuminance();這個函數用於計算圖像的平均亮度值。
此外,我們假定有一個CGPUImage類,它創建並維護一個浮點格式的紋理。CImageBlurFilter負責模糊一個圖像。CImageScaleCopy負責把一個浮點紋理中的數據縮小尺寸後複製到另一個紋理中去。
下面看一下HDR處理的大致流程:
1.初始化操作。創建一個和屏幕同樣大小的浮點紋理texColor,創建FBO對象stdfbo,創建一個爲屏幕1/4大小的浮點紋理texBloom,創建一個32*32大小的浮點紋理imgLumSample。此操作在應用程序初始化階段執行一次。 2.渲染前操作。將texColor綁定到stdfbo對象中,並應用stdfbo對象。 3.渲染場景。像往常一樣渲染場景,只不過場景中的貼圖、光照可以爲浮點數,並且會向緩衝區(texColor)中寫入浮點型的數據。 4.渲染後操作。 (1)將texBloom綁定到stdfbo對象,然後以texColor爲紋理,渲染一個爲屏幕1/4大小的矩形,這樣texBloom便成爲texColor的1/4大小的副本。 (2)把texBloom綁定到stdfbo對象,然後以imgLumSample爲紋理,渲染一個32*32大小的正方形,並使用一個shader對每個象素取對數。這樣imgLumSample成爲texColor的更小尺寸的取了對數後副本。 (3)把imgLumSample的數據回讀到系統內存,然後計算出平均亮度。(如果你覺得回讀是一個很慢的操作,也可以在GPU上繼續執行下采樣操作,直到紋理大小縮小到1*1,那麼這個像素的值取值數就代表了平均亮度。而經過我的試驗,這樣做的效率會比回讀更加低下。) (4)步驟(3)執行後,imgLumSample中的數據就沒有作用了,但接下來可以把texBloom下采樣到imgLumSample中,在下采樣的過程中只選取高亮度的像素。再對imgLumSample進行模糊,這樣imgLumSample就成爲了texBloom的更小尺寸高亮度部分的副本。 (5)對imgLumSample運用高斯模糊。這一步也是通過shader實現的。 (6)禁用FBO對象,接下來對屏幕輸出最後渲染結果。綁定Tone Mapping Shader,在Shader中根據計算出來的平均亮度值對texColor進行Tone Mapping,Tone Mapping之後的結果和imgLumSample疊加後輸出到屏幕上。 |
另外,人眼對光線變化有一個適應過程,爲了模擬這個過程,我們可以維護另一個浮點類型的變量LumAdapt,存儲當前人眼已經適應的亮度值。在每一幀計算出當前幀的平均亮度LumAve後,讓LumAdapt慢慢向LumAve逼進。使用下面的代碼完成這一點:
lumAdapt += (lum - lumAdapt) * ( 1 - pow( 0.98f, 30 * dTime ) );
其中,lum是當前場景的平均亮度,dTime是自從上一幀到現在所經過的時間。
接下來,我們仔細研究一下後處理的具體代碼。
void CRenderer::PostProcess() { float dx = 0.5f/Width; float dy = 0.5f/Height; int loc; //把texColor複製到imgBloom ScaleCopy->ScaleCopyTextureImage(texColor,Width,Height,imgBloom); MeasureLuminance(imgBloom); //計算imgBloom的平均亮度 lumAdapt += (lum - lumAdapt) * ( 1 - pow( 0.98f, 30 * dTime ) ); //計算當前適應的亮度 // render bloom map // progBloom由Bloom.vs和Bloom.fs組成。其代碼在後面給出。這個Shader用於提取出 UseShaderProgram(progBloom);
//接下來,把texColor渲染到imgBloom裏面,使用progBloom Shader程序提取出亮度較大的部分。
loc = gapi->Shader->GetUniformLocationARB(progBloom->ProgramID,"texColor"); glBindTexture(GL_TEXTURE_2D,texColor); //設置渲染對象。 CRenderTarget tgt; tgt.AddColorAttachment(imgBloom); SetRenderTarget(tgt); gapi->Shader->Uniform1iARB(loc,0); loc = gapi->Shader->GetUniformLocationARB(progBloom->ProgramID,"AveLum"); gapi->Shader->Uniform1fARB(loc,lumAdapt); loc = gapi->Shader->GetUniformLocationARB(progBloom->ProgramID,"imgH"); gapi->Shader->Uniform1iARB(loc,Height); loc = gapi->Shader->GetUniformLocationARB(progBloom->ProgramID,"imgW"); gapi->Shader->Uniform1iARB(loc,Width); glBegin(GL_QUADS); glTexCoord2f(dx,1-dy); glVertex2i(0,0); glTexCoord2f(dx,dy); glVertex2i(0,imgBloom->GetHeight()); glTexCoord2f(1-dx,dy); glVertex2i(imgBloom->GetWidth(),imgBloom->GetHeight()); glTexCoord2f(1-dx,1-dy); glVertex2i(imgBloom->GetWidth(),0); glEnd(); UseShaderProgram(0); ResetRenderTarget(); // 下采樣imgBloom到imgLumSample中。imgLumSample的大小爲32*32。 tgt.ColorAttachCount = 0; tgt.AddColorAttachment(imgLumSample); SetRenderTarget(tgt); UseShaderProgram(progDownSample8); loc = gapi->Shader->GetUniformLocationARB(progDownSample8->ProgramID,"imgH"); gapi->Shader->Uniform1iARB(loc,imgBloom->GetHeight()); loc = gapi->Shader->GetUniformLocationARB(progDownSample8->ProgramID,"imgW"); gapi->Shader->Uniform1iARB(loc,imgBloom->GetWidth()); loc = gapi->Shader->GetUniformLocationARB(progDownSample8->ProgramID,"texColor"); glBindTexture(GL_TEXTURE_2D,imgBloom->GetID()); gapi->Shader->Uniform1iARB(loc,0); glBegin(GL_QUADS); glTexCoord2f(dx,1-dy); glVertex2i(0,0); glTexCoord2f(dx,dy); glVertex2i(0,imgLumSample->GetHeight()); glTexCoord2f(1-dx,dy); glVertex2i(imgLumSample->GetWidth(),imgLumSample->GetHeight()); glTexCoord2f(1-dx,1-dy); glVertex2i(imgLumSample->GetWidth(),0); glEnd(); UseShaderProgram(0); ResetRenderTarget(); // 模糊Bloom貼圖。BlurFilter是一個類,在GPU上執行模糊操作。 BlurFilter->SetImage(imgLumSample); BlurFilter->Blur(); imgLumSample = BlurFilter->GetImage(); // Tone Mapping UseShaderProgram(progTone); if (!progTone->Validate()) throw HException(progTone->Info); gapi->BindTexture2D(texColor,0); loc = gapi->Shader->GetUniformLocationARB(progTone->ProgramID,"texSrc"); gapi->Shader->Uniform1iARB(loc,0); gapi->BindTexture2D(imgLumSample->GetID(),1); loc = gapi->Shader->GetUniformLocationARB(progTone->ProgramID,"texBloom"); gapi->Shader->Uniform1iARB(loc,1); loc = gapi->Shader->GetUniformLocationARB(progTone->ProgramID,"AveLum"); gapi->Shader->Uniform1fARB(loc,lumAdapt); loc = gapi->Shader->GetUniformLocationARB(progTone->ProgramID,"Key"); gapi->Shader->Uniform1fARB(loc,HDRKey); glColor4ub(255,255,255,255); gapi->FBO->BindFramebuffer(GL_FRAMEBUFFER_EXT,0); //設置渲染對象爲屏幕 glBegin(GL_QUADS); glTexCoord2f(dx,1-dy); glVertex2i(0,0); glTexCoord2f(dx,dy); glVertex2i(0,Height); glTexCoord2f(1-dx,dy); glVertex2i(Width,Height); glTexCoord2f(1-dx,1-dy); glVertex2i(Width,0); glEnd(); UseShaderProgram(0); gapi->BindTexture2D(0,1); glBindTexture(GL_TEXTURE_2D,0); }
void CRenderer::MeasureLuminance(CGPUImage *img) { // 把img渲染到imgLumSample,使用shader計算每個象素的對數值 CRenderTarget tgt; tgt.AddColorAttachment(imgLumSample); SetRenderTarget(tgt); UseShaderProgram(progLogSample); if (!progLogSample->Validate()) throw HException(progLogSample->Info); gapi->MultiTexture->ActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,img->GetID()); int loc = gapi->Shader->GetUniformLocationARB(progLogSample->ProgramID,"texSrc"); gapi->Shader->Uniform1iARB(loc,0); glBegin(GL_QUADS); glTexCoord2i(0,1); glVertex2i(0,0); glTexCoord2i(0,0); glVertex2i(0,SampleSize); glTexCoord2i(1,0); glVertex2i(SampleSize,SampleSize); glTexCoord2i(1,1); glVertex2i(SampleSize,0); glEnd(); UseShaderProgram(0); ResetRenderTarget(); glBindTexture(GL_TEXTURE_2D,imgLumSample->GetID()); //回讀到CPU計算亮度。 glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_FLOAT,data); lum = 0; for (int i=0;i<imgLumSample->GetHeight();i++) for (int j=0;j<imgLumSample->GetWidth();j++) lum += data[i*imgLumSample->GetWidth()*4+j*4]; lum /= imgLumSample->GetHeight()*imgLumSample->GetWidth(); lum = exp(lum); }下面給出上面代碼涉及到的Shader程序。Shader程序的組成如下:
程序對象 |
Vertex Shader |
Fragment Shader |
作用 |
progBloom |
Common.vs |
Bloom.fs |
提取場景中的高亮部分 |
progDownSample8 |
Common.vs |
DownSample8.fs |
將輸入圖像下采樣到1/8大小 |
progTone |
Common.vs |
Tone.fs |
Tone Mapping並負責整合Bloom map產生最終結果輸出到屏幕上 |
progLogSample |
Common.vs |
LogSample.fs |
對輸入圖像進行下采樣,並取對數值 |
progBlurX |
Common.vs |
BlurX.fs |
在X方向上對圖像進行高斯模糊 |
progBlurY |
Common.vs |
BlurY.fs |
在Y方向上對圖像進行高斯模糊 |
progScaleCopy |
Common.vs |
ScaleCopy.fs |
下采樣原圖像到1/4大小 |
所有shader程序共用同一個Vertex Shader,這個Vertex Shader非常簡單,就是傳遞頂點位置和紋理座標到後面的管線。因爲所有的操作都是在Fragment Shader裏面完成的。
Common.vs:
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
1/4無損下采樣
//ScaleCopy.fs 用於下采樣圖象到1/4大小
#version 110
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texSrc;
uniform int imgW,imgH;
void main()
{
float dx = 1.0/float(imgW);
float dy = 1.0/float(imgH);
vec4 color = texture2D(texSrc,gl_TexCoord[0].xy);
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*3.0,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx,dy));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*3.0,dy));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*3.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy*3.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx,dy*3.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy*3.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*3.0,dy*3.0));
color /= 16.0;
gl_FragData[0] = color;
}
1/8有損下采樣
//DownSample8.htm 下采樣到1/8大小,可能丟失細節。
#version 110
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texSrc;
uniform int imgH,imgW;
void main()
{
float dx = 1.0/float(imgW);
float dy = 1.0/float(imgH);
vec4 color = texture2D(texSrc,gl_TexCoord[0].xy);
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*4.0,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*6.0,0.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*4.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*6.0,dy*2.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy*4.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy*4.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*4.0,dy*4.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*6.0,dy*4.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,dy*6.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*2.0,dy*6.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*4.0,dy*6.0));
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(dx*6.0,dy*6.0));
color /= 16.0;
gl_FragData[0] = color;
}
對數採樣:
//LogSample.fs 用於對原始圖像進行採樣,並對計算採樣後像素的亮度,然後再對亮度取對數後輸出。
#version 120
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texSrc;
void main()
{
vec4 lumfact = vec4(0.27,0.67,0.06,0.0);
vec4 color = texture2D(texSrc,gl_TexCoord[0].xy);
float lum = log(dot(color , lumfact) + 0.0001);
gl_FragData[0] = vec4(lum,lum,lum,1.0);
}
高斯模糊:
//高斯模糊在X軸和Y軸上各做一次。共需要兩個pass
// 在X軸上的高斯模糊
#version 110
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texSrc;
uniform int imgW;
void main()
{
float d = 1.0/float(imgW);
vec4 color = vec4(0.0,0.0,0.0,0.0);
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(-5.0*d,0.0)) * 0.1;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(-4.0*d,0.0)) * 0.22;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(-3.0*d,0.0)) * 0.358;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(-2.0*d,0.0)) * 0.523;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(-1.0*d,0.0)) * 0.843;
color += texture2D(texSrc,gl_TexCoord[0].xy ) * 1.0;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2( 1.0*d,0.0)) * 0.843;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2( 2.0*d,0.0)) * 0.523;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2( 3.0*d,0.0)) * 0.358;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2( 4.0*d,0.0)) * 0.22;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2( 5.0*d,0.0)) * 0.1;
color /= 5.0;
gl_FragData[0] = color;
}
//在Y軸上的高斯模糊。原理相同
#version 110
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texSrc;
uniform int imgH;
void main()
{
float d = 1.0/float(imgH);
vec4 color = vec4(0.0,0.0,0.0,0.0);
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,-5.0*d)) * 0.1;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,-4.0*d)) * 0.22;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,-3.0*d)) * 0.358;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,-2.0*d)) * 0.563;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0,-1.0*d)) * 0.873;
color += texture2D(texSrc,gl_TexCoord[0].xy ) * 1.0;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0, 1.0*d)) * 0.873;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0, 2.0*d)) * 0.563;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0, 3.0*d)) * 0.358;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0, 4.0*d)) * 0.22;
color += texture2D(texSrc,gl_TexCoord[0].xy+vec2(0.0, 5.0*d)) * 0.1;
color /= 5.0;
gl_FragData[0] = color;
}
產生Bloom map(即抽出高亮部分)
//Bloom.fs 產生Bloom貼圖
#version 110
#extension GL_ARB_draw_buffers : enable
uniform sampler2D texColor;
uniform float AveLum;
uniform int imgH,imgW;
void main()
{
float dx = 1.0/float(imgW);
float dy = 1.0/float(imgH);
//對texColor進行採樣
vec4 color = texture2D(texColor,gl_TexCoord[0].xy);
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(dx*3.0,0.0));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(0.0,dy));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(dx*3.0,dy));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(0.0,dy*2.0));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(dx*3.0,dy*2.0));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(0.0,dy*3.0));
color += texture2D(texColor,gl_TexCoord[0].xy+vec2(dx*3.0,dy*3.0));
color /= 8.0;
//計算該像素在Tone Mapping之後的亮度值,如果依然很大,則該像素將產生光暈
vec4 cout = vec4(0.0,0.0,0.0,0.0);
float lum = color.x * 0.3 + color.y *0.59 + color.z * 0.11;
vec4 p = color*(lum/AveLum);
p /= vec4(vec4(1.0,1.0,1.0,0.0)+p);
float luml = (p.x+p.y+p.z)/3.0;
if (luml > 0.8)
{
cout = p;
}
gl_FragData[0] = cout;
}
Tone Mapping 和輸出
//Tone.fs
uniform sampler2D texSrc;
uniform sampler2D texBloom;
uniform float AveLum;
uniform float Key;
const vec4 lumfact = vec4(0.27,0.67,0.06,0.0);
void main()
{
vec4 color = texture2D(texSrc,gl_TexCoord[0].xy);
float lum = dot(color , lumfact);
color *= Key *lum/AveLum;
color /= vec4(vec4(1.0,1.0,1.0,0.0)+color);
gl_FragColor = clamp(color + texture2D(texBloom,gl_TexCoord[0].xy)*1.3, 0.0,1.0);
}
參考文獻
[1] Reinhard, Erik, Mike Stark, Peter Shirley, and James Ferwerda."Photographic
Tone Reproduction for Digital Images" . ACM Transactions on Graphics (TOG), Proceedings of the 29th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH), pp.
267-276. New York, NY: ACM Press, 2002.
[2] "HDR Lighting sample" in DirectX 9.0c SDK