一、蒙板
蒙板是这样的一种机制,他用来控制深度在蒙板其后的物体,在蒙板区域的某个位置是否被显示。这个功能可以使用混色通过控制ALPHA通道的值来完成,但是 这样的效果并不好,会产生蒙板上物体比较虚的效果。使用蒙板以后我们可以使得透过深度较靠前的物体看到深度较靠后物体的一个部分。 这个机制可以用两种方法来实现。
第一种方式是使用模拟的方式,它的原理是使用混色当中的象素叠加操作。所以我们在操作的一开始必须 要打开混色开关,glEnable(GL_BLEND);然后我们在需要的地方放置我们的蒙板,模板是一个黑白贴图,在需要透视的后方物体的地方这个贴图 使用白色,而需要遮挡的地方我们使用黑色。在载入并绑定蒙板贴图以后我们控制混色方案为 glBlendFunc(GL_DST_COLOR,GL_ZERO);这样贴图的黑色部分被贴至指定位置,而白色部分则没有被贴图。接着载入并绑定与蒙 板对应的贴图,并且更换混色方案为glBlendFunc(GL_ONE,GL_ONE),然后贴图,这样的效果是仅仅将原先蒙板贴到场景中的部分贴上 图。至此一个蒙板的使用完成。
第一种方式是使用模拟的方式,它的原理是使用混色当中的象素叠加操作。所以我们在操作的一开始必须 要打开混色开关,glEnable(GL_BLEND);然后我们在需要的地方放置我们的蒙板,模板是一个黑白贴图,在需要透视的后方物体的地方这个贴图 使用白色,而需要遮挡的地方我们使用黑色。在载入并绑定蒙板贴图以后我们控制混色方案为 glBlendFunc(GL_DST_COLOR,GL_ZERO);这样贴图的黑色部分被贴至指定位置,而白色部分则没有被贴图。接着载入并绑定与蒙 板对应的贴图,并且更换混色方案为glBlendFunc(GL_ONE,GL_ONE),然后贴图,这样的效果是仅仅将原先蒙板贴到场景中的部分贴上 图。至此一个蒙板的使用完成。
第二钟方式使用了OpenGL当中的蒙板缓存。使用这种机制的方法是首先在创建场景时打开蒙板缓存。然后在需要的时候用glEnable允许蒙板缓存的使 用。在完成以上的初始化工作以后我们可以开始使用蒙板缓存,在正式使用之前我们需要用蒙板缓存指定接下来允许进行绘图的地方。因此我们就需要向蒙板缓存当 中写入关于这个区域的数据而写入的过程类似与绘制的过程。在写入这些数据之前为了保证这个写入的过程不会改写深度缓存和颜色缓存,我们必须要用 glColorMask指定所有的颜色掩码为GL_FALSE来保证没有任何颜色被写入到颜色缓存,而对于深度缓存的操作则非常的简单直接用 glDisable关闭之就可以了。然后开始写入蒙板缓存,写入的代码如下:
glStencilFunc(GL_ALWAYS,1,1);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
我们首先来解释这两个函数的用法:
第一个函数glStencilFunc,它的原型如下:
void
glStencilFunc(
GLenum func,
GLint ref , // 参考值
GLuint mask // 掩码 通常为1
);
GLenum func,
GLint ref , // 参考值
GLuint mask // 掩码 通常为1
);
对于参数func我们可以用下面的表来解释:
常量
|
含义
|
GL_NEVER
|
从不通过蒙板测试
|
GL_ALWAYS
|
总是通过蒙板测试
|
GL_LESS
|
只有参考值<(蒙板缓存区的值&mask)时才通过
|
GL_LEQUAL
|
只有参考值<=(蒙板缓存区的值&mask)时才通过
|
GL_EQUAL
|
只有参考值=(蒙板缓存区的值&mask)时才通过
|
GL_GEQUAL
|
只有参考值>=(蒙板缓存区的值&mask)时才通过
|
GL_GREATER
|
只有参考值>(蒙板缓存区的值&mask)时才通过
|
GL_NOTEQUAL
|
只有参考值!=(蒙板缓存区的值&mask)时才通过
|
第二个函数glStencilOp,它的原型如下:
void
glStencilOp(
GLenum fail, // 蒙板测试失败时的动作
GLenum zfail, // 深度测试失败时的动作
GLenum zpass // 蒙板测试和深度测试都通过时的动作
);
GLenum fail, // 蒙板测试失败时的动作
GLenum zfail, // 深度测试失败时的动作
GLenum zpass // 蒙板测试和深度测试都通过时的动作
);
这里可以采取的动作可以用下面的表来表示
常量
|
描述
|
GL_KEEP
|
保持当前的蒙板缓存区值
|
GL_ZERO
|
把当前的蒙板缓存区值设为0
|
GL_REPLACE
|
用glStencilFunc函数所指定的参考值替换蒙板参数值
|
GL_INCR
|
增加当前的蒙板缓存区值,但限制在允许的范围内
|
GL_DECR
|
减少当前的蒙板缓存区值,但限制在允许的范围内
|
GL_INVERT
|
将当前的蒙板缓存区值进行逐位的翻转
|
在解释完这里的两个函数以后我们对于这里设定蒙板的一系列操作就非常清楚了。首先我们使得蒙板测试无论如何都会通过,并且我们设置当通过蒙板测 试以后就用参考值1来代替原来缓存当中相应位置的值,这样在绘制一个蒙板区域以后这个区域的蒙板缓存的值全部为1,而不是这个区域的蒙板缓存的值保持为 0。
现在来看怎样在这个蒙板区域当中绘制模型。在按照上面的方法建立好蒙板缓存以后,我们打开深度缓存并且把颜色掩码改回GL_TRUE允许向颜色缓冲区写入数据。然后我们又要使用上面的两个函数对深度缓冲的操作方式进行修改。具体的代码如下:
glStencilFunc(GL_EQUAL,1,1);
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);
这段代码的意义是,只有参考值与深度缓冲区的值都为1才能通过测试,也就是说只有深度缓冲区在上次操作时设定过的区域才能允许绘图,而第二个函 数调用表明,无论测试通过与否深度缓冲区的值都不会被改变。这样整个蒙板的实现就完成了。需要注意的时在使用完模板后为了其他区域的正常渲染我们需要使用 glDisable禁用蒙板。
二、剪裁空间
我们可以使用一个剪裁空间来控制允许绘图的空间区域。在OpenGL当中一般允许同时定义这样的6个空间。要使用剪裁空间我们需要首先用 GL_CLIP_PLANE调用glEnable来打开状态,然后用glClipPlane函数来设定空间方程。我们知道一个方程Ax+By+Cz+D = 0可以定义一个空间,而所谓定义剪裁空间方程是指,使用这个方程来乘以当前视图模型矩阵M的倒数,也就是说凡满足(A,B,C,D)/M>=0的顶 点点就允许被绘制,而其他的顶点就会被忽略。下面是一个定义剪裁空间的程序段,这个代码段允许在Y<=0的空间区域内绘图:
GLdouble eqr[]
=
...
{ 0 , - 1 , 0 , 0 }
;
glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE0, eqr);
glDisable(GL_CLIP_PLANE0);
// 前面提到的所谓允许6个剪裁空间就是类似于光照的操作使用GL_CLIP_PLANEi i从0-5
// glClipPlane的第二个参数指向一个包括4个元素的数组,这4个元素对应于方程的4个参数
glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE0, eqr);
glDisable(GL_CLIP_PLANE0);
// 前面提到的所谓允许6个剪裁空间就是类似于光照的操作使用GL_CLIP_PLANEi i从0-5
// glClipPlane的第二个参数指向一个包括4个元素的数组,这4个元素对应于方程的4个参数
三、应用:实现反射效果
使用上面讲的蒙板和剪裁平面我们可以很容易的实现物体在另一个平面上的反射效果。这里的核心是对于物体,当部分位于平面以下时我们要利用剪裁空间的操作将 位于平面以下的部分忽略掉,而对于反射物同样要通过剪裁平面剪裁掉位于平面之上的部分,另外还要利用蒙板来限制反射物的显示位置。而这种实现反射物的方法 使用的是将反射物与反射平面进行ALPHA通道的混合来实现。下面是具体的代码:
void
DrawWall()
... {
glBindTexture(GL_TEXTURE_2D,TexHandle[WALL]);
glBegin(GL_QUADS);
glNormal3f( 0 , 1 , 0 );
glTexCoord2f( 0 , 0 ); glVertex3f( - 2 , 0 , 2 );
glTexCoord2f( 1 , 0 ); glVertex3f( 2 , 0 , 2 );
glTexCoord2f( 1 , 1 ); glVertex3f( 2 , 0 , - 2 );
glTexCoord2f( 0 , 1 ); glVertex3f( - 2 , 0 , - 2 );
glEnd();
}
void DrawBall()
... {
glBindTexture(GL_TEXTURE_2D,TexHandle[BALL]);
gluQuadricNormals(quadric,GL_SMOOTH);
gluQuadricTexture(quadric,GL_TRUE);
gluSphere(quadric, 0.35 , 32 , 32 );
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
... {
GLdouble eqr[] = ... { 0 , - 1 , 0 , 0 } ; // 关于反射物体的剪裁空间方程
GLdouble eqr1[] = ... { 0 , 1 , 0 , 0 } ; // 关于球体的剪裁空间方程
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT); // Clear Screen / Depth / Stencil Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glTranslatef( 0 , - 0.6 ,zoom);
// 指定蒙板区域
glColorMask(GL_FALSE | GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1 , 1 );
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
DrawWall();
glEnable(GL_DEPTH_TEST);
// 使用蒙板
glStencilFunc(GL_EQUAL, 1 , 1 );
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE0, eqr);
glPushMatrix();
glScalef( 1 , - 1 , 1 );
glTranslatef( 0 ,height, 0 );
glRotatef(xRot, 1 , 0 , 0 );
glRotatef(yRot, 0 , 1 , 0 );
DrawBall();
glPopMatrix();
glDisable(GL_CLIP_PLANE0);
glDisable(GL_STENCIL_TEST);
// 混合地面与反射物体
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f( 1 , 1 , 1 , 0.8 );
DrawWall();
glDisable(GL_BLEND);
// 绘制球体
glClipPlane(GL_CLIP_PLANE0,eqr1);
glEnable(GL_CLIP_PLANE0);
glTranslatef( 0 ,height, 0 );
glRotatef(xRot, 1 , 0 , 0 );
glRotatef(yRot, 0 , 1 , 0 );
DrawBall();
glDisable(GL_CLIP_PLANE0);
return TRUE; // Everything Went OK
}
... {
glBindTexture(GL_TEXTURE_2D,TexHandle[WALL]);
glBegin(GL_QUADS);
glNormal3f( 0 , 1 , 0 );
glTexCoord2f( 0 , 0 ); glVertex3f( - 2 , 0 , 2 );
glTexCoord2f( 1 , 0 ); glVertex3f( 2 , 0 , 2 );
glTexCoord2f( 1 , 1 ); glVertex3f( 2 , 0 , - 2 );
glTexCoord2f( 0 , 1 ); glVertex3f( - 2 , 0 , - 2 );
glEnd();
}
void DrawBall()
... {
glBindTexture(GL_TEXTURE_2D,TexHandle[BALL]);
gluQuadricNormals(quadric,GL_SMOOTH);
gluQuadricTexture(quadric,GL_TRUE);
gluSphere(quadric, 0.35 , 32 , 32 );
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
... {
GLdouble eqr[] = ... { 0 , - 1 , 0 , 0 } ; // 关于反射物体的剪裁空间方程
GLdouble eqr1[] = ... { 0 , 1 , 0 , 0 } ; // 关于球体的剪裁空间方程
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT); // Clear Screen / Depth / Stencil Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glTranslatef( 0 , - 0.6 ,zoom);
// 指定蒙板区域
glColorMask(GL_FALSE | GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1 , 1 );
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
DrawWall();
glEnable(GL_DEPTH_TEST);
// 使用蒙板
glStencilFunc(GL_EQUAL, 1 , 1 );
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
glEnable(GL_CLIP_PLANE0);
glClipPlane(GL_CLIP_PLANE0, eqr);
glPushMatrix();
glScalef( 1 , - 1 , 1 );
glTranslatef( 0 ,height, 0 );
glRotatef(xRot, 1 , 0 , 0 );
glRotatef(yRot, 0 , 1 , 0 );
DrawBall();
glPopMatrix();
glDisable(GL_CLIP_PLANE0);
glDisable(GL_STENCIL_TEST);
// 混合地面与反射物体
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f( 1 , 1 , 1 , 0.8 );
DrawWall();
glDisable(GL_BLEND);
// 绘制球体
glClipPlane(GL_CLIP_PLANE0,eqr1);
glEnable(GL_CLIP_PLANE0);
glTranslatef( 0 ,height, 0 );
glRotatef(xRot, 1 , 0 , 0 );
glRotatef(yRot, 0 , 1 , 0 );
DrawBall();
glDisable(GL_CLIP_PLANE0);
return TRUE; // Everything Went OK
}