[分享干货晒技术]OpenGL模版缓存

发表于2015-07-29
评论0 1.7k浏览

  UGUI的Mask给我们制作UI特效提供了很多便利之处,但是一直不太明白其原理。之前看过OpenGL,直觉就是使用模版缓存(Stencil Buffer)来进行实现的,但是一直没确定;今天开周会的时候讨论了UGUI的shader,才最终确定了确实如此。这里通过OpenGL的API的介绍来介绍下模版缓存的原理,以及Mask的实现机制。

     首先,模版缓存类似深度缓存,但是用于模版测试,无论是固定管线还是可编程管线,模版测试都是可以通过设置来启用的。
     其次,模版测试的原理简单说来就是使用一个参考值和模版缓存中的当前像素的位置对应的模版缓存值进行比较,如果通过这个比较则该片段(fragment)会继续进行后续的步骤,最终可能会写到颜色缓冲区中;否则该片段永远不会被写到最终的颜色缓冲区中。
     然后模版缓存的使用过程一般是两次渲染,即分为两个Pass进行渲染:
     1. 第一步渲染一个mask区域,将mask区域的值写到stencil buffer中;
     2. 渲染真正需要显示的内容,渲染时和stencil buffer中的值进行比较,通过则继续后面的操作,否则丢弃该片段。
     介绍了大概流程之后,我们通过OpenGL的API来尝试了解其具体工作流程。
     void glStencilFunc(GLenum func, GLint ref, GLuint mask);
     该函数用于设置模版测试的比较函数和相关参数,具体操作就是fun(ref & mask, stencil value & mask)。func是比较函数,有如下类型可选:GL_NEVER(始终不通过), GL_ALWAYS(始终通过), GL_LESS(小于则通过), GL_LEQUAL(小于等于则通过), GL_QUAL, GL_GEQUAL, GL_GREATER和GL_NOTEQUAL。ref是用来进行比较的参考值,最后的比较就是用这个ref和模版缓存中的对应像素的模版缓存值进行比较。mask是一个掩码,指定使用ref和模版缓存的哪些位进行比较。
     void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass);
     该函数用于指定当一个片段通过或未通过模版测试时,模版缓存中的值应该如何进行修改。fail, zfail和zpass这三个函数可以是:GL_KEEP(保持当前模版缓存值不变), GL_ZERO(模版缓存置零), GL_REPLACE(用参考值代替当前值),GL_INCR( ), GL_INCR_WRAP( 超出则回绕), GL_DECR(--), GL_DECR_WRAP(--超出则回绕), GL_INVERT(反转各位)。fail,zfail和zpass仅有一个会被选中执行:如果没有通过模版缓存则执行fail函数,如果通过了模版测试但是没有通过深度测试则执行zfail, 模版测试和深度测试都通过则执行zpass。
     
     下面通过一个例子来讲解绘制过程。
     我们需要绘制的物体是一个在一个四边形mask(黑色细线区域)区域中的一个红色椭圆,我们希望的是红色椭圆在mask范围内的会被显示出来,其他部分不会显示出来,如下图所示。
     为达到此目的,我们的渲染过程如下:
     glClear(GL_STENCIL_BUFFER_BIT);     // 清空模版缓冲中的值,即模版缓存全部为0
     glStencilFunc(GL_ALWAYS, 0x1, 0x1); // 让后面的绘制操作始终通过模版测试,ref设置为1,mask设置为1
     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // 全部使用ref替换当前的模版缓存
     glBegin(...);
          glVertex(...);
          glVertex(....);
          ...
     glEnd(...);                                        // 绘制四边形,一次drawcall
     glStencilFunc(GL_EQUAL, 0x1, 0x1);   // 使用等于1操作进行模版测试
     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);               // 绘制后不改变模版缓存,以备后用
      glBegin(...);
          glVertex(...);
          glVertex(....);
          ...
     glEnd(...);                                        // 绘制椭圆,一次drawcall
     通过上述操作即可实现mask的效果,注意在第一次drawcall后,模版缓存的值如下图所示:
     这里只是简单介绍了一下模版缓存(stencil buffer)和模版测试(stencil test)的原理,后面看看UGUI的UI/Default Shader后在结合UGUI的实际情况再做实际探讨。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引