opengl -如何实现“门户渲染”

2cmtqfgy  于 2022-11-04  发布在  其他
关注(0)|答案(2)|浏览(209)

在过去的一周里,我一直在尝试实现类似于游戏Antichamber(更精确地说,这个技巧如下所示)的东西:

这里有一个视频,我希望实现(即使它是用虚幻引擎4;我不使用那个):https://www.youtube.com/watch?v=Of3JcoWrMZs
我查找了最好的方法,并找到了模板缓冲区。在this文章和this代码(“drawPortals()”函数)之间,我在网上找到了几乎实现它的方法。
它可以很好地处理一个到另一个房间的传送门(不是可穿越的传送门,意味着你不能穿过它并被传送到另一个房间)。在入口后面有另一个球体,我用它来检查深度缓冲区是否正常工作,并在入口后面绘制它:

当我在这个入口附近添加另一个入口时,问题就出现了。在这种情况下,我设法正确显示了另一个入口(灯光关闭,但右边的球体颜色不同,以显示它是另一个球体):

但如果我转动摄像机,使第一个入口必须画在第二个入口上,那么第一个入口的深度就不对了,第二个入口就会画在第一个入口上,就像这样:
x1c4d 1x指令集
而它应该是这样的:

所以,这就是问题所在。我可能在深度缓冲区上做了一些错误的事情,但是我找不到是什么。
我的渲染部分代码大致如下:

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 */

更新

我设法找出了问题所在,但我仍然无法解决它。它实际上与深度缓冲区无关,而是与模板有关。
基本上,我画第一个入口的方法是这样的:1)用1填充模板缓冲区中的门户框架的位;在门户外只有0 2)在模具有1的地方绘制门户房间(以便将其绘制到框架的门户上
我对第二个入口重复这一点。
对于第一个门户,我在第一步得到这样的东西(原谅愚蠢的图纸,我很懒):

指令集
然后在步骤2之后:

然后我从第二个门户开始:


指令集
但是现在,在步骤1和2之间,我告诉模具只在位为1的地方绘制;由于缓冲区现在被清空了,我失去了第一个门户的1的跟踪,所以如果第二个门户的部分帧在前一个帧之后,第二个帧的1仍然会被绘制在第二个门户的房间里,而不管深度缓冲区是多少。

指令集
我不知道我是否解释对了...

yrwegjxp

yrwegjxp1#

现在已经有一段时间了。因为不会有答案(可能),我写了我现在使用的变通方案。
我尝试使用不同的模板函数来解决这个问题,并且在呈现新门户时不清空模板缓冲区;这个想法是利用我通过查看模板缓冲区来知道先前的门户在哪里被渲染的事实。
最后,我没能成功;在我最初的问题中的例子中,两个入口中的一个总是遮蔽了另一个的部分。
所以,我决定做一些简单的事情:我检查门户是否可见,然后按照问题中发布的代码绘制它(因此每次都清空模板缓冲区)。
在伪代码中,我这样做:

for(var i = 0; i < PORTALS_NUMBER; i++) 
{
    // I get the normal to the portal's frame and its position
    var normal = mPortalFramesNormals[i];
    var framePos = mPortalFrames[i].GetPosition();

    // I compute the scalar product between the normal and the direction vector between the camera's position and the frame's position
    var dotProduct = normal * (currentCameraPosition - framePos);

    // If the dot product is 0 or positive, the portal is visible
    if(dotProduct >= 0)
    {
        // I render the portal
        DrawPortal(mPortalFrames[i], mPortalRooms[i]);
    }
}

glDisable(GL_STENCIL_TEST);

// Now I draw the portals' frames in the depth buffer, so they don't get overwritten by other objects in the scene
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);

for(var i = 0; i < PORTALS_NUMBER; i++)
{
    mPortalFrames[i].Draw();
}

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

在我的情况下,我需要显示4个门户网站在总共定位为一个立方体(所以我知道,最多2个门户网站是直接可见的时间),只有一个面的框架显示门户网站,而另一个没有,它的工作完美。
我知道可能有一种更优雅的方法,只使用模板缓冲区,但现在这对我很有效。
如果有人知道一个更好的方法来做到这一点,我仍然有兴趣知道!

**EDIT:**我发现了其他版本的模板函数(例如,glStencilFuncSeparate、glStencilOpSeparate、glStencilMaskSeparate),它们允许对背面和正面执行不同的操作。这似乎是我需要解决的问题,但我无法在短期内尝试。我只是指出它,以防有我同样问题的人在这里徘徊。

ma8fv8wu

ma8fv8wu2#

我晚了几年,但这个问题仍然出现在搜索这个问题时,我已经解决了它,所以我想我会放弃我的解决方案的任何人谁来了。

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);

现在,当你通过门户网站绘制对象时,它们会显示在需要它们的地方,但是屏幕的其余部分仍然会有以前的深度信息!
当然,不要忘记像之前那样用 * 第三个 * portalFrameObj1.Draw()覆盖深度信息。这对下一部分有帮助:

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

在代码的开头,当你设置你的模具时,你禁用了深度测试。你不需要这样做!
glStencilOp有三个参数:

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

保持深度测试打开。设置glStencilFunc(GL_ALWAYS, 0, 0xFF);而不是GL_NEVER,以便模具测试成功,并使用dppass而不是sfail来设置模具:glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
现在,您也可以使用glStencilFuncSeparate来进行一些优化,但您不需要它来使门户彼此模糊。

相关问题