浏览量:1,471

模板缓冲区

本文主要介绍模板缓冲区的用途,基本用法,主要的操作函数和模板缓冲区在倒影渲染中的应用。

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

一. 介绍

所谓的模板缓冲区是做什么用的,举个简单的例子来说明,如图1所示。屏幕上画一个平面,把这个平面写入到模板缓冲区中,再画小球的时候,进行模板测试,小球在平面范围内的内容会显示出来,不在平面范围内的就不显示。假设,把图1中画的平面,看成一扇窗户,而小球看成窗户外面的风景,那么只有在窗户范围内的风景才会显示出来。所谓的模板缓冲区,基本就干这么一件事儿,它在倒影(Reflection)和阴影(Shadow)效果处理上,都起到非常重要的作用。

2014-1-21 22-43-17

图1. (1)画一个平面和一个球;(2)启用模板测试,小球不在平面范围内的内容不显示;(3)关闭模板测试,小球在与不在平面范围内,都将内容显示出来

只有存在模板缓冲区的情况下,才会进行模板测试,即将存储在模板缓冲区中的值与参考值进行对比,通过测试的像素才可能在屏幕上显示,而且依赖测试的结果,可能对模板缓冲区中的值进行修改。

OpenGL和DirectX都支持模板缓冲区,每个像素占的位数通常是1、4、8等。渲染过程中,对片断(Fragment)的处理,包括裁剪测试、透明度测试、模板测试、深度测试等等,它们的顺序如图2所示,模板测试发生在深度测试之间。

2014-1-21 22-44-07

图2. 每个片断的处理管线

二. OpenGL中与模板缓冲区相关的操作

OpenGL中与模板缓冲区相关的操作,主要有下面几种。

glEnable/glDisable(GL_STENCIL_TEST);
glClearStencil(0);
glClear(… | GL_STENCIL_BUFFER_BIT);
glStencilFunc(function, ref, mask);
glStencilOp(stencil_fail, depth_fail, depth_pass);
glStencilMask(mask);

默认情况下,模板测试是关闭的,需要调用glEnable()开启模板测试,不用时调用glDisable关系模板测试。

glClearStencil()和glClear()用于清除模板缓冲区。

glStencilFunc设置模板测试比较的条件、参考值和掩码,function可以取的值和意义如下表所示:

2014-1-21 22-44-48

glStencilOp()用于更新模板缓冲区的操作,模板缓冲区的更新与模板测试的结果和深度测试的结果都有关系,该函数有3个参数,它们的意思分别表示:

(1) stencil_fail,规定模板测试失败时进行的操作,默认值是GL_KEEP;

(2) depth_fail,规定模板测试通过,但是深度测试失败时的操作,默认值是GL_KEEP;

(3) depth_pass,规定了下面有两种情况的操作,默认值是GL_KEEP:

i. 模板测试和深度测试都通过

ii.  模板测试通过但是不存在深度缓冲区或者未开启深度测试

glStencilOp()中3个参数可取值和它们对应的意义如下表所示:

2014-1-21 22-45-17

三. 倒影

这里以NENE教程Lesson 26为例,介绍模板缓冲区在倒影效果渲染上的应用,倒影效果的DEMO如图3所示。

2014-1-21 22-45-34

图3. 倒影效果

如果不采用模板缓冲测试,则会产生图4所示的错误的倒影效果:

2014-1-21 22-45-45

图4. 错误的倒影效果

渲染一个对象的倒影,相当于把对象绘制两遍,一遍当作实体,一遍当作倒影。渲染倒影,可以使用《任意点与平面的反射矩阵》中提供的倒影矩阵,NENE中的代码,求的是y=0平面上的倒影,所以只简单使用了glScalef(1.0f, -1.0f, 1.0f),就实现了倒影对象的渲染。此外,对倒影部分,还需要进行两个操作:(1)裁剪掉不在倒影平面区域范围内的内容,这就是模板缓冲区干的事儿;(2)裁剪掉在倒影平面上的内容,这就是平面裁剪干的事儿,如果不干这步,渲染出来的倒影可能如图5所示,箭头所示的内容是倒影,在模板缓冲区中,但是却在倒影平面之上,正常情况不该显示出来,否则,就像电视中的贞子从电视中爬出来一样。详细的代码参考NENE中的Lesson26,文章最后也给出了代码,这里只给出渲染部分用到的代码。

2014-1-21 22-45-56

图5. 错误的倒影效果

int DrawGLScene(GLvoid)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glLoadIdentity();

	//把模板缓冲区中,地面区域设置成,抑制R、G、B、Alpha写入帧缓冲区中
	double eqr[] = {0.0f,-1.0f, 0.0f, 0.0f};
	glTranslatef(0.0f, -0.6f, zoom);
	glColorMask(0,0,0,0);
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_ALWAYS, 1, 1);	
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
	glDisable(GL_DEPTH_TEST);
	DrawFloor();

	//重新启动深度测试,恢复R、G、B、Alpha写入帧缓冲区的权限,重新设置模板缓冲区测试条件、更新方法等
	glEnable(GL_DEPTH_TEST);
	glColorMask(1,1,1,1);
	glStencilFunc(GL_EQUAL, 1, 1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	//渲染小球倒影,并且裁剪掉在地面以上的倒影
	glEnable(GL_CLIP_PLANE0);
	glClipPlane(GL_CLIP_PLANE0, eqr);
	glPushMatrix();	
		glScalef(1.0f, -1.0f, 1.0f);
		glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
		glTranslatef(0.0f, height, 0.0f);
		glRotatef(xrot, 1.0f, 0.0f, 0.0f);
		glRotatef(yrot, 0.0f, 1.0f, 0.0f);
		DrawObject();
	glPopMatrix();
	glDisable(GL_CLIP_PLANE0);
	glDisable(GL_STENCIL_TEST);

	//设置小球倒影与地面的混合方法
	glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
	glEnable(GL_BLEND);
	glDisable(GL_LIGHTING);
	glColor4f(1.0f, 1.0f, 1.0f, 0.8f);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	DrawFloor();
	glEnable(GL_LIGHTING);
	glDisable(GL_BLEND);

	//绘制小球
	glTranslatef(0.0f, height, 0.0f);
	glRotatef(xrot, 1.0f, 0.0f, 0.0f);
	glRotatef(yrot, 0.0f, 1.0f, 0.0f);
	DrawObject();
	xrot += xrotspeed;
	yrot += yrotspeed;
	glFlush();
	return TRUE;
}

四. 参考

【1】       https://www.opengl.org/sdk/docs/man2/

【2】       Mark J. Kilgard, Creating Reflections and Shadows Using Stencil Buffers

【3】       http://www.cnblogs.com/aokman/archive/2010/12/13/1904723.html

【4】       http://blog.csdn.net/zjull/article/details/11617301

spacer

Leave a reply