OpenGL模版测试

最近看了Crazyjumper(文章地址)的博客里的一篇文章,为了学习和记录特写此文,如有不妥之处请指出学习,谢谢!

模板测试首先,模板测试需要一个模板缓冲区,这个缓冲区是在初始化OpenGL时指定的。如果使用GLUT工具包,可以在调用glutInitDisplayMode函数时在参数中加上GLUT_STENCIL,例如:


glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL);

在Windows操作系统中,即使没有明确要求使用模板缓冲区,有时候也会分配模板缓冲区。但为了保证程序的通用性,最好还是明确指定使用模板缓冲区。如果确实没有分配模板缓冲区,则所有进行模板测试的像素全部都会通过测试。通过glEnable/glDisable可以启用或禁用模板测试。

glEnable(GL_STENCIL_TEST);  // 启用模板测试glDisable(GL_STENCIL_TEST); // 禁用模板测试

OpenGL在模板缓冲区中为每个像素保存了一个“模板值”,当像素需要进行模板测试时,将设定的模板参考值与该像素的“模板值”进行比较,符合条件的通过测试,不符合条件的则被丢弃,不进行绘制。条件的设置与Alpha测试中的条件设置相似。但注意Alpha测试中是用浮点数来进行比较,而模板测试则是用整数来进行比较。比较也有八种情况:始终通过、始终不通过、大于则通过、小于则通过、大于等于则通过、小于等于则通过、等于则通过、不等于则通过。

glStencilFunc(GL_LESS, 3, mask);

这段代码设置模板测试的条件为:“小于3则通过”。glStencilFunc的前两个参数意义与glAlphaFunc的两个参数类似,第三个参数的意义为:如果进行比较,则只比较mask中二进制为1的位。例如,某个像素模板值为5(二进制101),而mask的二进制值为00000011,因为只比较最后两位,5的最后两位为01,其实是小于3的,因此会通过测试。如何设置像素的“模板值”呢?glClear函数可以将所有像素的模板值复位。代码如下:

glClear(GL_STENCIL_BUFFER_BIT);

可以同时复位颜色值和模板值:

glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

正如可以使用glClearColor函数来指定清空屏幕后的颜色那样,也可以使用glClearStencil函数来指定复位后的“模板值”。每个像素的“模板值”会根据模板测试的结果和深度测试的结果而进行改变。

glStencilOp(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(按位取反)。在新版本的OpenGL中,允许为多边形的正面和背面使用不同的模板测试条件和模板值改变方式,于是就有了glStencilFuncSeparate函数和glStencilOpSeparate函数。这两个函数分别与glStencilFunc和glStencilOp类似,只在最前面多了一个参数face,用于指定当前设置的是哪个面。可以选择GL_FRONT, GL_BACK, GL_FRONT_AND_BACK。注意:模板缓冲区与深度缓冲区有一点不同。无论是否启用深度测试,当有像素被绘制时,总会重新设置该像素的深度值(除非设置glDepthMask(GL_FALSE);)。而模板测试如果不启用,则像素的模板值会保持不变,只有启用模板测试时才有可能修改像素的模板值。(这一结论是我自己的实验得出的,暂时没发现什么资料上是这样写。如果有不正确的地方,欢迎指正)另外,模板测试虽然是从OpenGL 1.0就开始提供的功能,但是对于个人计算机而言,硬件实现模板测试的似乎并不多,很多计算机系统直接使用CPU运算来完成模板测试。因此在一些老的显卡,或者是多数集成显卡上,大量而频繁的使用模板测试可能造成程序运行效率低下。即使是当前配置比较高端的个人计算机,也尽量不要使用glStencilFuncSeparate和glStencilOpSeparate函数。从前面所讲可以知道,使用剪裁测试可以把绘制区域限制在一个矩形的区域内。但如果需要把绘制区域限制在一个不规则的区域内,则需要使用模板测试。

   我们仍然来看一个实际的例子。这是一个比较简单的场景:空间中有一个球体,一个平面镜。我们站在某个特殊的观察点,可以看到球体在平面镜中的镜像,并且镜像处于平面镜的边缘,有一部分因为平面镜大小的限制,而无法显示出来。整个场景的效果如下图:

代码如下:

#include <gl/glut.h>

#define WIDTH 400
#define HEIGHT 400

//渲染前的初始化
void Initialize()
{
	glClearColor(0.0f, 0.0f, 0.0f , 1.0f);
	glShadeModel (GL_SMOOTH);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	
	
	GLfloat ambient[]={0.2f, 0.2f, 0.2f, 1.0f};
	GLfloat light_position[] = {3.0f, 0.0f, -1.0f, 1.0f};
    GLfloat light_diffuse[]  = {0.5f, 0.5f, 1.0f, 1.0f};//一般漫反射光值与反射光值相同,毕竟是属于同一光源是吧
    GLfloat light_specular[] = {0.5f, 0.5f, 1.0f, 1.0f};
	//设置光位置和属性
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
	//选择光照模型
	glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);//默认
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT,ambient);
	//启用光照
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);//开启0号光源
}
//重置渲染窗口和视图
void Reshape(int width, int height)
{
	// 创建透视效果视图
	
	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,4.828f,1000.0f);
	glViewport(0, 0, width, height);
    glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(7, 0, 6, 0, 0,0, 0, 1, 0);
}

void DrawSphere() //画球
{
	glPushMatrix();
	glTranslatef(0.0f, 0.0f, 2.0f);
	glutSolidSphere(0.5, 30, 30);
	glPopMatrix();
}

void Display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	DrawSphere();     //画镜外球

	glDisable(GL_LIGHTING);
	glClearStencil(1);                  //设置清空模版缓存的后默认值为0
	glClear(GL_STENCIL_BUFFER_BIT);			//清空模版缓存
	glStencilFunc(GL_ALWAYS, 2, 0xFF);     //模版测试总是通过
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);   //使得之后经测试的像素点的模版值都为1
	glEnable(GL_STENCIL_TEST);          //开启模版测试
	glDepthMask(GL_FALSE);            //将深度缓冲设为只读,使得之后画的图形无Z关系,只存在重叠关系
	glRectf(-2.0f, 3.0f, 2.0f, -3.0f);   //画镜面
	glDepthMask(GL_TRUE);            //将深度缓冲设为可写
	glEnable(GL_LIGHTING);
	

	glStencilFunc(GL_EQUAL, 2, 0xFF);       //将模版测试的条件设置为模版值等与1的可通过,其余的都放弃
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

	glPushMatrix();
	glScaled(1, 1, -1); //关于Y轴对称
	DrawSphere(); //画镜中球
	glPopMatrix();
	glDisable(GL_STENCIL_TEST); //关闭模板测试,防止镜外球被过滤

    glutSwapBuffers();
}


int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE|GLUT_STENCIL);
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(WIDTH, HEIGHT);
    glutCreateWindow("OpenGL模版缓存演示");
	
	Initialize();
	glutReshapeFunc(&Reshape);
    glutDisplayFunc(&Display);
	
    glutMainLoop();//进入渲染和消息循环
    return 0;
}
代码模版测试过程示意图(个人理解)


好吧,先到这.......
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值