In Raytracing in Curved Spacetime: Introduction I explained that photon paths in a curved spacetime are governed by the geodesic equations rather than a simple linear formula. Before getting to the camera setup, it’s worth understanding why, because the answer is already visible in flat space when you naively apply the straight-line formula in non-Cartesian coordinates.

Why the straight-line formula breaks

In Euclidean space, a ray is just described by

\[\mathbf{x}(t) = \mathbf{x}_0 + t\, \mathbf{d}\,.\]

Here $\mathbf{x}$ and $\mathbf{x}_0$ are points on the manifold and $\mathbf{d}$ lives in the tangent space. Each point has its own tangent space, and tangent vectors and points live in different spaces. This formula works because in Cartesian coordinates the natural identification between points and vectors in $\mathbb{R}^n$ lets you treat a direction as a displacement. In other coordinate systems, that identification breaks down. Let’s see what happens in polar coordinates.

Polar coordinates

In 2D, the transformation between Cartesian and polar coordinates is:

\[\begin{align} x &= r \cos \theta \\ y &= r \sin \theta \end{align}\]

Why the naive approach fails

You can define a tangent direction in polar components, but you cannot then add coordinate tuples as if they were vectors in a single linear space. For example, start at the point $(r, \theta) = (1, 0)$ and pick the polar direction $\mathbf{d} = (0, 1)_p$, i.e. pure $\theta$-motion at constant $r$. Applying the straight-line formula naively gives (note that this is incorrect usage and just serves as an example of how Cartesian coordinates are special):

\[\mathbf{x}(t) = (1,\, 0) + t\,(0,\, 1) = (1,\, t)_p\]

This traces a circle of radius 1, not a straight line. Converting a few points to Cartesian makes it obvious:

\[\begin{align} (1, 0)_p & \rightarrow (1, 0)_c \\ (1, \pi/2)_p & \rightarrow (0, 1)_c \\ (1, \pi)_p & \rightarrow (-1, 0)_c \end{align}\]

The trajectory curves because polar coordinate tuples can’t be added like vectors. Adding a displacement only works in Cartesian coordinates, where the basis vectors are constant.

This is why we need to carefully distinguish between points like $\mathbf{x}$ and $\mathbf{x}_0$ and elements of the tangent space like $\mathbf{d}$.

Getting straight lines right

The straight-line formula

\[\begin{align} x(t) & = x_0 + t d_x \\ y(t) & = y_0 + t d_y \end{align}\]

is actually the solution of a differential equation:

\[\begin{align} \ddot{x}(t) & = 0 \\ \ddot{y}(t) & = 0 \end{align}\]

The rest of this section works through the algebra to reach the geodesic equations in polar coordinates. If you want to skip ahead, jump to Christoffel Symbols.

The problem in polar coordinates is that the basis vectors of the tangent space

\[\partial_r,\ \partial_\theta\]

depend on position. As a result, derivatives of vectors must also account for changes in the basis vectors themselves. From the coordinate transformation, we can express the polar basis vectors in terms of the Cartesian ones.

We write

\[\mathbf{x} = r \begin{pmatrix} \cos \theta \\ \sin \theta \end{pmatrix}\]

and compute the following:

\[\begin{align} \mathbf{e}_r \equiv \partial_r \mathbf{x} & = \frac{\partial x}{\partial r}\,\partial_x + \frac{\partial y}{\partial r}\,\partial_y = \cos \theta\, \partial_x + \sin \theta\,\partial_y \\ \mathbf{e}_\theta \equiv \partial_\theta \mathbf{x} & = \frac{\partial x}{\partial \theta}\,\partial_x + \frac{\partial y}{\partial \theta}\,\partial_y = - r \sin \theta\, \partial_x + r \cos \theta\,\partial_y \end{align}\]

Both depend on $(r, \theta)$, so the basis changes from point to point, and the differential equation gets more complicated.

Drag the point in the diagram below to see this in action. The polar basis vectors (solid) change at every point, while the Cartesian ones (dashed) stay the same.

Let’s see how $\ddot{\mathbf{x}}(t)$ behaves in polar coordinates:

\[\begin{align} \dot{\mathbf{x}}(t) & = \frac{d}{dt}\mathbf{x}(r(t), \theta(t)) = \frac{\partial \mathbf{x}(r, \theta)}{\partial r} \dot{r} + \frac{\partial \mathbf{x}(r, \theta)}{\partial \theta} \dot{\theta} \\ \ddot{\mathbf{x}}(t) & = \frac{d^2}{dt^2}\mathbf{x}(r(t), \theta(t)) = \ddot{r}\,\partial_r \mathbf{x} + \ddot{\theta}\,\partial_\theta \mathbf{x} + \dot{r}\,\frac{d}{d t}\left(\partial_r \mathbf{x}\right) + \dot{\theta}\,\frac{d}{d t}\left(\partial_\theta \mathbf{x}\right) \end{align}\]

These derivatives depend on $t$:

\[\begin{align} \frac{d}{d t}(\partial_r \mathbf{x}) & = \dot{r}\,\partial_r \partial_r \mathbf{x} + \dot{\theta}\,\partial_\theta \partial_r \mathbf{x} \\ \frac{d}{d t}(\partial_\theta \mathbf{x}) & = \dot{r}\,\partial_r \partial_\theta \mathbf{x} + \dot{\theta}\,\partial_\theta \partial_\theta \mathbf{x} \\ \end{align}\]

Putting it all together:

\[\begin{align} \ddot{\mathbf{x}}(t) & = \ddot{r}\,\partial_r \mathbf{x} + \ddot{\theta}\,\partial_\theta \mathbf{x} + \dot{r}\left( \dot{r}\,\partial_r \partial_r \mathbf{x} + \dot{\theta}\,\partial_\theta \partial_r \mathbf{x} \right) + \dot{\theta}\left(\dot{r}\,\partial_r \partial_\theta \mathbf{x} + \dot{\theta}\,\partial_\theta \partial_\theta \mathbf{x}\right) \end{align}\]

Setting

\[\begin{align} \mathbf{e}_r & = \partial_r \mathbf{x} = (\cos \theta, \sin \theta) \\ \mathbf{e}_\theta & = \partial_\theta \mathbf{x} = (-r \sin \theta, r\cos\theta) \end{align}\]

and computing:

\[\begin{align} \partial_r \partial_r \mathbf{x} & = \partial_r \mathbf{e}_r = 0 \\ \partial_\theta \partial_r \mathbf{x} & = \partial_\theta \mathbf{e}_r = \frac{1}{r} \mathbf{e}_\theta \\ \partial_r \partial_\theta \mathbf{x} & = \partial_r \mathbf{e}_\theta = \frac{1}{r} \mathbf{e}_\theta \\ \partial_\theta \partial_\theta \mathbf{x} & = \partial_\theta \mathbf{e}_\theta = -r \mathbf{e}_r \end{align}\]

Plugging everything in:

\[\begin{align} \ddot{r} - r \dot{\theta}^2 = 0 \\ \ddot{\theta} + \frac{2}{r} \dot{r}\dot{\theta} = 0 \\ \end{align}\]

These are the geodesic equations for flat space in polar coordinates. In curved spacetime, the same kind of equations appear, just with different Christoffel symbols, which are computed from the metric. It’s precisely these equations that the raytracer solves numerically for each photon path.

Christoffel Symbols

Setting

\[\mathbf{e}_r := \partial_r \mathbf{x},\quad \mathbf{e}_\theta := \partial_\theta \mathbf{x}\]

we have

\[\begin{align} \ddot{\mathbf{x}}(t) & = \ddot{r}\mathbf{e}_r + \ddot{\theta}\mathbf{e}_\theta + \dot{r}\left( \dot{r}\,\partial_r \mathbf{e}_r + \dot{\theta}\,\partial_\theta \mathbf{e}_r \right) + \dot{\theta}\left(\dot{r}\,\partial_r \mathbf{e}_\theta + \dot{\theta}\,\partial_\theta \mathbf{e}_\theta\right) \\ & = \ddot{x}^i \mathbf{e}_i + \dot{x}^i \dot{x}^j \partial_i \mathbf{e}_j \\ & = \left(\ddot{x}^k + \Gamma^k_{i j} \dot{x}^i \dot{x}^j \right)\,\mathbf{e}_k \end{align}\]

when defining

\[\partial_i \mathbf{e}_j = \Gamma^k_{i j} \mathbf{e}_k\,.\]

The non-zero Christoffel symbols for polar coordinates are

\[\begin{align} \Gamma^r_{\theta \theta} & = -r \\ \Gamma^\theta_{r \theta} = \Gamma^\theta_{\theta r} & = \frac{1}{r} \end{align}\]

Parallel transport and the Christoffel correction

Since every point has its own tangent space, comparing vectors at different points requires a way to carry one vector over to another point without changing it. This is what parallel transport does, and the Christoffel symbols tell you how to do it: they encode how much the basis vectors rotate as you move through the coordinate system.

In flat space, a vector always comes back unchanged after a full loop. In curved spacetime, it generally doesn’t. That failure is one way to detect curvature. We won’t get into curvature here, but the transport machinery is the same. To see what this means concretely, consider carrying a vector around a circle at constant $r$ in polar coordinates.

What “parallel transport” means

A tangent vector $\mathbf{V} = V^r\,\mathbf{e}_r + V^\theta\,\mathbf{e}_\theta$ is described by its components in the polar basis we derived earlier. Moving it without rotating it sounds trivial in Cartesian coordinates: just keep $(V_x, V_y)$ constant. In polar coordinates the same physical operation looks more complicated because $\mathbf{e}_r$ and $\mathbf{e}_\theta$ rotate as you move around the circle. If you naively keep the polar components $(V^r, V^\theta)$ constant, the vector rotates with the basis, which is not the same vector anymore.

Parallel transport fixes this by adjusting the components to compensate for the rotating basis. The general parallel transport equation for a vector $V^k$ along a curve $x^i(t)$ is

\[\frac{dV^k}{dt} + \Gamma^k_{ij}\,\dot{x}^i\,V^j = 0\]

Note the similarity to the geodesic equations: a geodesic is a curve that parallel-transports its own tangent vector, so setting $V = \dot{x}$ recovers $\dot{x}^i\dot{x}^j$. Here $\dot{x}^i$ is the tangent to the curve you’re transporting along, and $V^j$ is the vector being transported.

For motion around a circle of radius $r$ with $\dot{\theta} = 1$ and $\dot{r} = 0$, all $i = r$ terms vanish, and the Christoffel symbols ($\Gamma^r_{\theta\theta} = -r$ and $\Gamma^\theta_{\theta r} = \tfrac{1}{r}$) give the transport equations:

\[\frac{dV^r}{dt} = r \, V^\theta, \qquad \frac{dV^\theta}{dt} = -\frac{1}{r} \, V^r\]

These have an analytic solution: the polar components trace out sine and cosine while the Cartesian components stay constant:

\[V^r(t) = V^r_0 \cos t + r \, V^\theta_0 \sin t, \qquad V^\theta(t) = V^\theta_0 \cos t - \frac{1}{r} V^r_0 \sin t\]

The interactive diagram below lets you see all of this in action.

Reading the diagram: Drag the origin of the arrow to set the radius $r$ (the Christoffel values in the right panel update live). Drag the handle at the arrow tip to set the initial direction. Press play to transport the vector around the circle.

  • Blue arrow: correctly transported vector. Its Cartesian components stay constant (shown in the right panel).
  • Red dashed arrow: what you get if you keep the polar components fixed. It drifts because the basis rotates underneath it.
  • Orange arrow: the $\Gamma$ correction, i.e. the gap from the red tip to the blue tip. This is what the Christoffel symbols add at each step.

Enable Show $\Gamma$ correction arrows to decompose the orange correction into its two arms:

\[\Delta V^r = -\Gamma^r_{\theta\theta}\,V^\theta\,\Delta t = r\,V^\theta\,\Delta t, \qquad \Delta V^\theta = -\Gamma^\theta_{\theta r}\,V^r\,\Delta t = -\frac{1}{r}\,V^r\,\Delta t\]

The $\Delta V^r\,\mathbf{e}_r$ arm (amber) and the $r\,\Delta V^\theta\,\hat{\mathbf{e}}_\theta$ arm (dark orange) are shown lined up to create the final displacement vector. The Christoffel symbols are exactly the proportionality constants that turn “how far you moved” ($\Delta t$) and “the vector to transport” ($V^r$, $V^\theta$) into “how much the components must change”.

Component space (lower-right panel): The key insight is visible in the small diagram: plot the polar components as the point $(V^r,\, r V^\theta)$. This point traces a circle as $t$ increases and never leaves it, regardless of $r$ or initial direction. The orange line is the total correction from the vector sum, and its horizontal/vertical legs are exactly the $\Delta V^r$ and $r\,\Delta V^\theta$ arms shown in the main diagram.

In curved spacetime, the same structure appears, but the vector no longer returns to where it started after a full loop. That failure to close is curvature.

Summary

The geodesic equation

\[\frac{d^2x^i}{d\lambda^2} + \Gamma^i_{j k} \frac{dx^j}{d\lambda} \frac{dx^k}{d\lambda} = 0\]

replaces the straight-line formula in any coordinate system. For flat space in polar coordinates, it gives the equations we derived above. For curved spacetimes like Schwarzschild or Kerr, the Christoffel symbols change but the structure is the same. The raytracer integrates this equation numerically for each photon.

The next post covers how to use this machinery to set up the camera.