from: http://dev.gameres.com/Program/Visual/3D/HDRTutorial/HDRTutorial.htm
HDR渲染器的实现(基于OpenGL)
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是图像中的最大亮度值。
又如:
也是一个简单的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大小 |
121所有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
相关推荐
在“基于opengl的hdr渲染实例”中,我们将探讨如何利用OpenGL实现HDR渲染,以及这对图形编程的意义。 首先,HDR渲染的核心在于处理超过普通8位或16位颜色深度的数据。在现实世界中,我们看到的场景包含了从最暗到最...
基于C++/OpenGL ES3实现PBR/IBL渲染器 配置: Android Studio 2020.3.1.RC1 NDK 21.2.6472646 OpenGL 3.0 ES CMake 3.4+ 通过HDR贴图生成cubmap贴图实现基于环境的光照 手动调整材质贴图缩放比列和displacement ...
OpenGL 3.3 HDR(高动态范围)是一个高级图形编程技术,用于在计算机图形中实现更真实的视觉效果。HDR允许渲染场景中的亮部和暗部细节同时存在,从而提供更宽广的亮度范围,模拟人眼看到的真实世界。在OpenGL 3.3...
压缩包中的"openglhdr"可能是实现全屏泛光HDR的一个示例项目或代码库,可能包含了所需的顶点着色器、片段着色器、纹理贴图以及相关的计算逻辑。通过研究这些文件,你可以深入理解如何在实际项目中应用OpenGL全屏泛光...
- **高级光照技术**:深入探讨如何实现真实感光照效果,如环境光遮蔽(Ambient Occlusion)、高动态范围照明(HDR Lighting)等。 - **后处理效果**:介绍后处理(Post Processing)技术,如模糊(Blur)、锐化(Sharpen)、...
在这个压缩包中,包含的是一个使用OpenGL实现天空盒的源码示例,主要用于学习和理解这种技术在三维游戏开发中的应用。 首先,天空盒的基本概念是基于视口空间中的六面体,这六个面分别代表了观察者的上、下、前、后...
它允许渲染器捕捉并显示比标准LDR(低动态范围)更广泛的亮度范围,从而更好地模拟现实世界的光照情况。在HDR中,亮度值可以超过1,这意味着可以呈现非常亮的光源,同时保留暗部细节。OpenGL提供了支持HDR的框架,...
3. **着色器**:现代OpenGL版本中引入了可编程着色器,允许开发者通过GLSL(OpenGL Shading Language)编写顶点着色器和片段着色器,实现高度定制化的渲染效果。 ### 功能与特性 1. **纹理映射**:OpenGL支持多种...
- **曲面细分着色器**:介绍Tessellation Shader的工作原理,实现复杂的几何体渲染。 - **计算着色器**:利用Compute Shader进行通用GPU编程,提高渲染效率。 #### 四、OpenGL进阶技巧 - **帧缓冲对象**:介绍FBO...
7. **GLSL着色器**:在OpenGL 3.3中,我们使用GLSL(OpenGL Shading Language)编写顶点和片段着色器来实现PBR效果。着色器代码会包含对上述光照和材质属性的计算。 8. **视口空间和投影变换**:在绘制过程中,需要...
学习和测试Forge 渲染 API实现 绘制纹理四边形 实例化 冯氏着色 光照贴图 照明(Blinn-Phong)定向光点光源聚光灯 负荷模型 古奇阴影分析和 UI 延迟光照 模板缓冲区卡通着色 镶嵌直通PN 三角形 盛开 HDR 渲染 伽马...
4. **32位浮点纹理和渲染缓存**:为了提高图像质量和细节表现,OpenGL 3.0支持32位浮点纹理和渲染缓存,这对于实现高动态范围(HDR)渲染等高级图形效果至关重要。 5. **基于阻塞队列的条件渲染**:这一特性增强了...
- 对于开发者而言,了解OpenGL的工作原理有助于更好地控制图形渲染过程,从而实现更为高效和高质量的图形应用。 #### 二、OpenGL的版本与模式 - **版本历史**: - OpenGL的发展经历了多个版本,每个版本都在原有...
在IT领域,尤其是在个人计算机上的多媒体体验中,视频渲染器起着至关重要的作用。"madVR.zip" 提供的正是这样一种高级视频渲染解决方案,它以其卓越的画质和性能而受到众多视频爱好者和专业人士的青睐。搭配Pot...
OpenGL是用于渲染2D、3D图形的跨语言、跨平台的应用程序编程接口(API),而C++是一种广泛使用的系统和应用程序编程语言,非常适合用于开发图形处理软件。在本项目中,“opengl c++创建天空盒代码”是实现了一个用C++...
在OpenGL中,着色器需要先被编译和链接成一个着色器程序对象,然后才能被应用到渲染管线中。 GLSL还支持着色器之间的通信,可以通过在顶点着色器和片段着色器之间传递的varying变量进行。顶点着色器可以输出varying...
渲染引擎一个使用C ++构建在OpenGL之上的3D渲染引擎,具有完全程序性的无限世界: 使用细分和几何体着色器在GPU上通过自动LOD生成地形和水用分形算法生成的程序植被,并在GPU上生成天空作为带有程序太阳盘的渐变立方...
OpenGL ES 3.0新增了计算着色器的支持,使得开发者可以在GPU上执行复杂的计算任务而不仅仅局限于图形渲染。计算着色器能够独立于渲染管线运行,允许开发者利用GPU强大的并行处理能力进行大规模的数据处理。这在诸如...
GLest渲染器OpenGL 4.6渲染器带来的乐趣和痛苦。特征利用无绑定和多绘制功能来最大程度地减少CPU开销带有许多灯光的延缓阴影通过滤波后的方差,指数和矩阴影映射选项(VSM,ESM,MSM) 体积照明避免边缘的À-Trous...