每个像素都有自己对应的 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);
//****************************************************************//