OpenGL模板缓冲区---StencilBuffer

  • 前言

在OpenGL中存在着多种缓冲区,这些缓冲区大致分为:
  颜色缓冲区:用于绘图的缓冲区,它包含了颜色索引或者RGBA颜色数据。
  深度缓冲区:存储每个像素的深度值,当启动深度测试时,片段像素深度值和深度缓冲区深度值进行比较,决定片段哪些像素点数据可以替换到颜色缓冲区中。
   模板缓冲区(Stencil Buffer):与颜色缓冲区和深度缓冲区类似,模板缓冲区可以为屏幕上的每个像素点保存一个无符号整数值。这个值的具体意义视程序的具体应用而定。在渲染的过程中,可以用这个值与一个预先设定的参考值相比较,根据比较的结果来决定是否更新相应的像素点的颜色值。这个比较的过程被称为模板测试。模板测试发生在透明度测试(alpha test)之后,深度测试(depth test)之前。如果模板测试通过,则相应的像素点更新,否则不更新。就像使用纸板和喷漆一样精确的混图一样,当启动 模板测试时,通过模板测试的片段像素点会被替换到颜色缓冲区中,从而显示出来,未通过的则不会保存到颜色缓冲区中,从而达到了过滤的功能。下图描述了模板缓冲区的原理:
OpenGL模板缓冲区---StencilBuffer_第1张图片
  累积缓冲区:累积缓冲区允许你把渲染到颜色缓冲区的值,拷贝到累积缓冲区。在多次拷贝操作到累积缓冲区时,可以用不同方式的把颜色缓冲区内容和当前累积缓冲区的内容进行重复混合
读者一定还记得在写OpenGL程序的时候熟悉的glClear命令吧,经常需要传入的缓冲区类型包括:

  1. 颜色缓冲区 GL_COLOR_BUFFER_BIT
  2. 深度缓冲区 GL_DEPTH_BUFFER_BIT
  3. 模板缓冲区GL_STENCIL_BUFFER_BIT

为了方便可以在一个命令中清除多个缓冲区(使用|操作符)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

  • 模板缓冲区

了解模板缓冲区的原理,下面我们看看在OpenGL中如何使用模板缓冲区进行测试。OpenGL中与模板缓冲区相关的函数有如下几个:

	// 默认模板缓冲区并未开启需要使用glEnable开启
	glEnable(GL_STENCIL_TEST); 
	 // 设置模板缓冲区的写入掩码:当为false时禁止在Stencil Buffer中写入(默认0xff)
	glStencilMask(0xFF);
	// 清除Stencil Buffer的值默认为0
	glClearStencil(clearStencilValue); 
	 // 默认值GL_ALWAYS, 0, 0xFF, 总是通过Stencil测试
	glStencilFunc(func, ref, mask);
	// 默认GL_KEEP, GL_KEEP, GL_KEEP, 不会改变Stencil Buffer
	glStencilOp(fail,zfail,zpass); 
	// 清除StencilBuffer
	glClear(GL_STENCIL_BUFFER_BIT); 
模板测试只有存在模板缓冲区且模板缓冲区开启的情况下进行,模板测试把像素存储在模板缓冲区的点与一个参考值进行比较(glStencilFunc),根据测试结果,对模板缓冲区的值进行响应的修改glStencilOp

//哈哈:来我这儿考试吧,通过的和不通过的都去glStencilOp那儿看看下一步的操作吧
void glStencilFunc (GLenum func, GLint ref, GLuint mask);
  func:
    GL_NEVER 从来不能通过
  GL_ALWAYS 永远可以通过(默认值)
  GL_LESS 小于参考值可以通过
  GL_LEQUAL 小于或者等于可以通过
  GL_EQUAL 等于通过
  GL_GEQUAL 大于等于通过
  GL_GREATER 大于通过
  GL_NOTEQUAL 不等于通过
  ref:  参考值
    mask: 掩码值(会与参考值和模板缓冲区内的值先作与操作,在参考func中的参数测试是否通过测试)
//呵呵:来看看怎么处理你们:
void glStencilOp (GLenum fail, GLenum zfail, GLenum zpass);
  fail模板测试未通过时该如何变化;zfail表示模板测试通过,但深度测试未通过时该如何变化;zpass表示模板测试和深度测试或者未执行深度测试均通过时该如何变化
  GL_KEEP(不改变,这也是默认值)
  GL_ZERO(回零)
  GL_REPLACE(使用测试条件中的设定值来代替当前模板值)
  GL_INCR(增加1,但如果已经是最大值,则保持不变),
  GL_INCR_WRAP(增加1,但如果已经是最大值,则从零重新开始)
  GL_DECR(减少1,但如果已经是零,则保持不变),
  GL_DECR_WRAP(减少1,但如果已经是零,则重新设置为最大值)
  GL_INVERT(按位取反)
也就是说对模板缓冲区有三种操作方式:分别是:

1. 模板缓冲区测试失败了:那么执行glStencilOp中第一个参数fail的操作方式;

2.模板缓冲区测试通过了:那么执行glStencilOp中第二个参数zfail的操作方式;

3.模板缓冲区测试通过了:那么执行glStencilOp中第三个参数zpass的操作方式;

注意三者的共同点都是对模板缓冲区进行操作。

如果模板缓冲区测试失败了,那么对颜色缓冲区就不会写入任何值。利用这个特点,我们可以如下操作:

	// 设置模板缓冲区测试失败,后续的绘制操作只会在模板缓冲区中进行
	glStencilFunc(GL_NEVER, 0x0, 0x0);
	glStencilOp(GL_INCR, GL_INCR, GL_INCR);
	//绘制模板对象
	draw_template_object()
	//接着在非模板对象的缓冲区绘制,这样就可以把模板部分空出来,有种被模板楼空的感觉
	glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

下面通过一个示例来演示如何使用模板缓冲区:

	glClearStencil(0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	glEnable(GL_STENCIL_TEST);
	glStencilFunc(GL_NEVER, 0x0, 0x0);
	glStencilOp(GL_INCR, GL_INCR, GL_INCR);
	
	glColor3f(1.0f, 1.0f, 1.0f);
	GLdouble dRadius = 1.0;
	//在模板缓冲区绘制(因为模板测试失败不能在颜色缓冲区写入)
	glBegin(GL_LINE_STRIP);
		for (double angle = 0.0; angle < 400.0; angle += 0.1)
		{
			glVertex3d(dRadius * cos(angle), dRadius * sin(angle), 0.0);
			dRadius *= 1.002;
		}
	glEnd();

	//现在与颜色缓冲区在模板缓冲区对应处有线的地方不会绘制
	glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	glColor3f(0.0, 1.0, 1.0);
	glRectf(x, y, x+_rectSize, y-_rectSize);
运行的结果如下图所示:

OpenGL模板缓冲区---StencilBuffer_第2张图片

这个颜色缓冲区中的矩形的一部分(条纹线)并没有被绘制,因为这部分对应到Stencil Buffer里面的值是1,而值等于1的地方不会通过模板缓冲区测试(因此并不会在颜色缓冲区中被绘制出来)

下面我们看看在osg中怎么实现同样的效果:

	osg::Geometry *lineStripGeometry = new osg::Geometry;
	osg::Vec3Array *lineVertexArray = new osg::Vec3Array;
	double dRadius = 0.1;
	for (float angle = 0.0; angle < 400.0; angle += 0.1)
	{
		lineVertexArray->push_back(osg::Vec3(dRadius * cos(angle), dRadius * sin(angle), 0));
		dRadius *= 1.002;
	}
	osg::Vec4Array *lineColorArray = new osg::Vec4Array;
	lineColorArray->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
	lineStripGeometry->setVertexArray(lineVertexArray);
	lineStripGeometry->setColorArray(lineColorArray);
	//OSG 3.2之后的版本在setColorArray中设置绑定方式
	lineStripGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);
	lineStripGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, lineVertexArray->size()));
	lineStripGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

	//添加模板测试
        osg::Stencil *lineStripStencil = new osg::Stencil;
	lineStripStencil->setFunction(osg::Stencil::NEVER, 0x0, 0x0);
	lineStripStencil->setOperation(osg::Stencil::INCR, osg::Stencil::INCR, osg::Stencil::INCR);
	lineStripGeometry->getOrCreateStateSet()->setAttributeAndModes(lineStripStencil);
	//////////////////////////////////////////////////////////////////////////

	osg::Geometry *quadGeometry = new osg::Geometry;
	
	osg::Vec3Array *quadVertexArray = new osg::Vec3Array;
	quadVertexArray->push_back(osg::Vec3(0, 0, 0));
	quadVertexArray->push_back(osg::Vec3(25, 0, 0));
	quadVertexArray->push_back(osg::Vec3(25, 25, 0));
	quadVertexArray->push_back(osg::Vec3(0, 25, 0));

	osg::Vec4Array *quadColorArray = new osg::Vec4Array;
	quadColorArray->push_back(osg::Vec4(0.0, 1.0, 1.0, 1.0));

	quadGeometry->setVertexArray(quadVertexArray);
	quadGeometry->setColorArray(quadColorArray);
	quadGeometry->setColorBinding(osg::Geometry::BIND_OVERALL);
	quadGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));
	quadGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

	osg::Stencil *quadStencil = new osg::Stencil;
	quadStencil->setFunction(osg::Stencil::NOTEQUAL, 0x1, 0x1);
	quadStencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::KEEP);
	quadGeometry->getOrCreateStateSet()->setAttributeAndModes(quadStencil);
	
	osg::Geode *geode = new osg::Geode;
	geode->addDrawable(lineStripGeometry);
	geode->addDrawable(quadGeometry);

本文参考文章:

  1. OpenGL模板缓冲区与模板测试
  2. Wikipedia Stencil buffer
  3. Stencil Buffer(模板缓冲区)

你可能感兴趣的:(C++,OpenGL,OSG)