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 this way works in Cartesian coordinates, or more generally affine coordinates with a constant basis.

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, in this Euclidean example,

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

On a general manifold, the corresponding definition uses the covariant derivative: \(\nabla_{\partial_i}\mathbf{e}_j = \Gamma^k_{ij}\mathbf{e}_k\). The ordinary derivatives above work because we expressed the polar basis in a fixed Cartesian basis in the Euclidean plane.

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 rule for carrying a vector along a path. Parallel transport keeps its covariant derivative along that path zero. The Christoffel symbols encode changes in the coordinate basis, including its orientation and scale. For example, $\mathbf{e}_\theta$ has length $r$, so it changes with radius even at fixed angle.

In the Euclidean plane, a vector comes back unchanged after transport around a full loop. In curved spacetime it can return changed, depending on the loop and the vector. This is one way curvature reveals itself. 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 accumulated correction, i.e. the gap from the red tip to the blue tip at the current point. It comes from integrating the transport equation from $0$ to $t$.

For a small step $\Delta t$, the transport equation gives

\[V^r(t+\Delta t)-V^r(t) = r\,V^\theta(t)\,\Delta t + O(\Delta t^2), \qquad V^\theta(t+\Delta t)-V^\theta(t) = -\frac{V^r(t)}{r}\,\Delta t + O(\Delta t^2).\]

These are first-order increments; the components on the right change during transport. The diagram instead uses the exact solution above. Its finite component differences are

\[\Delta V^r = V^r(t)-V^r_0, \qquad \Delta V^\theta = V^\theta(t)-V^\theta_0.\]

Enable Show accumulated correction arms to decompose the orange gap into \(\Delta V^r\,\mathbf{e}_r\) (amber) and \(r\,\Delta V^\theta\,\hat{\mathbf{e}}_\theta\) (dark orange), using the basis at the current point. These add up to the difference between the correctly transported vector and the one with unchanged polar components.

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 transport equation applies, but transport around a loop can change the vector when it returns to the starting point. Curvature governs this change for small loops; a particular loop or vector can still return unchanged.

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\]

describes geodesics in any coordinate system when $\lambda$ is an affine parameter. 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 these equations or equivalent Hamiltonian or separated equations, depending on the geometry.

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