我的总结:
模板缓存,就是规定一个0和1组成的矩阵,里面写了1的地方才能显示。每次渲染一个模型,这个模型占用的无数个片元格子,就会自动在stencil buffer里面置为1存储起来,用于下一帧来显示.或者这一帧直接拿这个矩阵来进行操作,来配置某些区域的显示和不显示。
一旦片段着色器处理了片段,就会执行所谓的模板测试,就像深度测试一样,可以选择丢弃片段。 之后剩余的片段被传递到深度测试,OpenGL 可能会丢弃更多的片段。 模板测试基于另一个称为模板缓冲区的缓冲区的内容,我们可以在渲染期间更新该缓冲区以实现有趣的效果。
模板缓冲区首先被清零,然后一个 包含多个1 的开放矩形存储在模板缓冲区中。 然后场景的片段只会在该片段的模板值包含 1 的地方被渲染(其他片段被丢弃)。
通过它我们可以实现很多的特效,例如轮廓、镜面效果,阴影效果等
基础知识:
1.Stencil functions
与深度测试类似,我们对模板测试何时应该通过或失败以及它应该如何影响模板缓冲区有一定的控制权。 我们总共可以使用两个函数来配置模板测试:glStencilFunc 和 glStencilOp。
1.1 用来输出到屏幕的glStencilFunc(根据模板缓冲决定丢弃片段)
注意:这个二进制0xFF是一个掩码, set a bitmask that is AND
ed with the stencil value,会和结果进行AND运算,之后的结果才会传出来。
glStencilFunc(GL_EQUAL, 1, 0xFF)
这告诉 OpenGL,只要片段的模板值等于 (GL_EQUAL) 参考值 1,片段就会通过测试并被绘制,否则将被丢弃。
但是 glStencilFunc 只描述了 OpenGL 是否应该根据模板缓冲区的内容传递或丢弃片段,而不是我们如何实际更新缓冲区。 这就是 glStencilOp 的用武之地。
1.2 用来存储模板信息的glStencilOp(更新和更改缓冲区)
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
因此,使用 glStencilFunc 和 glStencilOp,我们可以根据其内容精确指定何时以及如何更新模板缓冲区,以及何时传递或丢弃片段。
2.Object outlining (对象边缘)
creating a small colored border around the (combined) objects.
- Enable stencil writing.(允许使用stencil)
- Set the stencil op to GL_ALWAYS before drawing the (to be outlined) objects, updating the stencil buffer with
1
s wherever the objects' fragments are rendered.(重启stencil函数到GL_ALWAYS ,给渲染了的地方,配置好stencil缓存,内容为多个1) - Render the objects.(渲染物体)
- Disable stencil writing and depth testing.(关闭stencil写入,和深度测试)
- Scale each of the objects by a small amount.(少量缩放对象)
- Use a different fragment shader that outputs a single (border) color.(用另一个shader输出边缘)
- Draw the objects again, but only if their fragments' stencil values are not equal to
1
.(再绘制一遍obj,但是只绘制片段的stencil值不等于1的) - Enable depth testing again and restore stencil func to GL_KEEP.(重新开启深度测试,并且重启stencil函数到GL_KEEP)
代码分析:
//开启深度测试
glEnable(GL_DEPTH_TEST);
//如果模板测试(这个地方有东西存在)和深度测试(没被挡住)都成功,将存储的模板值替换为,通过 glStencilFunc 设置的参考值(1)
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
//清除所有Buffer内容
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//绘制地板的时候,关闭模板缓冲
glStencilMask(0x00); // make sure we don't update the stencil buffer while drawing the floor
//绘制地板
normalShader.use();
DrawFloor()
//开启模板缓冲,并此时渲染两个箱子,箱子的模板中的0,1数据会缓存到模板缓存中
glStencilFunc(GL_ALWAYS, 1, 0xFF);
//开启对模板buffer的写入
glStencilMask(0xFF);
DrawTwoContainers();
//把箱子放大1.1倍,然后与缓冲区进行对比,不相同的地方(其实就是描边部分),拿出来变成1
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
//关闭对模板buffer的写入
glStencilMask(0x00);
//关闭深度测试
glDisable(GL_DEPTH_TEST);
//绘制两个1.1倍的箱子
shaderSingleColor.use();
DrawTwoScaledUpContainers();
//重新开启对模板buffer的写入(用于下一帧,相当于复原)
glStencilMask(0xFF);
//任何有东西的地方,都会输出1
glStencilFunc(GL_ALWAYS, 1, 0xFF);
//开启深度测试
glEnable(GL_DEPTH_TEST);