How to implement “portal rendering”?

I’ve been trying to implement something like the game Antichamber (to be more precisly this trick shown below) for the past week:

[ATTACH=CONFIG]1390[/ATTACH]

Here’s a video of what I’m hoping to achieve (even though it was done with the Unreal Engine 4; I’m not using that): https://www.youtube.com/watch?v=Of3JcoWrMZs

I looked up the best way to do this and I found out about the stencil buffer. Between this article and this code (the “drawPortals()” function) I found online I managed to almost implement it.

It works nicely with one portal to another room (not a crossable portal, meaning you can’t walk through it and be teleported in the other room). In my example I’m drawing a portal to a simple squared room with a sphere in it; behind the portal there is another sphere, that I used to check whether the depth buffer was working correctly and drawing it behind the portal:

[ATTACH=CONFIG]1392[/ATTACH]

[ATTACH=CONFIG]1391[/ATTACH]

The problems arise when I add another portal that is near this one. In that case I manage to display the other portal correctly (the lighting is off, but the sphere on the right is of a different color to show that it’s another sphere):

[ATTACH=CONFIG]1393[/ATTACH]

But if I turn the camera so that the first portal has to be drawn over the second one, then the depth of the first one becomes wrong, and the second portal gets drawn over the first one, like this:

[ATTACH=CONFIG]1394[/ATTACH]

while it should be something like this:

[ATTACH=CONFIG]1395[/ATTACH]

So, this is the problem. I’m probably doing something wrong with the depth buffer, but I can’t find what.

My code for the rendering part is pretty much this:


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