3 · Geometry
Chapter 3

Geometry

Everything so far has lived in flat space — Euclidean planes and boxes. This section moves into curved and higher-dimensional geometry, where the pullback approach becomes especially powerful.

Drawing a geodesic in the hyperbolic plane, tiling 2 by a Coxeter group, or rendering the Hopf fibration of 𝑆3 — these are all situations where the geometry is most naturally described by equations and group actions, not by explicit parameterizations. Shaders handle them by evaluating those equations at every pixel, which is exactly the per-pixel computation model we've been building.

A note on difficulty: the shaders in this section are more involved than the earlier ones. The 2D examples (stereographic projection, hyperbolic tilings) are comparable to what came before, but the 3D hyperbolic honeycombs and the Hopf fibration use more elaborate distance functions and raymarching setups. You don't need to follow every implementation detail to use them — the "edit the function at the top" pattern still applies — but the explanations go deeper into the geometry to show what these techniques can do.

We start with stereographic projection — the bridge between planes and spheres — then move to the hyperbolic plane and its tilings, circle packings, hyperbolic 3-space, and the Hopf fibration.

3.0.1Stereographic Projection

This shader shows a curve drawn simultaneously in the plane and on the sphere, connected by stereographic projection. The sphere sits at the origin with its south pole on the 𝑧 =0 plane. Projection from the north pole sends the plane curve 𝑓(𝑢,𝑣) =0 to a curve on 𝑆2, and both are rendered together so you can see the correspondence.

The curve on the sphere uses the same |𝑓|/|𝑓| distance estimate as in the plane, multiplied by the conformal factor 𝜆 =4/(4 +|𝑤|2) to convert Euclidean distance in the plane to geodesic distance on the sphere. This keeps the curve width visually consistent across the sphere surface despite the stretching near the north pole.

The scene includes a grid on the ground plane, soft shadow and ambient occlusion from the sphere onto the plane, and Phong shading with Fresnel on the sphere.

3.0.2Hyperbolic Geometry

The hyperbolic plane is a natural first stop: the geometry is curved, parallel lines diverge, triangles have angle sums less than 𝜋, and a circle's circumference grows exponentially with its radius.

All the hyperbolic shaders work in the upper half-plane model internally, where 2 ={(𝑥,𝑦) :𝑦 >0} with metric 𝑑𝑠2 =(𝑑𝑥2 +𝑑𝑦2)/𝑦2. You can view the output in any of four models — Poincaré disk, upper half-plane, Klein disk, or the band model — by changing a single #define. The models are related by conformal (or projective, for Klein) maps, and the shader applies these at the pixel level: convert screen coordinates to UHP coordinates, do all the geometry there, convert back.

Models of the hyperbolic plane

The Poincaré disk maps 2 conformally onto the open unit disk via 𝑤 =(𝑧 𝑖)/(𝑧 +𝑖). Geodesics appear as circular arcs perpendicular to the boundary. Angles are faithfully represented, but distances are wildly distorted — the boundary circle represents infinity.

The upper half-plane is the most natural for computation. Geodesics are semicircles centered on the real axis (or vertical rays). Reflections across geodesics are circle inversions or Euclidean reflections, both easy to compute.

The Klein disk maps 2 onto the unit disk so that geodesics become straight chords — useful for seeing incidence relations, but angles are distorted.

The band model maps 2 to an infinite horizontal strip via tanh, giving a scrollable "ribbon" view that's good for seeing how structures repeat along a geodesic.

Hyperbolic Geometry Explorer

The explorer is a graphing calculator for the hyperbolic plane. You specify points, geodesics, and horocycles in UHP coordinates, and they're drawn in whichever model you choose.

Points are specified as 𝑧 =𝑥 +𝑖𝑦 in the upper half-plane. Each gets its own color and hyperbolic radius (the dot is a true hyperbolic disk — it looks round in the Poincaré model but distorted in Klein).

Geodesics are specified by two UHP points. The shader finds the unique geodesic through them — either a vertical line (if the points share an 𝑥-coordinate) or a semicircle centered on the real axis. The semicircle's center is 𝑐 =(|𝑧1|2 |𝑧2|2)/(2(𝑥1 𝑥2)), and its radius is |𝑧1 𝑐|. The geodesic is rendered by computing hyperbolic distance to the curve and thresholding with antialiasing.

Horocycles are circles tangent to the boundary at infinity. In UHP, a horocycle at a finite point 𝑏 is a Euclidean circle tangent to the real axis at 𝑏 with some Euclidean radius 𝑟. A horocycle "at infinity" is a horizontal line 𝑦 =. To compute hyperbolic distance to a finite horocycle, apply the Möbius transformation 𝑇(𝑧) = 1/(𝑧 𝑏) which sends 𝑏 to infinity, mapping the horocycle to a horizontal line at height 1/(2𝑟). Then the signed distance is log(Im(𝑇(𝑧)) 2𝑟), and we take the absolute value for the unsigned distance.

Each object type has per-object colors and sizes (radii, thicknesses), so you can distinguish different elements visually.

Triangle Tilings

A triangle group (𝑃,𝑄,𝑅) is the group generated by reflections in the three sides of a hyperbolic triangle with angles 𝜋/𝑃, 𝜋/𝑄, 𝜋/𝑅. The condition 1/𝑃 +1/𝑄 +1/𝑅 <1 ensures the triangle is hyperbolic (angle sum less than 𝜋). The orbit of this triangle under the group tiles the entire hyperbolic plane.

The fundamental triangle is built in UHP with a specific layout:

The three vertices sit at the intersections of these curves, computed from the circle equations.

To render, every pixel is folded into the fundamental triangle by iterated reflection: check if the point is on the wrong side of each mirror, and if so, reflect it. Repeat until stable (at most ~100 iterations, though convergence is usually fast). The number of reflections gives the parity — even means orientation-preserving, odd means orientation-reversing — which provides a natural two-coloring.

Edges and vertices are drawn by computing hyperbolic distance from the folded point to the triangle's sides and vertices.

Wythoff Tilings

The triangle tiling shows the underlying (𝑃,𝑄,𝑅) triangle group, but the most interesting tilings of the hyperbolic plane come from the Wythoff construction: choose a "generating point" 𝐺 inside the fundamental triangle, reflect it across the mirrors, and connect adjacent images with geodesic segments.

Different positions of 𝐺 produce different tilings:

The shader computes 𝐺 for each of these cases. Vertex positions use geodesic interpolation along triangle edges, with bisection search to find the equidistant point. The incenter is found by Riemannian gradient descent on the function (𝑑𝑎 𝑑𝑏)2 +(𝑑𝑏 𝑑𝑐)2 +(𝑑𝑎 𝑑𝑐)2, using the UHP metric to scale the gradient correctly.

The key insight for rendering is that after folding a pixel into the fundamental triangle, the Wythoff edges are just geodesic segments from 𝐺 to its reflections 𝐺𝑎, 𝐺𝑏, 𝐺𝑐 across each mirror. Which edges to draw depends on which mirrors 𝐺 lies on — if 𝐺 is on a mirror, no edge crosses that mirror.

3.0.3Apollonian Circle Packing

Leaving hyperbolic tilings, we turn to a classical construction from inversive geometry. An Apollonian gasket starts from four mutually tangent circles and fills in every interstice: given any three mutually tangent circles, there are exactly two circles tangent to all three, and the gasket is the limit of inserting every such circle.

The starting point is Descartes' circle theorem. If four circles are mutually tangent with curvatures 𝑘1,𝑘2,𝑘3,𝑘4 (where curvature = 1/𝑟, negative for an enclosing circle, zero for a line), then

(𝑘1+𝑘2+𝑘3+𝑘4)2=2(𝑘21+𝑘22+𝑘23+𝑘24)

This is a quadratic in 𝑘4 given 𝑘1,𝑘2,𝑘3:

𝑘4=𝑘1+𝑘2+𝑘3±2𝑘1𝑘2+𝑘2𝑘3+𝑘3𝑘1

The two roots correspond to the two circles tangent to a given triple — the small one filling the interstice (+ root) and the large one enclosing the configuration ( root).

The shader asks you to specify three curvatures K1, K2, K3 and a sign choice INNER. It computes 𝑘4 from Descartes, then solves for the actual positions: given the curvatures, the centers are determined (up to isometry) by the tangency constraints. For the general case with no lines, this is a system of three distance equations |𝑐𝑖 𝑐𝑗| =|𝑟𝑖| +|𝑟𝑗| (external tangency) or ||𝑟𝑖| |𝑟𝑗|| (internal).

To render the gasket, the shader uses iterated circle inversions. Given the four initial circles, their six tangent points determine four dual circles — each dual circle passes through the three tangent points that don't involve a given initial circle. Inverting through a dual circle swaps the two initial circles on either side of it, acting as a "reflection" in the packing.

The algorithm for each pixel:

  1. Check if the pixel is inside any of the four initial circles — if so, color it and stop.

  2. Otherwise, find which dual circle contains the pixel and invert through it. This maps the pixel into a smaller copy of the packing.

  3. Repeat until the pixel lands inside a circle or the iteration limit is reached.

The number of inversions gives a "depth" that controls the color fade — deeper circles wash toward white. Circle boundaries are drawn with a thickness that scales with the cumulative Jacobian of the inversions, so borders have consistent pixel width at every depth.

Some configurations to try:

Credit

This shader was written by Summer Haag (University of Colorado Boulder), a graduate student at the IHP trimester program.

3.0.4Hyperbolic 3-Space

Everything above lives in the hyperbolic plane. Now we go up a dimension to 3, where the same exponential geometry plays out in three dimensions: geodesics diverge, horospheres replace horocycles, and the volume of a ball grows exponentially with its radius. The shader techniques generalize naturally — raymarching along hyperbolic geodesics instead of Euclidean rays, and folding into fundamental domains of reflection groups, just as we did for the triangle tilings.

The model we use is the hyperboloid model: embed 3 as the upper sheet of a hyperboloid in Minkowski space 3,1,

3={(𝑥,𝑦,𝑧,𝑤):𝑥2+𝑦2+𝑧2𝑤2=1,𝑤>0}

with the induced metric from the Minkowski inner product 𝑢,𝑣 =𝑢𝑥𝑣𝑥 +𝑢𝑦𝑣𝑦 +𝑢𝑧𝑣𝑧 𝑢𝑤𝑣𝑤.

This is the 3D analog of the upper half-plane model — but better suited to computation because every isometry of 3 extends to a linear map on 3,1 preserving , . Distances, reflections, and geodesics all have clean formulas:

To specify a point, you give spatial coordinates (𝑥,𝑦,𝑧) and compute 𝑤 =1+𝑥2+𝑦2+𝑧2. To draw a geodesic through two points 𝐴,𝐵, you decompose 𝐵 into components parallel and perpendicular to 𝐴: the tangent direction at 𝐴 toward 𝐵 is 𝑡 =𝐵 +𝐴,𝐵 𝐴 (the Minkowski-perpendicular component), and the distance from any point 𝑝 to the geodesic line is arccosh𝛼2𝛽2 where 𝛼 = 𝑝,𝐴 and 𝛽 =𝑝,𝑡.

Points and Geodesics

This first shader is a simple explorer: place balls at points in 3 and draw geodesic lines between them. The SDF for a ball of hyperbolic radius 𝑟 centered at 𝑐 is just 𝑑(𝑝,𝑐) 𝑟, and the geodesic tube SDF uses the distance-to-geodesic formula above.

Raymarching works just like in Euclidean space, except the ray is a geodesic: 𝛾(𝑡) =cosh(𝑡) 𝑂 +sinh(𝑡) 𝑅 where 𝑂 is the camera position on 3 and 𝑅 is a unit spacelike tangent direction. At each step, evaluate the SDF and advance by the returned distance. Normals are computed by finite differences along a parallel-transported frame.

Try moving the points farther from the origin and watch how the geodesic lines curve — or rather, how they stay straight while the space curves around them.

Ideal Tetrahedral Honeycomb

An ideal tetrahedron in 3 has all four vertices at infinity (on 𝜕3). Despite having infinite edge lengths, such a tetrahedron has finite volume — a signature feature of hyperbolic geometry. The dihedral angle at each edge is 𝜋/3, so exactly six tetrahedra fit around each edge, giving the {3,3,6} honeycomb.

The tiling algorithm is the same fold-into-fundamental-domain technique from the 2D tilings, now in one higher dimension. The fundamental tetrahedron has 4 face mirrors, each a totally geodesic hyperplane in 3. For any point 𝑝, the bounce function iterates: check which face half-space 𝑝 violates, reflect through that mirror, repeat until 𝑝 is inside all 4 half-spaces. Each face normal has the form ( ±𝜓, ±𝜓, 𝜓,𝜒) where 𝜓 =322 and 𝜒 =122, and all four dot products are computed simultaneously via a single swizzled expression.

After folding, the edge SDF computes distance to the 6 edges of the tetrahedron using cosh2(𝑑) =12 +𝑤2 +min𝑖(𝑝2𝑖 |3 𝑤 𝑝𝑖 +𝑝𝑗𝑝𝑘|), handling all 6 edges at once via cyclic permutation. The face SDF renders thin shells around each totally geodesic face plane, bounded by a ball around the origin to prevent them from extending to infinity.

Dodecahedral Honeycomb

A regular dodecahedron has pentagonal faces with interior angles of 108 ° and dihedral angles of about 116.6 ° in Euclidean space. In hyperbolic space, we can inflate the dodecahedron until its dihedral angles become exactly 90 ° — four cells then fit perfectly around each edge, tiling all of 3. This is the {5,3,4} honeycomb: faces are pentagons {5}, three meet at each vertex {3}, four cells around each edge {4}.

The same bounce algorithm applies, but now with 12 face mirrors instead of 4. Icosahedral symmetry compresses this: abs(p.xyz) handles sign flips, and a 𝜑-weighted cyclic shift `q += \varphi \cdot q.\text{yzx}$ finds the winning mirror, so the loop body is just a few lines.

After folding, the SDF tests distance to the 20 vertices and 30 edges of the fundamental dodecahedron. The vertices split into two icosahedral orbits (8 "cubic" + 12 "golden"), reduced to 4 dot products by abs and cyclic symmetry. The edges split into 6 axis-aligned + 24 golden, packed into vec4 batches — four sign combinations at once — computing cosh2(𝑑) =𝛼2 𝛽2 for each edge in parallel.

Edges are colored by depth: the 𝑤-coordinate of the hit point controls per-channel power curves, so deeper structures shift from warm to cool.

3.0.5Hopf Fibration

The Hopf fibration 𝜋:𝑆3 𝑆2 is the map sending (𝑧1,𝑧2) 2 with |𝑧1|2 +|𝑧2|2 =1 to the point 𝑧1/𝑧2 {} 𝑆2. The fiber over each point 𝑝 𝑆2 is a great circle in 𝑆3. We can't see 𝑆3 directly, but stereographic projection 𝜎:𝑆3 3 sends each fiber to a circle (or line) in 3-space, and the resulting picture — nested tori woven from linked circles — is one of the iconic images in mathematics.

This shader raymarches those fibers in 3. The beautiful part is that the distance computation lives entirely on the sphere — no fiber is ever parameterized, and no closest-point equation is ever solved.

The SDF via the bundle structure

The key formula is the distance from a point 𝑥 3 to the Hopf fiber over 𝑝 𝑆2:

𝑑(𝑥,fiber𝑝)|𝑥|2+14arccos𝜋(𝜎1(𝑥)),𝑝

This works by lifting everything to 𝑆3 and using the Riemannian submersion structure. The point 𝑥 lifts to 𝑞 =𝜎1(𝑥) 𝑆3, and 𝑞 projects to =𝜋(𝑞) 𝑆2. The angular distance arccos,𝑝 on 𝑆2 measures how far 𝑞's fiber is from 𝑝's fiber, measured on the base. Since 𝜋 is a Riemannian submersion from 𝑆3(1) to 𝑆2(1/2), the actual distance between fibers on 𝑆3 is half this angle. Finally, stereographic projection has conformal factor (|𝑥|2 +1)/2 at 𝑥, which converts the 𝑆3 distance to a Euclidean distance. Combining the 1/2 from the submersion with the 1/2 from the conformal factor gives the 1/4.

We just compose three maps — inverse stereographic projection, the Hopf map, and a dot product — and get an SDF directly from the bundle geometry.

Intrinsic vs. extrinsic thickness

The INTRINSIC toggle changes where the tube radius is subtracted. In extrinsic mode (INTRINSIC 0), the radius is subtracted after multiplying by the conformal factor:

dFiber = conformal * ang - TUBE_RADIUS

This gives fibers of uniform Euclidean thickness in 3 — the tubes all look the same width on screen.

In intrinsic mode (INTRINSIC 1), the radius is subtracted on 𝑆3 before applying the conformal factor:

dFiber = conformal * (ang - TUBE_RADIUS)

Now fibers have uniform thickness in the round metric on 𝑆3. Near the image of the north pole (where stereographic projection stretches space), the fibers swell dramatically. This reveals the conformal distortion and gives a more honest picture of the geometry on 𝑆3.

Base point distributions

The shader draws the fiber over each point returned by basePoint(i). Two distributions are provided:

The latitude rings layout places points along parallels of 𝑆2. All fibers in one ring lie on the same Clifford torus (the preimage of a latitude circle under 𝜋), so this is good for seeing individual tori clearly.

The Fibonacci spiral distributes points approximately uniformly over 𝑆2 using the golden angle. This shows fibers from many different tori simultaneously, giving a fuller picture of the global structure.

Fibers are colored by their base point: hue encodes longitude on 𝑆2, brightness encodes latitude. Linked fibers (which come from nearby base points) get similar colors, making the linking visible.

3.0.6Hopf Preimage of Curves

The Hopf fibration shader draws individual fibers — the preimage of a finite set of points on 𝑆2. This shader generalizes: draw a curve 𝑓(𝑤) =0 in the plane, and visualize its full preimage as a surface in 3.

The chain of maps is:

𝑥3𝜎1←←←←←←←←𝑞𝑆3𝜋𝑆2stereo←←←←←←←←←←←𝑤2

A curve 𝑓(𝑤) =0 in the plane pulls back through this chain to a surface in 3. If 𝑓 defines a circle on 𝑆2, the preimage is a torus (the union of all Hopf fibers over that circle). If 𝑓 defines a lemniscate, you get a pinched surface. An elliptic curve gives a surface of higher genus.

The user interface is as simple as it gets: write float curve(vec2 w) returning 𝑓. The gradient is computed numerically inside the SDF — two extra evaluations of curve per sample, which costs almost nothing compared to the geometric chain (𝜎1, 𝜋, stereo) that dominates each step.

The SDF via chained conformal factors

The distance from 𝑥 3 to the preimage surface is:

sdf(𝑥)|𝑥|2+12(|𝑤|2+1)|𝑓(𝑤)||𝑓(𝑤)|

Each factor has a geometric meaning. The ratio |𝑓|/|𝑓| is the Euclidean distance to the curve in the plane (the standard gradient-corrected distance estimate). The factor 2/(|𝑤|2 +1) is the conformal factor of stereographic projection 𝑆2 2, converting the plane distance to a spherical distance. The factor (|𝑥|2 +1)/2 is the conformal factor of 𝜎1:3 𝑆3, converting the 𝑆3 distance to a Euclidean distance. The Riemannian submersion 𝑆3(1) 𝑆2(1/2) contributes the remaining factor of 1/2, which cancels the 2 in the numerator.

This is the same gradient-correction idea from the 2D level set shaders, but now it's being composed through three conformal maps.

Raymarching a thin shell

The preimage is a surface (codimension 1), not a solid region, so it has to be rendered as a thin shell: |sdf(𝑥)| <𝜀. Raymarching abs(d) - thickness is notoriously fragile — the SDF has a crease at the zero-set, and sphere tracing steps right over it.

The shader avoids this by stepping with the signed distance to the zero-set (not the shell). This makes the ray decelerate as it approaches the surface from either side. As a safety net, it tracks sign changes between consecutive samples: if the signed distance flips, the ray has crossed the zero-set, and the shader bisects to find the crossing. This gives clean surfaces even for thin shells.

Example curves

The shader includes several curves to try: