opengl - 如何实现"门户渲染"

Ber*_*iol 7 opengl rendering stencil-buffer

过去一周,我一直在努力实现类似游戏Antichamber(更准确地说是下面显示的这个技巧):

特技

这是我希望实现的视频(尽管它是使用虚幻引擎4完成的;我没有使用它):https://www.youtube.com/watch?v = Off3JcoWrMZs

我查找了最好的方法,我发现了模板缓冲区.在这篇文章和这个代码("drawPortals()"函数)之间,我在网上找到了我几乎实现它.

它可以很好地与一个门户到另一个房间(不是一个可交叉的门户,这意味着你无法穿过它并被传送到另一个房间).在我的例子中,我正在绘制一个门户到一个简单的方形房间,里面有一个球体; 在门户网站后面还有另一个球体,我用来检查深度缓冲区是否正常工作并将其绘制在门户网站后面:

面前

侧

当我添加另一个接近此门户的门户时,会出现问题.在这种情况下,我设法正确显示另一个门户(灯光关闭,但右侧的球体颜色不同,表明它是另一个球体):

在此输入图像描述

但是如果我转动相机以便第一个门户必须被绘制在第二个门户上,那么第一个门户的深度就会出错,而第二个门户将被绘制在第一个门户上,如下所示:

在此输入图像描述

虽然应该是这样的:

在此输入图像描述

所以,这就是问题所在.我可能在深度缓冲区做错了,但我找不到什么.

我的渲染部分的代码就是这样的:

glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);

// First portal
glPushMatrix();

// Disable writing to the color and depht buffer; disable depth testing
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);

// Make sure that the stencil always fails
glStencilFunc(GL_NEVER, 1, 0xFF);

// On fail, put 1 on the buffer
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);

// Enable writing to the stencil buffer
glStencilMask(0xFF);

// Clean the buffer
glClear(GL_STENCIL_BUFFER_BIT);

// Finally draw the portal's frame, so that it will have only 1s in the stencil buffer; the frame is basically the square you can see in the pictures
portalFrameObj1.Draw();

/* Now I compute the position of the camera so that it will be positioned at the portal's room; the computation is correct, so I'm skipping it */

// I'm going to render the portal's room from the new perspective, so I'm going to need the depth and color buffers again
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);

// Disable writing to the stencil buffer and enable drawing only where the stencil values are 1s (so only on the portal frame previously rendered)
glStencilMask(0x00);
glStencilFunc(GL_EQUAL, 1, 0xFF);

// Draw the room from this perspective
portalRoomObj1.Draw();

glPopMatrix();


// Now the second portal; the procedure is the same, so I'm skipping the comments
glPushMatrix();
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);

glStencilFunc(GL_NEVER, 1, 0xFF);
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
glStencilMask(0xFF);
glClear(GL_STENCIL_BUFFER_BIT);

portalFrameObj2.Draw();

/* New camera perspective computation */

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);

glStencilMask(0x00);
glStencilFunc(GL_EQUAL, 1, 0xFF);

portalRoomObj2.Draw();

glPopMatrix();


// Finally, I have to draw the portals' frames once again but this time on the depth buffer, so that they won't get drawn over; first off, disable the stencil buffer
glDisable(GL_STENCIL_TEST);

// Disable the color buffer
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glClear(GL_DEPTH_BUFFER_BIT);

// Draw portals' frames
portalFrameObj1.Draw();
portalFrameObj2.Draw();

// Enable the color buffer again
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);


/* Here I draw the rest of the scene */
Run Code Online (Sandbox Code Playgroud)

UPDATE

我设法找出问题所在,但我仍然无法解决问题.它实际上与深度缓冲区无关,而与模板有关.

基本上,我绘制第一个门户的方式是这样的:1)用1s填充模板缓冲区中的门户框架的位; 在门户外面只有0 2)绘制模板有1s的门户室(这样它就被绘制到框架的门户上)

我在第二个门户网站重复这个.

对于第一个门户网站,我在第1步得到这样的东西(原谅愚蠢的图纸,我很懒): 在此输入图像描述

然后在第2步之后:
在此输入图像描述

然后我从第二个门户开始:
在此输入图像描述

但现在,在步骤1和步骤2之间,我告诉模板仅绘制位为1的位置; 由于缓冲区现在被清除,我丢失了第一个门户网站1s的跟踪,所以如果第二个门户网站框架的一部分位于前一个框架的后面,第二个框架的1s仍将与第二个门户网站的房间一起绘制,无论深度缓冲区.有点像这张图片: 在此输入图像描述

我不知道我是否设法解释它...

Amr*_*ras 1

我晚了几年,但是在搜索这个问题时这个问题仍然出现,而且我也必须解决它,所以我想我会为其他过来的人放弃我的解决方案。

1. 不要清除深度缓冲区(帧间除外)

您的解决方案的主要问题是您定期清除整个深度缓冲区。每次你这样做时,你都会摆脱你可能需要弄清楚哪些门户要绘制以及哪些门户被遮挡的任何信息。您实际需要破坏的唯一深度数据位于设置模板的像素上;剩下的你可以保留。

在“穿过”门户绘制对象之前,请像这样设置深度缓冲区:

    // first, make sure you're writing to the depth buffer
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    glDepthMask(GL_TRUE);
    glStencilMask(0x00);
    // you can have the stencil enabled for this step, if you did anything fancy with it earlier (like depth testing)
    glStencilFunc(GL_EQUAL, 1, 0xFF);
    // the depth test has to be enabled to write to the depth mask. But don't worry; it'll always pass.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    // set the depth range so we only draw on the far plane, leaving a "hole" for later
    glDepthRange(1, 1);
    // now draw the portal object again
    portalFrameObj1.Draw();
    // and reset what you changed so the rest of the code works
    glDepthFunc(GL_LESS);
    glDepthRange(0, 1);
    glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); 
Run Code Online (Sandbox Code Playgroud)

现在,当您“通过”门户绘制对象时,它们将显示在需要的位置,但屏幕的其余部分仍将具有以前的深度信息!每个人都赢了!

当然,不要忘记像您所做的那样用第三个信息覆盖深度信息。 portalFrameObj1.Draw()这将对下一部分有所帮助:

2. 在设置模板之前检查您的门户是否被遮挡

在代码开始时,当您设置模板时,您将禁用深度测试。你不需要!

glStencilOp有三个参数:

  • sfail当模板测试失败时适用。
  • dpfail当模板测试成功但深度测试失败时适用。
  • dppass当模板和深度测试都成功(或者模板成功但深度测试被禁用或没有数据)时适用。

保持深度测试开启。设置glStencilFunc(GL_ALWAYS, 0, 0xFF);而不是GL_NEVER,因此模板测试成功,并使用dppass设置模板而不是sfailglStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

现在,您还可以尝试glStencilFuncSeparate稍微优化一下,但您不需要它来使门户相互遮挡。