OpenGL如何在模板测试失败且深度测试成功时写入模板缓冲区?

Posted

技术标签:

【中文标题】OpenGL如何在模板测试失败且深度测试成功时写入模板缓冲区?【英文标题】:OpenGL How to write to stencil buffer when stencil test fails and depth test succeeds? 【发布时间】:2020-11-25 18:15:14 【问题描述】:

我正在尝试在我的程序中实现一种效果,当它位于其他对象之后时,我会渲染一个对象的剪影,类似于这里的做法: https://i.pinimg.com/originals/87/8f/c4/878fc47a5bd6d62dfbaec3520cb4d9f5.jpg

我想要的是写入通过模板测试但未通过深度测试的立方体对象片段中的模板缓冲区。

这是我的实现:

glBindFramebuffer(GL_FRAMEBUFFER, this->screenFBO);
glEnable(GL_DEPTH_TEST);
glStencilOp(GL_KEEP,    // stencil fail
            GL_REPLACE, // stencil pass, depth fail
            GL_KEEP);   // stencil pass, depth pass

glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

glStencilMask(0x00);
glDisable(GL_STENCIL_TEST);
this->renderEntity(entityBuffer[0]); // skybox
glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);

this->renderEntity(entityBuffer[3]); // white cube

glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
// render rest of the models

帧缓冲区已完成,模板缓冲区按预期工作,因为我已经使用来自 https://learnopengl.com/Advanced-OpenGL/Stencil-testing 的教程进行了测试

但是按照我的设置方式,它不会向模板缓冲区渲染任何内容,我可以通过 RenderDoc 进行检查。

Code Output (the cube stays behind the sphere)

我做错了什么?并提前感谢您的时间!

【问题讨论】:

我设法使效果正常工作,尽管这样做我必须先渲染圆圈以在模板缓冲区中制作蒙版,然后禁用深度缓冲区并首先绘制模板所在的 silouette 立方体缓冲区为 1,然后是模板缓冲区为 0 的普通立方体,尽管这样做我会丢失有关对象深度的信息 【参考方案1】:

我终于让它完全正常工作了! 我的解决方案是首先在颜色缓冲区和深度缓冲区中绘制所有内容,而不是在模板缓冲区上(基本上只是在绘制和设置 glStencilMask(0) 之前启用深度测试) 然后绘制我想要突出显示的项目,确保我使用 glStencilMask(255) 将其写入模板缓冲区并确保它通过 glStencilFunc(GL_ALWAYS, 1, 255) 的模板测试

然后禁用深度测试以使用 glStencilMask(0) 渲染闪亮的轮廓/形状以通过其他对象渲染,并确保它在渲染到另一个立方体顶部时不会通过模板测试,使用glStencilFunc(GL_NOTEQUAL, 1, 255)

渲染闪亮的立方体,重新启用深度测试,它就可以工作了!

还要确保 glStencilOp 设置为:glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

这样,只有当写入模板缓冲区的项目没有被其他对象遮挡(深度测试失败)时,模板缓冲区才会被写入。

对我来说,最大的问题是弄清楚为什么即使我的对象会被其他对象遮挡,模板缓冲区也会被更新,而我没有想到的是我在其他对象之前渲染它,所以有渲染时没有什么可以遮挡它!因此,请确保先渲染场景,然后再渲染具有此属性的对象,这样您就可以使用 glStencilOp 功能

glBindFramebuffer(GL_FRAMEBUFFER, this->screenFBO);
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP,    // stencil fail
            GL_KEEP, // stencil pass, depth fail
            GL_REPLACE);   // stencil pass, depth pass

glStencilMask(255);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

glStencilMask(0);

this->renderEntity(entityBuffer[0]); // skybox

glStencilMask(0);
glStencilFunc(GL_ALWAYS, 1, 255);
this->renderEntity(entityBuffer[6]); // sphere
this->renderEntity(entityBuffer[1]);
this->renderEntity(entityBuffer[2]);
this->renderEntity(entityBuffer[4]);
this->renderEntity(entityBuffer[5]);
this->renderEntity(entityBuffer[7]);
this->renderEntity(entityBuffer[8]);
this->renderEntity(entityBuffer[9]);
this->renderEntity(entityBuffer[10]);
this->renderEntity(entityBuffer[11]);
this->renderEntity(entityBuffer[12]);

glStencilFunc(GL_ALWAYS, 1, 255);
glStencilMask(255);
this->renderEntity(entityBuffer[3]); // normal cube

glStencilMask(0);
glStencilFunc(GL_NOTEQUAL, 1, 255);

glDisable(GL_DEPTH_TEST);
entityBuffer[3]->setShader(5);
entityBuffer[3]->setScale(glm::vec3(1.05));
this->renderEntity(entityBuffer[3]); // white cube
entityBuffer[3]->setScale(glm::vec3(1.0));
entityBuffer[3]->setShader(0);
glEnable(GL_DEPTH_TEST);

result can be seen here

【讨论】:

我看到球体后面的盒子部分也是白色的(“轮廓”)。我猜原因是glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); 在深度测试失败时阻止了模板缓冲区的更新。因此,当第一次使用glStencilFunc(GL_ALWAYS, 1, 255); 渲染立方体时,球体后面的部分不会在模板缓冲区中变为 1。 glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE); 第一次填充立方体的模板缓冲区时将忽略深度缓冲区。

以上是关于OpenGL如何在模板测试失败且深度测试成功时写入模板缓冲区?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL ES 中的模板测试

在 OpenGL 中,(如何)我可以在两个深度缓冲区之间进行深度测试?

◮OpenGL-模板测试

◮OpenGL-模板测试

这个简单的 OpenGL/JOGL 模板测试有啥问题?

C++的fstream如何检查一个文件是不是成功打开?