浏览量:1,558

累积缓冲区

经过《锯齿与抗锯齿技术介绍》和《随机采样和分布式光线追踪》两篇文章的铺垫,现在介绍一种图形学中较高级的主题,就是累积缓冲区。读这篇文章前,假设你们对图形学的顶点处理管线,glFrustum()和gluPerspective()之间的关系等都很熟悉。文章首先会简单的介绍下OpenGL中的几种缓冲区和基本的清除缓冲区的操作,接着介绍下OpenGL中操作累积缓冲区的接口使用方法,最后介绍如何使用累积缓冲区完成抗锯齿(anti-alias)、运动模糊(Motion Blur)和深度场(Depth Field)的特效。在文章最后,使用glut库,提供了三种特效实现的程序。

示例代码下载地址:https://github.com/twinklingstar20/twinklingstar_cn_demo_accumulation_buffer/

一. OpenGL中的缓冲区

1.1   缓冲区分类:

OpenGL系统通常有以下几种缓冲区:

(1)颜色缓冲区

OpenGL的颜色缓冲区主要有:front-left,front-right,back-left,back-right和一些辅助颜色缓冲区。我们绘制的图像就是存储在颜色缓冲区中,它可以存储颜色索引、RGB颜色数据以及alpha值,如果一些OpenGL实现支持立体渲染(stereoscopic rendering,立体的概念参见【6】,举个与立体3D的应用,例如3D电影或者3D显示器,戴上带有偏振效果的眼镜可以看立体电影),会有存储左右眼图像的左缓冲区和右缓冲区,相反,如果不支持立体渲染,则只有左缓冲区。在支持双缓存的系统中会有前缓冲区和后缓冲区,而单缓冲区系统只有前缓冲区。OpenGL可能还支持一些不用于显示的辅助颜色缓冲区,OpenGL并没有规定那些缓冲区的用途,所以你可以随意的使用那些缓冲区(前提是如果有)。

(2)深度缓冲区

深度缓冲区存储每个像素的深度值,深度值用于存储物体与人眼的距离,深度值越大的像素会被深度值小的像素覆盖,深度缓冲区有时候也称为Z缓冲区。

(3)模板缓冲区

模板缓冲区的一个应用就是限制屏幕上可显示的区域,举个例子,如果你要存储一个形状奇特的挡风玻璃,你只需要模板缓冲区存储这个挡风玻璃的形状,接着绘制整个屏幕,只有挡风玻璃所在区域中的内容会显示出来,这就是模板缓冲区的功能。

(4)累计缓冲区

后面会详细介绍。

1.2   清除缓冲区

在图形程序当中,清除缓冲区的开销非常的大,在一个1280*1024的显示器,就有上百万个像素。OpenGL专门设计了清除缓冲区的命令,充分利用体系结构的特点来解决这个问题。清除缓冲区,分两步走战略:

(1)指定要用什么值来填充要清除的缓冲区,例如颜色缓冲区中所有的像素值都用(0,0,0)填充等,指定不同缓冲区值的方法如下所示,单从名字就可以看出指定哪类缓冲区要填充的值了。各个函数具体的参数和使用方法,可以参考【4】中的文档。

void glClearColor();
void glClearIndex();
void glClearDepth();
void glClearStencil(  );
void glClearAccum();

(2)选择待清除的缓冲区并开始清除,OpenGL实现这个功能的函数如下所示,具体可以参考【4】中的文档。

void glClear(GLbitfield mask);

下面举个清除颜色缓冲区和深度缓冲区的例子,演示这清除的整个步骤,如下面的代码片段所示:

glClearColor(0.0, 0.0, 0.0, 0.0);
glClearDepth(1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

如果要清除多个缓冲区的话,开销可能就会加倍。OpenGL允许规定清除多个缓冲区,清除缓冲区是一个较慢的过程,一些显卡支持几个缓冲区同步进行清除,对于不允许同步进行的硬件,就只能顺序进行。所以采用

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

可能

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);

快。

二.累积缓冲区

2.1 累积缓冲区简介

累积缓冲区的概念最早在是1990年Paul Haeherli等发表的“The Accumulation Buffer: Hardware Support for High-Quality Rendering”论文中提出的(参考【3】)。该论文描述累积缓冲区这种硬件体系结构,它可以存储红、绿、蓝、Alpha四个分量,每个分量都占16位,因此累积缓冲区中的每个像素需要用64位来存储。累积缓冲区有三个主要的操作:

(1)清除,每个像素的R、G、B、Alpha分量都被设置成0;

(2)增加权重,绘图缓冲区中的(相当于OpenGL中的颜色缓冲区)每个像素值被乘以一个浮点系数(可正可负),然后加到累积缓冲区中;

(3)把累积缓冲区中保存的R、G、B、Alpha分量以一定比例缩放,返回给绘图缓冲区中。

累积缓冲区与其它缓冲区是一种并行的结构关系,与当前的图形渲染体系结构相兼容,上面的几种操作都能非常的快速的完成,而且采用累积缓冲区这样一种硬件结构,可以较高效地实现抗锯齿、运动模糊、深度场、柔化阴影的特效(论文中也提出了实现的方法,下面会分别介绍如何利用累积缓冲区实现几种特效)。

正因为累积缓冲区的这些优势,使它能在OpenGL标准接受,下面先介绍累积缓冲区在OpenGL中主要的接口定义和使用方法,接着分别介绍如果利用累积缓冲区完成抗锯齿、运动模糊、深度场这些特效,以及他们能产生特效的原理。

2.2 OpenGL中的累积缓冲区

操作OpenGL中的累积缓冲区主要用到的函数如下所示:


void glAccum(  GLenum op, GLfloat value)     

1. op 规定对累积缓冲区的操作。只能是GL_ACCUM,GL_LOAD,GL_ADD,GL_MULT,GL_RRETURN五个参数
GL_ACCUM

从当前颜色缓冲区中获得R、G、B、A的数值,除以(2^分量占的位数-1)(指的是当前颜色缓冲区中的位数),结果是一个0到1之间的浮点数,再乘以value的值,把相应的像素分量加到累积缓冲区中。

GL_LOAD

与GL_ACCUM的操作类似,不同的是得到0到1之间的浮点数,乘以value的值后替换累积缓冲区中的数值,而不是加到累积缓冲区中。

GL_ADD

将value值加到累积缓冲区中的R、G、B、A分量上

GL_MULT

将累积缓冲区中的每个分量乘以浮点数value,将得到的结果写到累积缓冲区相应的位置上。

GL_RETURN

把累积缓冲区中的值返回到当前的颜色缓冲区中。

2. value 规定了对累积缓冲区操作的浮点数数值,op相当于规定了如何使用value的值。

累积缓冲区是颜色缓冲的拓展区域,图像不能直接往上面绘制,但是被绘制进颜色缓冲区中的图像通过一定的处理可以增加到累积缓冲区中。通过不同方法,可以使用累积缓冲区完成各种不同的效果,主要有抗锯齿、运动模糊、深度场等。累积缓冲区中包括四类值,有红,绿,蓝和alpha值,每种分量的位数依赖OpenGL的实现,通过调用四次glGetIntegerv可以查询每种分量的位数。无论每个分量占几位,每种分量的值都在[-1,1]范围内,累积缓冲区中的像素与帧缓冲区中的像素是一一对应的关系。

这里为了简化OpenGL的说明,避免对windows API的介绍,使用glut的函数库来解释累积缓冲区的用法,主要可以分成四个步骤:

(1)首先需要申请打开累计缓冲区

glutInitDisplayMode(... | GLUT_ACCUM);

(2)指定R、G、B、A的值来填充要清除累积缓冲区,在使用累积缓冲区渲染前清除累积缓冲区(这就是前面说的清除缓冲区的步骤)

glClearAccum(r,g,b,A);

glClear(GL_ACCUM_BUFFER_BIT);

累积缓冲区通常是一个累积和,所以通常把它们初始化为0。

(3)每次给累积缓冲区中增加一帧

glAccum(GL_ACCUM,fi)

(4)最后把累积缓冲区中累积的数值,返回到颜色缓冲区中

glAccum(GL_RETURN, 1.0);

其实这里的第2个参数不一定非要是1.0,但是它的值需要是1/(f1+…+fn),就不会产生明显变暗的效果。

用一个等式来表示这样一个流程,如下所示:

Accum = f1*frame1 + f2*frame2 + …. + fn*framen

2.3 抗锯齿

在文章《锯齿与抗锯齿技术介绍》中介绍了锯齿的概念,在《随机采样和分布式光线追踪》一文介绍了随机采样,随机采样可以抗锯齿,它的基本思想是:对每个像素进行随机的超采样,得到随机的采样点后,再经过重构过滤器处理,最后得到一个像素点的值,最简单的重构过滤器就是箱式过滤器,即对所有的采样点求合取平均值。参考【3】中,作者提出的一种方法是使用高斯过滤器,不仅仅使用到一个像素内部的采样点,还可以使用相邻像素内的采样点,它可以取得比箱式过滤器更好的抗锯齿效果,但是对论文中说的高斯过滤器还没有完全理解。

接下来介绍的方法也是论文【3】的作者提出的,在参考【2】书中,作者实现了该方法,介绍下该方法的基本思想。

函数glFrustum(left,right,bottom,top,nearPlane,farPlane)就是相当于是指定一个视锥,视锥范围内的物体会显示在屏幕上,如图1所示。将近平面在水平和垂直方向上随机的位移(图形学中的三维坐标系统下的量)一小段距离,要保证位移量在屏幕上显示的大小小于一个像素尺寸,再经过透视投影后,将图像绘制到颜色缓冲区中,累积缓冲区就会从颜色缓冲区中获得像素数据,乘以一个系数,累积到累积缓冲区中。累积一次,就相当于采样一次,经过多次累积后,将结果返回给颜色缓冲区。如果每次累积的时候,使用到的系数相同,这个过程就相当于是随机超采样,最后通过箱式过滤器过滤得到结果像素。

2013-11-26 18-15-35

图1 glFrustum函数规定的近平面

具体的实现可以参考最后附录文件,这里就不贴代码了,如下所示,运行结果如图2所示:

2013-11-26 18-17-13

2013-11-26 18-17-27

2013-11-26 18-17-40

图2 这6张图片上半平面的棋格表示的是非使用累积缓冲区绘制出的效果,而下半平面的棋格表示使用累积缓冲区绘制出的效果,采样个数分别是2,4,6,8,12,16(从左往右,从上往下的顺序)

2.4 运动模糊

使用累积缓冲区可以实现模糊运动的特效,这里将关键的代码片段放出来,如下所示,累积缓冲区实现运动模糊的效果挺简单的,代码就不解释了。运行结果图3所示,特别要解释说明一点的是,代码片段中每次累积的系数都是0.05,为什么从下到上,亮度的变化是由亮到暗的呢,原因就是球之间有重叠。如果球与球之间没有重叠,则亮度值就会一样,我觉得这时候为了正确的仿真运动模糊的特效,累积系数可以递减。

void animation(Vector3 from,Vector3 to)
{
	int i , frameNum = 10;
	float dx = (from.x - to.x) / (float)frameNum;
	float dy = (from.y - to.y) / (float)frameNum;
	float dz = (from.z - to.z) / (float)frameNum;
	drawScene(to);
	//为了保证累积系数的和是.0
	glAccum(GL_LOAD, 0.5);
	for( i=1 ; i<frameNum ; i++ )
	{
		glAccum(GL_ACCUM,0.05f);
		Vector3 pos = to;
		pos.x += dx*i; pos.y += dy*i; pos.z += dz*i;
		drawScene(pos);
	}
	glAccum(GL_RETURN,1.0f);
}

2013-11-26 18-20-00

图3. 运动模糊演示程序

2.5 深度场

在《随机采样和分布式光线追踪》一篇文章中,已经简单的解释了深度场的概念,也可以参考【5】,发现这个网址深度场概念的解释更加简单易懂。在提到的文章中,介绍了可以用光线追踪的方法来实现深度场的特效,用累积缓冲区实现深度场的方法与它略有不同。

depth-field

图4. 对视锥进行微位移,实现深度场的特效

与2.3小节抗锯齿的方法类似,通过glFrustum()函数,位移近平面的窗口,对场景进行反复绘制,如图4所示。如图4最上面的图所示,未使用抖动时,三个方块在累积缓冲区中形成初始值;如图4中间图所示,当把眼睛的位置移动以A点处,中间的方块通过累积,与累积缓冲区中间方块相重叠,还能较清晰的聚焦,前后两个方块的位置与累积缓冲区原有方块位置存在差异,不能完全重叠,因此能产生一定的模糊效果;当眼睛位于B处时,处理过程与眼睛在A处相似。对眼睛移距离的计算,是由x方向上、y方向上的随机位移量和一个常数确定的,不存在理论上的最优值,需要反复的测试,得到最优效果。具体的实现可以参考最后附录文件,这里就不贴代码了,得到的结果如图5所示:

2013-11-26 18-22-00

图5. 左图,前面3个小球较清晰,但是后面的小球是模糊的;通过按键盘上的“U”键,焦距变大,因此前面的几个小球变得模糊,后面的小球变得清晰

2.6 柔和阴影

这种实现技术,会在以后结合阴影技术,单独抽离出来介绍。

三.参考

【1】      http://www.cs.uiuc.edu/class/sp05/cs419/accum/accum.html

【2】      《OpenGL Programming Guide-Sixth Edition-The Official Guide Learning OpenGL, Version 2.1

【3】      Paper,Paul Haeherli等,“The Accumulation Buffer: Hardware Support for High-Quality Rendering

【4】      http://www.opengl.org/sdk/docs/man2/

【5】      http://www.cs.mtu.edu/~shene/DigiCam/User-Guide/950/depth-of-field.html

【6】      http://en.wikipedia.org/wiki/Stereoscopy

spacer

Leave a reply