上一章给大家介绍了深度缓存,而模板缓存是深度缓存的扩充,当你需要控制哪一个像素需要被渲染,哪一个像素需要被忽略时,模板缓存能够为你提供更多的方法。和深度缓存一样,模板缓存存储了所有像素的模板值,但是这次你必须手动控制这些值如何改变。记住,如果一个像素深度测试失败了的话,模板测试将不会再决定该像素是否绘制,而像素会反过来影响模板缓存中的值。
为了让大家对模板缓存有一个直观的认识,让我们来分析一下下面这个例子:
如上图所示:模板缓存首先被清0,我们的立方体将正常显示,然后我们将模板缓存中的一个矩形区域设置为1,我们的立方体在绘制时,我们将只绘制模板值为1的像素区域,从而达到控制像素绘制与否的目的。现在你对模板缓存的作用有了一个初步了解了,让我们来看看OpenGL中是如何使用它的:
和深度缓存一样,首先你要启动模板测试来让模板缓存生效,
设置模板值
然后需要设置缓存中各个像素的模板值, 我们在确定模板的形状后,我们通常只需要调用一次绘制命令就可以达到利用模板裁剪画面的效果,如果你想像上面的例子那样创建一个矩形的裁剪区域,只需要绘制一个2D的矩形区域就行了。只不过在你绘制之前需要使用以下几个API来控制一下模板缓存的值:
首先是glStencilFunc,这个api是用来模板测试使用的比较函数以及参数的,参数如下:
func:比较函数
ref: 比较函数用来比较的参数值。
mask: 掩码,如果模板缓存包含了s个位平面,那么mask参数中较低的s个位数据将分别于模板缓存中的值,以及ref值进行位与操作,然后再进行具体的比较。
第一个参数中的比较函数可以是以下任意一种:
GL_NEVER:无论模板值为何值,都不能通过模板测试
GL_LESS:测试模板值是否小于ref值
GL_LEQUAL:测试模板值是否小于等于ref值
GL_GREATER:测试模板值是否大于ref值
GL_GEQUAL:测试模板值是否大于等于ref值
GL_EQUAL:测试模板值是否等于ref值
GL_NOTEQUAL:测试模板值是否不等于ref值
GL_ALWAYS:无论模板值为何值,总是能通过模板测试。
例如:如果你不想让小于2的模板值通过模板测试,你只需要这样调用:
然后是glStencilOp,这个api是用来指定深度测试以及模板测试以后对模板值的操作,参数如下:
sfail: 模板测试失败后执行的操作
dpfail: 模板测试通过但深度测试不通过时候执行的操作。
dppass: 模板测试和深度测试都通过后,或者深度测试未开启时执行的操作。
以上三个参数的值可以是以下任意一种:
GL_KEEP: 保持当前值
GL_ZERO: 将模板值清0
GL_REPLACE: 模板值将会被替换为glStencilFunc中的ref参数值。
GL_INCR: 如果模板值小于最大值,模板值将会+1
GL_INCR_WRAP: 和GL_INCR一样,只是遇到最大值时,模板值会清0。
GL_DECR:如果模板值大于0,模板值将会-1。
GL_DECR_WRAP: 和GL_DECR一样,只是当模板值减到0时,会变为最大值。
GL_INVERT: 将模板值按位取反。
最后,glStencilMask可以为模板缓存设置掩码,由于模板缓存中每个模板值是一个无符号的整形数,因此我们的掩码也是一个0-0xFFFFFFFF范围内的数,每个模板值都会与掩码做与运算后再使用,默认掩码为0xFFFFFFFF。
现在回到我们的例子,我们要把一个矩形区域的模板值设置为1,我们需要进行如下操作:
这里我们可以看到,我们通过glStencilFunc函数让任意模板值都能通过模板测试,这样如果深度测试也通过的话就会根据glStencilOp的第三个参数设置的GL_REPLACE将模板值都替换为ref参数值1,从而达到了将所有模板值设置为1的目的。而图中的矩形区域并不需要真正的绘制,它只需要用来指定哪些像素的模板值生效,这样就不需要我们去逐像素的去设置模板缓存值了。
glColorMask函数允许你指定哪些数据输入颜色缓存,4个参数分别对应RGBA值,这里我们将4个参数都置为GL_FALSE,意味着所有颜色数据都不会输入到颜色缓存中。而深度缓存需要另外调用glDepthMask并传入GL_FALSE参数来屏蔽深度值。这样我们矩形区域外的所有像素的颜色和深度都被屏蔽了,所以我们的绘制就不会影响到这些像素。这样操作操作比调用glClear来清除颜色和深度缓存要干净得多。
绘制中使用模板值
在了解了如何设置模板缓存后,使用模板缓存变得非常简单。你只需要通过一个测试函数来决定哪些像素需要绘制,然后将之前禁用的深度和颜色重新启用就可以了。
如果你调用这个方法来设置了测试函数,只有模板缓存中值为1的像素能通过这个测试。一个像素只有在通过深度测试和模板测试后才能绘制,所以没有必要去设置glStencilOp。在例子中,只有那个矩形区域中的模板值被设置为1,所以只有在这个区域中的像素会被绘制。
一个小细节需要注意的是,我们在绘制我们的立方体时,仍会影响到模板缓存中的值,为了避免这种影响,我们将模板缓存的掩码设置为0。这样一来,我们以后的任何写入操作都会被屏蔽掉。
镜面反射的例子
现在我们在我们绘制的立方体下方加入一个反射镜面,首先在立方体的顶点数组中添加如下数据:
然后,在绘制立方体过后单独绘制这个镜面:
现在我们创建一个反射效果,只需要将原有的立方体向下平移并相对Z轴翻转就行了:
这里将反射面的颜色设置为黑色,这样就不会有纹理图片能够通过这个反射面。绘制效果如下:
有两个值得一提的地方:
1:下方的立方体被反射面遮住是因为未能通过上一章我们提到的深度测试。
2:超出反射面范围的立方体依然能够被看见
第一个问题很好解决,只需要临时屏蔽掉向深度缓存写入值就可以了:
第二个问题通过控制深度缓存似乎不好解决,我们需要屏蔽掉反射面范围之外的那些像素,所以我们的模板缓存就可以一展身手了!
我们可以将整个绘制过程细分为以下几个阶段,以便我们分析每个阶段应该做什么:
1.绘制上方的立方体
2.打开模板测试,再将我们需要绘制区域的模板值写成1。
3.绘制反射面
4.设置模板函数,让模板值为1的像素通过模板测试。
5.绘制一个颠倒的立方体。
6.关闭模板测试。
绘制代码如下:
最后我们只需要将反射面上的图像灰化,这需要在我们的片段着色器中实现,overrideColor是用来灰化图案的颜色:
然后在绘制我们的反射立方体时传入一个灰度值,uniColor是glGetUniformLocation的返回值:
大功告成!我们只用最基本的OpenGL的API就可以实现一个看似很复杂的效果,很神奇吧!
最后,如果需要完整代码,或者有任何疑问,请联系我们,我们的QQ群是:280689979