`

HDR渲染器的实现(基于OpenGL)

 
阅读更多

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" World Wide Web link. 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

 

 

 

 

 

 

 

 

  • 大小: 41.4 KB
  • 大小: 60.3 KB
  • 大小: 509 Bytes
  • 大小: 433 Bytes
  • 大小: 1023 Bytes
  • 大小: 1.1 KB
  • 大小: 2.5 KB
  • 大小: 1.9 KB
  • 大小: 33.1 KB
  • 大小: 15.6 KB
  • 大小: 14.9 KB
  • 大小: 27.2 KB
分享到:
评论

相关推荐

    FastReport 模版打印如何实现

    FastReport 模版打印如何实现

    使用运算放大器模拟反相放大器的闭环电压增益simulink.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    基于java的抗疫医疗用品销售平台设计与实现.docx

    基于java的抗疫医疗用品销售平台设计与实现.docx

    vue+SpringBoot749乡村日常政务管理系统的设计与实现java毕业设计源码含论文.rar

    jdk版本:jdk1.8+ 前端:vue.js+ElementUI 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:springboot 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+springboot+vue+MYSQL+MAVEN 前端采用的Vue框架,后端采用java语言,sprinboot框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统

    java毕业设计源码ssm859基于SSM框架的线上DIY手工艺品综合平台+vue程序数据库含论文.rar

    前端采用的Vue框架,后端采用java语言,ssm框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统 本系统基于SSM(Spring+SpringMVC+MyBatis)框架,适用于毕业设计, 基于B/S模式, mysql数据库,感兴趣的朋友们可以下载研究一下。 jdk版本:jdk1.8+ 前端:vue.js+ElementUI 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:ssm 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+ssm+vue+MYSQL+MAVEN

    java基于ssm+vue企业在线培训系统源码 带毕业论文

    【资源说明】 1、开发环境:ssm框架;内含Mysql数据库;VUE技术 2、项目代码都经过严格调试,代码没有任何bug!下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料学习借鉴。 4、本资源作为“参考资料”如果需要实现其他功能,需要能看懂代码,并且热爱钻研,自行调试。

    任务管理系统Web(Spring+Mybaties).zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    这是一个基于mediapipe实现的运动计数项目;大三上期末小组作业;python。.zip

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    (全国物联网大赛一等奖,挑战杯铜奖)基于QT的人脸识别,定位导航,脑电心率测算,用GPRS传到服务端的疲劳驾驶检测系统(毕设&课

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    南开大学2023年前端开发实训大作业.zip(课设&实训&大作业&项目)

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    毕业设计 C-V2X漏洞挖掘 开放资源.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    基于 Java+SqlServer 实现的学生学籍管理系统毕业设计(源代码+论文+开题报告+外文翻译+答辩PPT)

    【作品名称】:基于 Java+SqlServer 实现的学生学籍管理系统【毕业设计】(源代码+论文+开题报告+外文翻译+答辩PPT) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【项目介绍】:本系统是用JSP开发工具实现的,主要完成对学籍的管理,包括添加、修改、删除,查询,以及用户管理等五个方面。系统可以完成对各类信息的浏览、查询、添加、删除、修改等功能。系统的核心是添加、修改和删除三者之间的联系,每一个表的修改都将联动的影响其它的表,当完成添加或删除操作时系统会自动地完成学籍的修改。查询功能也是系统的核心之一,在系统中主要根据学生学号和课程号进行查询,其目的都是为了方便用户使用。系统也有完整的用户添加、删除和密码修改功能。 本系统功能主要划分学生操作管理、管理员操作管理这2大模块。其中学生操作模块包括学籍维护、选课管理、成绩查询、修改密码这几个小模块。管理员操作包括后台学生信息管理、选修课程信息管理、基础课程信息管理、成绩这几个小模块。 在本系统中使用的是SQL Server2000数据库

    本项目属于机器学习的简单部分,基于为了快速理解机器学习而搭建的人工智能速成项目(毕设&课设&实训&大作业&竞赛&项目)

    本项目属于机器学习的简单部分,基于为了快速理解机器学习而搭建的人工智能速成项目,大家可以根据其中的项目时间进行相关的学习.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    2022年研一高级机器学习课程设计.zip(课设&实训&大作业&项目)

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    java-ssm947基于JSP的贝儿宠物店管理系统的设计与实现+jsp程序源码数据库含论文.rar

    本系统基于SSM(Spring+SpringMVC+MyBatis)框架,适用于毕业设计, 基于B/S模式, mysql数据库,感兴趣的朋友们可以下载研究一下。 后端采用java语言,ssm框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统 jdk版本:jdk1.8+ 前端:jsp 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:ssm 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+ssm+jsp+MYSQL+MAVEN

    vue+SpringBoot421露营地管理系统设计与实现java毕业设计源码含论文.rar

    jdk版本:jdk1.8+ 前端:vue.js+ElementUI 开发工具:IDEA 或者eclipse都支持 编程语言: java 框架支持:springboot 数据库: mysql 版本不限 数据库工具:Navicat/SQLyog都可以 详细技术:java+springboot+vue+MYSQL+MAVEN 前端采用的Vue框架,后端采用java语言,sprinboot框架,mybatis操作数据源,使用软件:idea,eclipse、MySQL。完成了用户登录管理等模块的设计与实现。完成了系统数据库的设计,并基于MySQL数据库管理系统

    基于java的小型药店管理系统设计与实现.docx

    基于java的小型药店管理系统设计与实现.docx

    基于java的人事管理系统设计与实现.docx

    基于java的人事管理系统设计与实现.docx

    技术栈Spring MVC+ Mybatis+Spring + Jsp+ Tomcat , 是Java Web入门非常好项目

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。

    simulink 电力系统之变压器空投合闸,励磁涌流分析.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

Global site tag (gtag.js) - Google Analytics