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

时间:2016-07-09 23:36:52

标签: opengl rendering stencil-buffer

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

trick

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

我查找了最好的方法,我发现了模板缓冲区。在this文章和this代码之间(“drawPortals()”函数)我在网上发现我几乎实现了它。

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

front

side

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

enter image description here

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

enter image description here

虽然它应该是这样的:

enter image description here

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

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

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)用1s填充模板缓冲区中的门户帧的位;在门户网站外面只有0 2)绘制模板具有1s的门户室(以便将其绘制到框架的门户上

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

对于第一个门户网站,我在步骤1得到这样的东西(原谅愚蠢的图纸,我很懒): enter image description here

然后在第2步之后:
enter image description here

然后我从第二个门户开始:
enter image description here

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

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

1 个答案:

答案 0 :(得分:0)

现在已经有一段时间了。既然不会出现问题(可能)我正在写关于我现在正在使用的解决方法。

我尝试使用不同的模板函数解决问题,而不是在渲染新门户时移除模板缓冲区;我的想法是通过查看模板缓冲区来利用我所知道的事实,其中前面的门户网站正在被渲染。

最后,我无法做到;在我在原始问题中发布的示例中,2个门户中的一个总是遮挡了另一个门户的部分。

所以,我决定做一些更简单的事情:我检查一个门户是否可见,然后我就像我在问题中发布的代码一样绘制它(每次都清空模板缓冲区)。

在伪代码中,我这样做:

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个门户位置作为一个立方体(所以我知道一次最多可以直接看到两个门户)并且只有一个面部显示门户,而另一个donesn&#t; t,它完美无缺。

我知道通过仅使用模板缓冲区可能有更优雅的方法,但现在这对我有用。

如果有人知道更好的方法,我仍然有兴趣知道!

编辑:我发现了其他版本的模板功能(即glStencilFuncSeparate,glStencilOpSeparate,glStencilMaskSeparate),它们允许对后面和前面做不同的事情。这似乎是我需要解决我所描述的问题,但我很快就无法尝试这个。我只是指出它,以防有同样问题的人在这里徘徊。