Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一,其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/02%20Stencil%20testing/ 关于模板测试的详细知识了解请看原教程,本篇旨在对Vires基于visual studio平台的编程思想与c++代码做纯Qt平台的移植,重在记录自身学习之用
程序源代码链接:https://pan.baidu.com/s/1-oSUhlUAreL89e4Kr1w4rg 提取码:c1g4
编译环境:Qt5.9.4
编译器:Desktop Qt5.9.4 MSVC2017 64bit
IDE:QtCreator
Vries在上节深度测试用的的箱子场景基础上,继续引用了模板测试,准确来讲,片段的丢弃顺序应该是先判断模板测试,在判断深度测试,只有这些测试都通过的片段,才能被最终渲染到窗口。
打开线框模式,可以看出箱子轮廓是一个略大于箱子的正方体。
按照正常的深度测试来说,体积略大的轮廓正方体包住了箱子正方体,显示出的结果应该是图3这样,全是深绿色。而不是图3图4两图结合起来成为图1所示的那种感觉。图1这样的效果都是因为用了模板测试的功劳。
模板测试简单来说像是一个挑拣工具,也是根据一个缓冲来选择视口的某片段是否通过。这个缓冲叫做模板缓冲,在一个模板缓冲中,每个片段的模板值默认是8位的,即具有256个不同的模板值,根据某片段的某模板值与模板缓冲中对应位置的模板值对比,我们可以选择该片段是通过还是丢弃。
在上图这个例子里,我们认定模板值为“1”的片段通过测试,为“0”的片段丢弃。
通过
glEnable(GL_STENCIL_TEST);
开启模板测试,在这行代码之后,默认情况下,所有绘制的物体都会以某种值的方式写进模板缓冲,该值暂时未知,不知是否为0。
这里,Vries的教程里多了一句,
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
帧的每次迭代绘制,均需清理模板缓冲中的模板值,但因为QOpenGLWidget中paintGL()函数的特殊机制,他会自动清除每种 缓冲的缓冲值,所以不需要在代码中加这句话。
(1) glStencilMask(Gluint mask)
在glEnable(GL_STENCIL_TEST)开启后,用于控制物体的片段是否写进模板缓冲,mask作用在于设置一个掩码,与将写入模板缓冲的模板值做与(And)运算,常用的mask有两个值:
glStencilMask(0xFF); //And运算之后,写入模板缓冲的模板值不变,允许片段模板值写入模板缓冲
glStencilMask(0x00); //And运算之后,写入模板缓冲的模板值会是0,也就是禁用写入的意思。
(2) glStencilFunc(GLenum func, GLint ref, GLuint mask)
模板缓冲的条件比较函数,决定具有某模板值的片段在该比较函数条件下是否可以通过模板测试。
AND
ed with both the reference value and the stored stencil value before the test compares them. Initially set to all 1
s.)举例说明:
glStencilFunc(GL_EQUAL, 1, 0xFF); //这句代码意思为模板值为1的片段可以通过模板测试,其他模板值的片段被丢弃。
(3) glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
模板测试功能函数,确定了在何种情况下可以对模板缓冲内容做更改,即何时更新模板缓冲内容。
有三个参数:
每一种选项的行为有以下选择
默认情况下为glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP),即无论测试是否通过,均不会对模板缓冲内容做更改,这条规则下的模板测试,无论是否开启,视口最终渲染出的内容都是一样的。
文章一开始描述的箱子轮廓场景代码,其关键部分代码比较复杂,需要解释一下。
简要分为以下几步:
core->glEnable(GL_STENCIL_TEST); //一经开启,默认向模板缓冲里写缓冲
core->glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); //规定所有测试都通过后,使用ref值写进模板缓冲。
/********** 画地板 ************/
core->glStencilMask(0x00); //禁止将地板的片段写进模板缓冲
ResourceManager::getShader("plane").use();
core->glActiveTexture(GL_TEXTURE0);
ResourceManager::getTexture("metal").bind();
plane->draw(GL_TRUE);
/********** 画两个箱子,并将其片段的模板值模板缓冲里,即模板缓冲只允许这两个箱子所占的区域是“1” ************/
core->glStencilFunc(GL_ALWAYS, 1, 0xFF); //允许以下绘制的所有片段以“1”的值写进缓冲
core->glStencilMask(0xFF);//可以写进模板缓冲
QMatrix4x4 model;
model.translate(-1.0f, 0.0001f, -1.0f);
ResourceManager::getShader("cube").use().setMatrix4f("model", model);
core->glActiveTexture(GL_TEXTURE0);
ResourceManager::getTexture("marble").bind();
cube->draw(GL_TRUE);
model.setToIdentity();
model.translate(2.0f, 0.0001f, 0.0f);
ResourceManager::getShader("cube").use().setMatrix4f("model", model);
cube->draw(GL_TRUE);
/********** 画箱子的轮廓,轮廓比箱子稍大,因为箱子所占的模板缓冲值是“1”, 所以在不允许在“1”的地方画轮廓 ************/
core->glStencilMask(0x00);//关闭写进模板缓冲,现在模板缓冲里只有两个箱子的缓冲值是“1”
core->glDisable(GL_DEPTH_TEST); //关掉深度测试保证了箱子的轮廓是完整的
core->glStencilFunc(GL_NOTEQUAL, 1, 0xFF); //规定在模板缓冲中“1”所占的位置,片段不允许通过
model.setToIdentity();
model.translate(-1.0f, 0.0001f, -1.0f);
model.scale(1.1f);
ResourceManager::getShader("cube_SingleColor").use().setMatrix4f("model", model);
cube->draw();
model.setToIdentity();
model.translate(2.0f, 0.0001f, 0.0f);
model.scale(1.1f);
ResourceManager::getShader("cube_SingleColor").use().setMatrix4f("model", model);
cube->draw();
core->glStencilMask(0xFF);//重新开启写进模板缓冲
core->glStencilFunc(GL_ALWAYS, 1, 0xFF); //因为glClear()函数并不能清除掉模板缓冲,所以我们需要还原缓冲模板缓冲功能函数glStencilFunc
core->glEnable(GL_DEPTH_TEST); //重新开启深度测试