`

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
分享到:
评论

相关推荐

    基于opengl的hdr渲染实例

    这是一个基于opengl的hdr 渲染实例,是初学hdr的很好的资料。

    基于C++/OpenGL ES3实现PBR/IBL渲染器

    基于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 ...

    RenderEngine:基于OpenGL构建的3D渲染引擎

    渲染引擎一个使用C ++构建在OpenGL之上的3D渲染引擎,具有完全程序性的无限世界: 使用细分和几何体着色器在GPU上通过自动LOD生成地形和水用分形算法生成的程序植被,并在GPU上生成天空作为带有程序太阳盘的渐变立方...

    GLest-Rendererer:玩具OpenGL渲染器

    GLest渲染器OpenGL 4.6渲染器带来的乐趣和痛苦。特征利用无绑定和多绘制功能来最大程度地减少CPU开销带有许多灯光的延缓阴影通过滤波后的方差,指数和矩阴影映射选项(VSM,ESM,MSM) 体积照明避免边缘的À-Trous...

    deferred-pbr:使用C99编写的PBR延迟渲染器和其他各种CG技术

    PBR延迟渲染器这是一个用C编写的SDL + OpenGL渲染项目,它实现了我发现有趣的各种计算机图形技术。 目前,它包括HDR延迟渲染管线,PBR照明,IBL,阴影映射,视差映射,色调映射,基本粒子发射器(正向渲染)。 材质...

    opengl:创建OpenGL图形引擎以测试先进的实时阴影和照明技术

    法线贴图视差映射自发光着色器所有光源类型的阴影贴图处理多个光影图像加载纹理2D文字叠加立方体贴图环境后期特效HDR色调映射延迟渲染屏幕空间环境光遮挡发光效果文献资料完整的文档可以在找到使用的图书馆用于...

    limitless-engine:OpenGL C ++图形引擎

    该项目是跨平台的3D图形引擎,致力于使用现代OpenGL进行高性能,低开销的渲染。 特征: 状态缓存 多线程上下文资源共享 MaterialCompiler +着色器程序自省 索引缓冲区自动绑定 纹理单元自动绑定 缓冲区流:孤立,不...

    titan:使用OpenGL用C ++编写的3D游戏引擎

    泰坦引擎 使用OpenGL并用C ++编写的图形... 延迟渲染器 GPU地形镶嵌 级联PCF阴影映射 SSAO Godrays和镜头光晕 盛开 景深 HDR色调映射 在GPU上进行地形高度图编辑 建造 Building已在Linux上经过测试。 目前不支持Windo

    glslViewer:用于2D3D着色器的基于控制台的GLSL沙箱

    glslViewer GlslViewer是一个基于控制台的灵活OpenGL沙盒,无需用户界面即可显示2D / 3D GLSL着色器。 您绝对可以使用Python模块(包括)或任何其他与glslViewer来回通信的工具(使用标准POSIX控制台输入/输出或OSC...

    ColumbusEngine:用C ++编写的3D跨平台游戏引擎

    基于物理的HDR前向渲染 具有高性能3D声音的音频系统(很快的音频效果) 先进的图形系统 基于子弹物理学的刚体物理学 输入系统 现场经理 先进的粒子效果系统 高级数学库(向量,矩阵,四元数) 后处理(花朵,晕影...

    盆景:锅中的体素引擎

    渲染器功能延迟阴影HDR照明阴影贴图屏幕空间环境光遮挡引擎特点通用线程队列游戏记录/播放热门代码重装粒子系统实体系统资产加载器Performance Profiler功能内存分配跟踪每帧的调用图跟踪(常规的帧百分比,CPU周期...

    Lumina-开源

    Lumina 是一个用于 OpenGL GLSL 着色器的跨平台 IDE。 灵活的基于 ECMA 脚本的语言用于工具和渲染控制。 它支持基于着色器的动画,为延迟着色、深度剥离或 HDR 渲染构建渲染器原型。

    BlueshiftEngine:Blueshift是使用C ++实现的跨平台3D游戏引擎。 它是免费的开放源代码,并且可以在Windows,macOS,iOS和Android上运行。 该项目目前处于开发初期

    HDR渲染和电影色调映射 后期处理 PBR渲染 环境探针 使用物理学 车辆物理 使用和编写脚本 使用DirectSound, 和OpenSLES进行2D和3D音频播放 使用渲染TrueType字体 Unicode字符串支持 从FBX导入3D网格/动画 支持的IDE...

    dagon:D的3D游戏引擎

    使用Reinhard,Hable / Uncharted,ACES和Filmic色调映射运算符进行HDR渲染 HDRI环境图 具有级联阴影映射和体积散射的定向光 球形和管状区域灯 射灯 正常/视差映射,视差遮挡映射 具有法线贴图和PBR材料属性的延期...

    红木:红木引擎

    特征OpenGL支持图形API抽象跨平台(在主要桌面平台上测试) 快速Sprite / Quad Batcher 简单资产/内容管理器和库活动系统音频(SoLoud)支持Spritefont支持支持HDR的2D Light Pre-pass渲染器目标/待办事项序列化文字...

    thelema-engine:Thelema-使用Kotlin编写的3D图形引擎。 基于libGDX的来源

    基于物理的渲染(PBR) 发光材料 音调映射 盛开 SSAO 级联阴影映射 运动模糊 3D图形 VBO,VAO,实例缓冲区 蒙皮网格 灯光:定向,指向 glTF 2.0加载 音讯 Ogg / Vorbis加载 WAV加载 程序声音生成 JSON格式 从JPG,...

    qtmultimedia-plugins-mdk:在libmdk之上实现的qt多媒体插件

    您可以替换sdk中的ffmpeg库以支持更多格式GPU解码器(由于qtmutimedia限制而硬编码,请参阅 优化的OpenGL渲染HDR色调映射建造从或下载sdk 将SDK提取到该目录中生成并安装。 在QtCreator中,您可以添加带有Make参数...

    Volumetric Light Beam - 体积光照明特效

    - “3D噪声”功能要求着色器功能等于或高于Shader Model 3.5 / OpenGL ES 3.0。 2012年之后发布的任何移动设备都应该支持它。 - 仅在Unity 5.5或更高版本上支持“体积粉尘颗粒”。 - “动态遮挡”功能计算遮挡的近似...

Global site tag (gtag.js) - Google Analytics