OpenGL Programming/Mini-Portal

In this series we'll create a teleportation system similar to the one used in Valve's Portal game.

= Concept =

We want to implement a teleportation device, where the source and destination are represented as holes in the walls that you can see through. Going in the source portal and out the destination portal should be completely seamless. It is also possible to place the two portals face to face, like mirrors, to create infinite depth.

The intuitive idea would be to place a second camera and render to texture (as seen in Post-Processing). However textures are distorted to map the object on which they are attached, especially if you look at it sideways. Typically they would look like a flat TV screen - but we're talking about PORTALS here!

Since we want a seamless effect, we'll use a different method. The scene will be rendered twice:
 * Once as if the player were already teleported, taking into account the player's distance to the portal. We'll clip the scene to the portal's boundaries using the Stencil buffer.
 * Once normally, without overwriting the portal by tricking the depth buffer.

To implement the portal we'll need a few prerequisites:
 * Matrix transformations
 * Mesh structure
 * Stencil buffer

= Overview =

Here's the points that we'll address to implement our portal system:
 * View through portal
 * Stencil draw in a distant rectangle
 * Stencil draw in rectangle intersection with camera
 * Collision detection
 * Warp
 * Infinite/recursive portals display (portals facing each others)
 * Object at 2 locations at the same time (duplicate objects)
 * Optimization
 * Physics

= Enabling back-face culling =

Drawing the portal will involve drawing the scene from behind the other portal, so to avoid any issue let's enable back-face culling, to draw front-facing polygons only:

= Defining the portals =

So we have two portals:
 * the source portal (portal1)
 * the destination portal (portal2)

We'll represent them as classic Mesh objects (see the Basics tutorials): a set of vertices to define the portal shape, and an object2world transformation matrix:

= Building a new camera =

So we need to position a new camera at the portal2 position, and then go backwards to cover the distance from portal1 to the original camera. Fortunately, it can be done easily by combining the transformation matrices (remember to read matrix multiplications backwards):

We now have a new world2camera (View) matrix. We can pass it to our shaders to make them render the scene from this new point of view:

You can do it for the second portal the same way.

= Protecting the portal scene - depth buffer =

Currently we just render the scene twice: from portal2 and from the main camera, but the second rendering overwrites the first.

The trick is to draw the portal in the depth buffer, but without writing it to the colors buffer: OpenGL will understand that something is displayed on the portal, but will not overwrite it with a blank rectangle.

We also take care of saving and restoring the previous color/depth configuration:

= Clipping the portal scene - stencil buffer =

At first glance, it seems we do not need to clip the portal scene, since we're overwriting its surroundings anyway, and the depth buffer protects the portal already.

However:
 * This forces you to rewrite all the scene background with a skybox: you cannot rely on  alone anymore to clear the background, since it may be written to by a portal view, and the main scene won't overwrite that part.
 * This is not optimized, since you redraw the full screen even for a tiny bit of portal.
 * And above all: when we display the two portals (not just one), the second portal view's depth conflicts with the first's.

Even if we redrew the first portal's depth while rendering the second portal's view, this was meant to protect the portal when rendering the main view - the second portal view may have objects much closer to the camera. Consequently, the depth buffer is not enough to protect the first portal when drawing the second one.

There are more powerful and relevant ways to protect a part of the screen:
 * the scissors (rectangle clipping)
 * the stencil buffer (arbitrary clipping)

Since we can look at our portal sideway, or draw it using another shape than a rectangle, the scissors are not enough, so we'll use the stencil buffer.

= Portal collision detection and warp =

The idea is to detect the intersection between a camera move (a line) and the portal (two triangles).

So we want to write a function that checks whether the line defined by two points  and   intersect the portal.

Wikipedia to the rescue!

We have a nice matrix ready to compute:
 * $$ \begin{bmatrix} t \\ u \\ v \end{bmatrix} = \begin{bmatrix} x_a - x_b & x_1 - x_0 & x_2 - x_0 \\ y_a - y_b & y_1 - y_0 & y_2 - y_0 \\ z_a - z_b & z_1 - z_0 & z_2 - z_0 \end{bmatrix}^{-1} \begin{bmatrix} x_a - x_0 \\ y_a - y_0 \\ z_a - z_0 \end{bmatrix}.$$


 * If $$\scriptstyle t \,\in\, [0,\, 1]$$, then there's an intersection with the plane.
 * If $$\scriptstyle u,\, v \,\in\, [0,\, 1], \; (u \,+\, v) \;\leq\; 1$$, then the intersection point is inside the triangle.

To implement it in C++, note that when initializing the matrix, each glm::vec3 is a column vector, so the values are rotated compared to the mathematic notation.

We also need to add some small values around the comparisons to make sure there's no floating point precision issue.

It's tempting to work in View coordinates to try and simplify the equations, but if you want to teleport objects later on (a cube? :), you'll need to work in object coordinates anyway.

When this test checks, we warp the camera. This is very easy because we already know how to compute its transformation matrix with !

= Basic version done! =

At this point, you have a basic portal, displaying the view from the brother portal, and teleporting when touched.

However, depending on the speed, you may see the screen flicker just before teleporting. In the next section, we will try to understand what causes this, and how to fix it.

= References =