深入了解OpenGL——模板测试

自己总结:1个比较(参考值和mask的&操作结果  和  当前模版值和mask的&操作结果 的 比较)

  1个规则(比较成功则片段保留,失败则丢弃)

          2个函数(glStencilFunc,glStencilOp),

  6个参数(比较类型,参考值,掩码值,模版失败的模版值处理,模版成功深度失败,模版成功深度成功),

  


我们在用OpenGL绘图时往往想制作一些复合图形以及凹凸多边形,像五角星、大的矩形里再画一个小的矩形;另外有时还想做些镂空图或类似的效果。

这时我们可以开启模板测试功能来完成这些需求。
由于OpenGL的一个绘制流水线特征以及GPU着色特征,像对于大矩形里画小矩形这种不能通过先绘制大矩形,然后再在其中绘制小矩形的方式进行绘制,否则当此复合图形在做一些旋转时,我们会发现小矩形部分会有非常严重的抖动。


模板测试的一个基本的特性是:它基于在某个坐标位置的模板缓存值与一个参考值相比较的结果,有条件地废弃一个片断。这里要注意的是,这个判定是基于当前被测试的片断(而不是已在帧缓存的像素),并且当前片断的每个像素都会进行模板测试,并且如果某些像素测试失败的话,这些像素就会被废弃。
下面介绍一下如何开启模板测试。
对于Mac OS X下,对你的OpenGL视图的属性中选择模板缓存的位数即可,模板缓存只有8位这一个选项。在iOS的OpenGL ES下,则需要建立一个模板缓存,建立方法与建立深度缓存类似。
然后,我们在代码中用glEnable(GL_STENCIL_TEST);来开启模板测试。


图解:

深入了解OpenGL——模板测试_第1张图片


下面谈谈模板测试是如何工作的。一开始,我们用glClear(GL_STENCIL_BUFFER_BIT);来清除模板缓存的值,使这些值均为0(默认设置,也无须修改)。当我们用类似glDrawArrays这些函数来绘制某个图形后,被绘制的图形先被光栅化,然后得到相应的片断。我们指定一个参考值,一个掩模值,跟当前的模板缓存的值进行比较。
如何比较呢?下面给出一段伪代码:
?
unsigned refValue = inputReferenceValue & mask;
unsigned stencilValue = currentStencilValue & mask;
BOOL isSuccessful = refValue  stencilValue;
currentStencilValue = updateStrategy(inputReferenceValue, currentStencilValue, isSuccessful);

更快的计算方式:

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

glStencilFunc(GL_LESS, 3, mask);


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



我们首先用自己指定的参考值与掩模值进行按位与操作,得到的结果和当前对应的模板值与掩模值的按位与的结果进行比较,比较函数也由我们自己指定。如果比较的结果是TRUE,即测试成功,当前的像素被保留;否则,测试失败,当前的像素被废弃。我们还可以指定在模板测试成功或失败后如何更新模板值。


下面介绍上述操作过程所涉及到的一些接口:

void glStencilFunc(GLenum  func, GLint  ref, GLuint  mask);
func是个枚举类型,它指定了比较操作,值可以是: GL_NEVER ,   GL_LESS ,   GL_LEQUAL ,   GL_GREATER ,   GL_GEQUAL ,   GL_EQUAL ,   GL_NOTEQUAL GL_ALWAYS
ref就是参考值。而mask就是掩模值。如果模板位数是8的话,那么mask最大值是0xff,即8个1;最小值是0。

void glStencilOp(GLenum  sfail, GLenum  dpfail, GLenum  dppass);
这个函数指定了对模板值的更新操作。这三个参数所接受的枚举值都是同一组: GL_KEEP , GL_ZERO , GL_REPLACE , GL_INCR , GL_INCR_WRAP , GL_DECR , GL_DECR_WRAP GL_INVERT。
sfail说明当模板测试失败后,用何种方式更新模板值;dpfail说明当模板测试成功,但深度测试失败后用何种方式更新模板值;dppass说明当模板测试与深度测试均成功后用何种方式更新模板值。

GL_ZERO表示用0来更新模板值;GL_KEEP说明保留原来的模板值;GL_REPLACE表明用参考值替代原来的模板值。


下面给出代码,演示一下如何在一个大正方形中绘制一个小正方形。

static GLfloat vertices[] = {
    -0.5f, 0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.5f, -0.5f, 0.0f,
    
    -0.2f, 0.2f, 0.0f,
    -0.2f, -0.2f, 0.0f,
    0.2f, 0.2f, 0.0f,
    0.2f, -0.2f, 0.0f
};
 
    // Drawing code here.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 
    glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);


?


上述代码中,我们首先要绘制的是小正方形。我们指定的比较操作是NOT EQUAL,即参考值与模板值与mask相与后进行比较,如果不等于,则为测试成功,否则为测试失败。由于一开始,模板值都是0,而参考值是1,因此“不等于”比较操作始终成功。而我们用的模板值的替换策略是:若测试成功,那么用参考值来替换模板值;而测试失败,则保留原来的模板值。由于在小正方形绘制后,整个片断测试完全成功,因此小正方形所对应的区域的模板值都将被替换成1。然后画大的正方形。由于绘制大正方形时,其与小正方形重叠的模板值已经是1,因此这块区域对大正方形片断而言,测试失败(1 NOT_EQUAL 1的布尔值是假),因此这块片断被废弃,而其它区域由于对应模板值尚都为0,因此都能比较成功。这样,我们就呈现出了大正方形中套小正方形的效果。
下面添上完整的工程。

附件:  OpenGLTest.zip 


镂空的正方形

其实这个思路也很简单。我们先用一个小正方形(被镂空部分)填充模板缓存,并且要使它测试失败,这样才能留住背景色。
然后再画大正方形的时,用GREATER进行比较,这样,周围的像素都能用大正方形的片断像素,而不是背景像素,而与小正方形的重叠部分,即镂空部分比较失败,这样这部分区域就可以保留背景色。下面的代码仍然只提供顶点以及绘制部分。

staticGLfloat vertices[] = {
    -0.2f, 0.2f, 0.0f,
    -0.2f, -0.2f, 0.0f,
    0.2f, 0.2f, 0.0f,
    0.2f, -0.2f, 0.0f,
     
    -0.5f, 0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
};
 
staticGLfloat colors[] = {
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 1.0f, 1.0f,
     
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 1.0f, 1.0f
};
 
- (void)drawRect:(NSRect)dirtyRect {
     
    // Drawing code here.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 
    glStencilFunc(GL_EQUAL, 0x1, 0x1);
    glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
     
    glStencilFunc(GL_GREATER, 0x1, 0x1);
    glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
   
    glFlush();
}


你可能感兴趣的:(OpenGL)