OpenG:Reflection-Stencil Buffer

每个像素都有自己对应的 Buffer,其实就是一个 32bit 的数,如 Color Buffer, Depth Buffer, Stencil Buffer. Stencil Buffer 与 Depth Buffer 有点特别,因为他们共用同一个 Buffer, Depth Buffer 占用 Buffer 前面的 24Bit, Stencil Buffer 占用后面的 8Bit. Stencil Buffer 可以使用从 1Bit-8Bit. 如在绘制反射时,就像照镜子一样,因为只需要在反射平面上绘制物体的镜像,即要么在反射平面上绘制,要不就不绘制,所以只需要用到 1Bit 的 Stencil Buffer.

 

什么叫 Stencil Buffer ?

即是一个模板,也就是说,他可以是一个平面,也可以是一个立体几何图形,如一个四边形,一个Teapot. 在模板所占据的空间中,他的值为 1(values stored in the stencil buffer), 在启用 Stencil Buffer 时,我们所画的图形只有在这个空间中的部分才能显示出来,所以我们可以创建一个模板,他是一个字,然后以后画的图形最多只能把这个字给显示出来,这个图形有其他部分都没有被写进 Color Buffer.

 

Stencil Buffer 最简单的运用,用来生成镜面反射。

1. 先要使编程环境支持 Stencil Buffer

        glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);

 

2. 设置清除 Stencil Buffer 使用的函数

        glClearStencil(0);

 

3. 在我们创建模板的时候,要先关掉 Depth Test und Color Mask,

        因为我们并不想把模板画到屏幕上

        glDisable(GL_DEPTH_TEST);

        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

        // 为了把我们的模板图形不显示到屏幕上,但又要写入 Stencil Buffer 中。

 

        我们什么时候创建模板的?就是在启用模板缓存后进行的第一次进绘制的图形就是模板。

        OpenGL会根据我们所设定的 glStencilFunc 的值和 glStencilOp 来比较,

        然后在 plane 中(即视口所对应的那个二维数组)写入比较的结果值。

 

4. 开始创建模板

        glEnable(GL_STENCIL_TEST);

        glStencilFunc(GL_ALWAYS, 0x1, 0x1);

        // 把模板图形所在的区域的 Buffer 值设置成 1, 其余的还是 0.

        // 这时用的就是给 glStencilFunc 指定的 ref 的值,现在是 1

        // 当然可以有其他的操作,如 GL_INCR, GL_INVERT(bitwise invert)

        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

       

        // 开始创建模板的图形

        drawFloor();

       

        // 模板创建好后,我们就要设置下一次进行绘制时的模板函数

        // 只有通过条件的像素才能被显示到屏幕上,否则就被丢弃

        // 但要注意,现在我们要进行绘制的就是镜像了,所以是要被显示到屏幕上的,

        // 所以在绘制之前,要把颜色屏蔽关掉和启用深度测试

        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

        glEnable(GL_DEPTH_TEST);

 

        // Stencil buffer 值等于 1 的地方才绘制到屏幕上       

        glStencilFunc(GL_EQUAL, 0x1, 0x1);

        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

 

5. 绘制镜像图像

        // 现在绘制我们的镜像图像

        // 镜像是跟原来的物体对称的, 所以用 glScalef 来进行反转,实现对称

        // 在绘制镜像物体的时候,灯光也要相应的反转

        glPushMatrix();

                glScalef(1, -1, 1);

                glutSolidTeapot(1.0f);

        glPopMatrix();

 

6. 在模板中显示的镜像图像已经创建好,不再需要模板了,所以我们关掉 stencil buffer

        glDisable(GL_STENCIL_TEST);

 

7. 绘制镜像所在的平面,就如镜子

        // 使用 Blend 与镜像图像混合起来

        glEnable(GL_BLEND);

        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        drawFloor();

        glDisable(GL_BLEND);

 

8. 绘制产生镜像的物体

        glutSolidTeapot(1.0f);

 

至此,真正的镜面反射已经创建完成。

非真正的反射可以如下实现:

先画对称物体,画出镜面(使用 Blend), 然后画出原物体,但这时如果旋转Camera,就会发现,那个对称的物体并不是平面的,还是原来的空间立体物体。但用 Stencil Buffer 实现的镜面反射是真正的镜面反射,镜像是只在镜面上显示,即是平面的。

 

下面的代码可以很好的工作

 //****************************************************************//

 if (useStencil) {
          glClearStencil(0);
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
 } else {
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 }

 if (useStencil) {
          glDisable(GL_DEPTH_TEST);
          glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

          /* Draw 1 into the stencil buffer. */
          glEnable(GL_STENCIL_TEST);
          glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
          glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

          /* Now render floor; floor pixels just get their stencil set to 1. */
          drawFloor();

          /* Re-enable update of color and depth. */
          glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
          glEnable(GL_DEPTH_TEST);

          /* Now, only render where stencil is set to 1. */
          glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
          glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 }

 glPushMatrix();
          glScalef(1, -1, 1);
          glTranslatef(0.0, 0.8, 0);
          glColor3f(0, 1, 0);
          glutSolidTeapot(1);
 glPopMatrix();

 

 if (useStencil) {
          glDisable(GL_STENCIL_TEST);
 }

 glEnable(GL_BLEND);
 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 glColor4f(0.7, 0.0, 0.0, 0.3);
 drawFloor();
 glDisable(GL_BLEND);

 

 glTranslatef(0, -0.0001, 0);
 glFrontFace(GL_CW);
 glColor3f(1, 1, 1);
 drawFloor();

 

 glTranslatef(0.0, 0.8, 0);
 glColor3f(0, 1, 0);
 glutSolidTeapot(1);
 //****************************************************************//