启用模板测试时,OpenGL会在内存中开辟一块空间作为模板缓冲区,里边保存了每个像素的“模板值”,模板测试的过程就是把每一个像素的模板值与一个设定的模板参考值进行比较,符合设定条件的通过测试,不符合条件的则不会绘制。
glClearStencil函数用来指定模板值复位后的初始值,跟使用glClearColor函数指定复位后画板的颜色一样。
glStencilFunc函数用来设置模板测试条件。第一个是模板测试条件,第二个是指定的当前模板的测试值,程序会使用改值与参考模板值进行比较,第三个是mask位,设置后,只会比较mask中二进制位位1的位。
glStencilOp (GLenum fail, GLenum zfail, GLenum zpass)用来设置模板值根据模板测试结果和深度测试结果的结果如何变化。
第一个参数表示模板测试未通过时该如何变化,第二个参数表示模板测试通过,但深度测试未通过时该如何变化,第三个参数表示模板测试和深度测试均通过时该如何变化。这种模板的变换可以是:
- GL_KEEP(不改变,这也是默认值)
- GL_ZERO(回零)
- GL_REPLACE(使用测试条件中的设定值来代替当前模板值)
- GL_INCR(增加1,但如果已经是最大值,则保持不变)
- GL_INCR_WRAP(增加1,但如果已经是最大值,则从零重新开始)
- GL_DECR(减少1,但如果已经是零,则保持不变)
- GL_DECR_WRAP(减少1,但如果已经是零,则重新设置为最大值)
- GL_INVERT(按位取反)
另外程序中用到了glPushMatrix和glPopMatrix两个函数,glPushMatrix函数将当前矩阵压入堆栈,使得之后的操作不受之前矩阵变换的影响,都是相对于原始的世界坐标系进行操作。
glPopMatrix将与之最近的一个glPushMatrix压入堆栈的矩阵取出,使得之前的矩阵变换对其后的运算有效。这两个函数的配合使用可以使得两者之间的矩阵操作不受之前生成的变换矩阵的影响,并且两者之间的矩阵操作也不会对主体的矩阵变换产生影响。
以下是使用模板测试功能,实现平面镜功能,镜子之外的物体不会绘制:
#define WindowWidth 400
#define WindowHeight 400
#define WindowTitle "OpenGL模板测试"
#include <gl/glut.h>
GLfloat angle = 0.0f;
void draw_sphere()
{
// 设置光源
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
{
GLfloat
pos[] = {5.0f, 5.0f, 0.0f, 1.0f},
ambient[] = {0.0f, 0.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
}
// 绘制一个球体
glPushMatrix();
glTranslatef(0, 0, 2);
glutSolidSphere(0.8, 20, 20);
glPopMatrix();
}
void display(void)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 设置观察点
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, 1, 5, 25);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(5, 0, 6.5, 0, 0, 0, 0, 1, 0);
glRotatef(angle,0,1,0);
glEnable(GL_DEPTH_TEST);
// 绘制球体
glDisable(GL_STENCIL_TEST);
draw_sphere();
// 绘制一个平面镜。在绘制的同时注意设置模板缓冲。
// 另外,为了保证平面镜之后的镜像能够正确绘制,在绘制平面镜时需要将深度缓冲区设置为只读的。
// 在绘制时暂时关闭光照效果
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_STENCIL_TEST);
glDisable(GL_LIGHTING);
glColor3f(0.5f, 0.5f, 0.5f);
glDepthMask(GL_FALSE);
glRectf(-2.0f, -2.0f, 1.6f, 1.6f);
glDepthMask(GL_TRUE);
// 绘制一个与先前球体关于平面镜对称的球体,注意光源的位置也要发生对称改变
// 因为平面镜是在X轴和Y轴所确定的平面,所以只要Z坐标取反即可实现对称
// 为了保证球体的绘制范围被限制在平面镜内部,使用模板测试
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glScalef(1.0f, 1.0f, -1.0f);
draw_sphere();
// 交换缓冲
glutSwapBuffers();
}
void myIdle(void)
{
angle+=0.1f;
if( angle >= 360.0f )
angle = 0.0f;
display();
}
int main(int argc, char* argv[])
{
// GLUT初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow(WindowTitle);
glutDisplayFunc(&display);
glutIdleFunc(&myIdle);
// 开始显示
glutMainLoop();
return 0;
}
运行截图: