Prince of Persia’s Parallel Projection

Prince of Persia (1989) uses oblique projection – the same as is used for furniture diagrams – to give a sense of depth to an otherwise flat 2D platformer.

Prince-of-Persia-11

The problem is that oblique projection, while it gives an intuitive depiction of objects, is not really physically possible, for say a camera.

To do any kind of realistic lighting of a game in this perspective, we need to understand how the light rays are received by the camera.

We can write down, just by looking at the screenshot, the screen space coordinates (X,Y) for some world space coordinates (x_0,y_0,z_0):

20160701_192043

(X,Y) = (\alpha x_0+2\gamma z_0, -\beta y_0-\gamma z_0)

up to a choice of origin, for some scaling factors \alpha, \beta, \gamma. We have noticed here that the lines of depth in the screenshot go along two pixels for every one they go up, to give smooth lines in the pixel art, at a good angle.

This gives a coordinate conversion, but we have no idea which direction the light rays are coming in, so we could not position reflections. We also do not know how far points in the world are from the projection plane.

Oblique projection, and parallel projection in general, is based upon light rays in a particular direction hitting a plane.

The difference between a perspective camera and a parallel camera is easily thought of as the difference between a point light and a directional light. In the same way we can think of a directional light as a point light at infinity (that is, in some sense, infinitely bright), we can think of an orthographic camera as a perspective camera at infinity (with infinite zoom).

An orthographic camera is one where the normal of the plane \mathbf{n} and the direction of incoming light \mathbf{l} are opposite. The particular case of isometric perspective is given by (given that we want \mathbf{n} and \mathbf{l} both to be unit vectors) -\mathbf{l}=\mathbf{n}=\frac{(1,1,1)}{\sqrt{3}}.

In general, a point \mathbf{x} on the projection plane satisfies (if we assume the plane goes through the origin)

\mathbf{x} \cdot \mathbf{n} = 0

and projecting a point in world space \mathbf{x}_0 in the direction \mathbf{l} by a distance \lambda gives a coordinate

\mathbf{x} = \mathbf{x}_0 + \lambda \mathbf{l}.

Putting these together gives (\mathbf{x}_0 + \lambda \mathbf{l})\cdot \mathbf{n} = 0, so that

\lambda = -\frac{\mathbf{x}_0\cdot\mathbf{n}}{\mathbf{l}\cdot\mathbf{n}}

which gives the projected position as

\mathbf{x} = \mathbf{x}_0 -\frac{\mathbf{x}_0\cdot\mathbf{n}}{\mathbf{l}\cdot\mathbf{n}} \lambda \mathbf{l}.

To convert this into screen space coordinates, we must choose basis vectors \mathbf{a},\mathbf{b} on the plane. These ought to be orthogonal, so that \mahtbf{a}\cdot\mathbf{n}=\mathbf{b}\cdot\mathbf{n}=\mathbf{a}\cdot\mathbf{b}=0.

20160701_192300

Then we have screen space coordinates

X = \mathbf{a}\cdot\mathbf{x} = \mathbf{a}\cdot\mathbf{x}_0 - \frac{(\mathbf{a}\cdot\mathbf{l})(\mathbf{x}_0\cdot\mathbf{n})}{\mathbf{l}\cdot\mathbf{n}},
Y = \mathbf{b}\cdot\mathbf{x} = \mathbf{b}\cdot\mathbf{x}_0 - \frac{(\mathbf{b}\cdot\mathbf{l})(\mathbf{x}_0\cdot\mathbf{n})}{\mathbf{l}\cdot\mathbf{n}}.

Comparing this with the original formula for the projection we wrote down earlier, it seems we should take
\mathbf{n}=(0,0,1),
\mathbf{a}=(\alpha,0,0),
\mathbf{b}=(0,-\beta,0),
and let \mathbf{l}=(l_1,l_2,l_3).

Equating the two forms then gives l_1 = \frac{-2\gamma l_3}{\alpha} and l_2 = \frac{-\gamma l_3}{\beta}.

We also need, for \mathbf{l} a unit vector,

1=l_1^2+l_2^2+l_3^2=\left(\left(\frac{4}{\alpha^2} + \frac{1}{\beta^2}\right)\gamma^2 + 1\right)l_3^2

so l_3 = -1/\sqrt{\left(\frac{4}{\alpha^2} + \frac{1}{\beta^2}\right)\gamma^2 + 1}, where we have taken the negative square root so that \mathbf{l} points in the opposite direction to \mathbf{n}. So we can finally write down

\mathbf{l}=\frac{(2 \gamma / \alpha, \gamma / \beta,-1)}{\sqrt{\left(\frac{4}{\alpha^2} + \frac{1}{\beta^2}\right)\gamma^2 + 1}}.

It seems sensible to take \alpha=\beta=1, so that squares appear as squares, and in the case of Prince of Persia, we might guess that the pillars in the screen shot have a square cross section, giving \alpha: 2\gamma = 20:12 = 5:4, so \gamma = 2/5. Of course, this value can be varied to taste.

That would give \mathbf{l} = \frac{\sqrt{5}}{3}(4/5,2/5,-1). In screen space, this maps to the zero vector, as expected. Reflections off the floor y=0 would have light rays \frac{\sqrt{5}}{3}(4/5,-2/5,-1), which maps to the screen space vector (0, 4\sqrt{5}/15), so since there is no horizontal component, reflections appear below objects, and we can now calculate the correct distance.

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *