Conformal Texture Mapping
In two previous articles, I’ve explored some unusual methods of texture mapping—beyond the conventional approach of linearly interpolating UV coordinates across triangles. This post is a sort of continuation-in-spirit of that work, but I’m no longer focusing specifically on quadrilaterals.
A problem that often afflicts texture mapping on smooth, curvy models (such as characters) is distortion: in some regions, the texture may appear overly squashed, stretched, or sheared on the 3D model. A related but distinct problem is that of different regions of the model having different texel density, due to varying scale in the UV mapping. I wanted to explore these issues mathematically. Are there ways to create texture mappings that have low distortion by construction?
Ultimately, I didn’t come to an altogether satisfying resolution of this question, but I encountered plenty of interesting math along the way and I want to share some of it. This post will be on the more esoteric, less immediately-applicable side—but I hope you’ll find the topic intriguing nonetheless.
Quantifying Texture Distortion
How can we mathematically characterize “distortion” in a texture mapping? To begin with, we should make a distinction between local and global forms of distortion. Local distortion would be visible when zooming in on a small region of the model—looking at a single triangle, or a small neighborhood around a point. Conversely, global distortion might only show up when you look at the whole model and compare texture scale and orientation across widely separated points.
Some amount of global distortion is inevitable in mapping a flat, 2D texture to a non-flat, 3D object. It’s not necessarily a problem, and it can even be useful in some cases to allow varying texel density to concentrate more texels in more-important or more-detailed parts of a model. For example, human head models usually give more texel density to the face region than to the sides, top, and back of the head.
However, local distortion is usually undesirable. It changes the shapes of features in the texture, distorts the shape of filter kernels operating in texture space, and gives unequal texel density along different axes—bad news all around.
To measure local distortion, we can look at the tangent basis implied by the UVs assigned on the mesh—the same basis we typically compute as part of the setup for normal and parallax mapping.
This basis can be computed per-triangle, and consists of the two 3D vectors within the triangle’s plane that correspond to the texture’s U and V axes—known as the tangent and bitangent vectors, respectively. (The triangle normal is usually included as a third basis vector, but we won’t need that here.) If the triangle’s vertex positions are $p_1, p_2, p_3$ and the corresponding UVs are $(u_1, v_1) \ldots (u_3, v_3)$, then the tangent and bitangent vectors $T, B$ can be defined by: $$ \begin{aligned} p_2 - p_1 &= (u_2 - u_1)T + (v_2 - v_1)B \\ p_3 - p_1 &= (u_3 - u_1)T + (v_3 - v_1)B \end{aligned} $$ These equations can be cast in matrix form, and solved as follows: $$ \begin{bmatrix} T_x & B_x \\ T_y & B_y \\ T_z & B_z \end{bmatrix} = \begin{bmatrix} (p_{2x} - p_{1x}) & (p_{3x} - p_{1x}) \\ (p_{2y} - p_{1y}) & (p_{3y} - p_{1y}) \\ (p_{2z} - p_{1z}) & (p_{3z} - p_{1z}) \end{bmatrix} \begin{bmatrix} (u_2 - u_1) & (u_3 - u_1) \\ (v_2 - v_1) & (v_3 - v_1) \end{bmatrix}^{-1} $$ In the case of a general parameterized surface given by $p(u, v)$, the tangent basis at a point is defined as $T = \partial p / \partial u, B = \partial p / \partial v$.
When using a tangent basis for normal mapping, you’d probably orthonormalize it at this point. Here, we don’t want to do that—the “raw” tangent basis contains the information we want to extract about texture distortion.
There are a couple of different ways a texture can be distorted locally. One is for it to be non-uniformly scaled; another is for it to be sheared:
Both of these effects can be measured by looking at the tangent basis. Nonuniform scaling can be measured by comparing the lengths of $T$ and $B$, and shear can be measured by the angle between them; an unsheared texture mapping should have $T$ and $B$ perpendicular.
A convenient metric for both forms of distortion is the eccentricity of the ellipse created by transforming a unit circle from tangent space to model space. If the mapping is undistorted, it will map the unit circle to another circle; if there’s any nonuniform scaling or shear present, the circle will get elongated into an ellipse (though not necessarily an axis-aligned one):
How can we compute the eccentricity from the tangent basis? The major and minor radii of the ellipse are the singular values of the tangent-to-model transform (i.e. the $[T, B]$ matrix). I’ll skip the detailed derivation, but I found that the major and minor radii $a, b$ can be expressed in terms of $T$ and $B$ as follows: $$ \begin{aligned} a^2 &= \tfrac{1}{2} \left[ (T^2 + B^2) + \sqrt{(T^2 - B^2)^2 + 4(T \cdot B)^2} \right] \\ b^2 &= \tfrac{1}{2} \left[ (T^2 + B^2) - \sqrt{(T^2 - B^2)^2 + 4(T \cdot B)^2} \right] \end{aligned} $$ The eccentricity of the ellipse can then be computed as: $$ \epsilon = \sqrt{1 - \frac{b^2}{a^2}} $$ This value equals 0 when the ellipse is a circle, and grows toward 1 as it becomes more elongated.
Conformal Maps
If a texture mapping—either on a triangle mesh or a general parameterized surface—has no local distortion anywhere, i.e. its eccentricity equals 0 at every point, then it belongs to a class known as conformal maps.
Conformal maps are a rich seam of mathematics, with a lot of connections to deep parts of geometry, analysis, and mathematical physics. In two dimensions, they’re particularly powerful and flexible (their usefulness falls off in higher dimensions).
Moreover, conformal maps are oddly aesthetically pleasing. 😄 There’s often a rather soothing quality of smoothness to them, owing to their lack of local distortion.
The key geometric property of these maps is that they preserve angles. To be precise: if two lines or curves intersect at a certain angle, then their images under a conformal map will intersect with the same angle. However, distances aren’t preserved in general: a conformal map may scale things up and down, with different scale factors at different points. As a result, shapes and sizes of things may be distorted in a global sense.
Another way to express the same idea is that a conformal map can be approximated to first order near any point as a similarity transformation—a linear transformation with no shear or nonuniform scaling, only rotation and uniform scaling.
We can also relax the definition of a conformal map by allowing the eccentricity to be bounded by a constant, $0 \leq \epsilon \leq \epsilon_{\text{max}}$, rather than requiring it to be exactly zero everywhere. This is called a quasiconformal map.
Möbius Transformations
To get some initial intuition for what conformal maps are like, it’s useful to narrow our focus to a specific type of conformal map that’s easy to analyze and play with. For this, we’ll look at Möbius transformations, which are just about the simplest conformal maps that are interesting enough to be worth studying. (They’re named after August Ferdinand Möbius, the same fellow better-known for the Möbius strip; he also invented homogeneous coordinates.)
In 2D, the most straightforward way to define these maps is with complex numbers. A 2D Möbius transformation has the form: $$ f(z) = \frac{az + b}{cz + d}, \qquad z \in \mathbb{C} $$ where $a, b, c, d \in \mathbb{C}$ are some constants, which should satisfy $ad - bc \neq 0$ (or the transform will be degenerate).
Here’s a Shadertoy that applies a Möbius transformation to a coordinate grid, animating the parameters over time. While watching this, pay attention to the grid intersections. Though the straight lines become curved, and the overall shapes may distort quite a bit, wherever two grid lines meet each other they always remain perpendicular! That’s the conformal property at work.
A few interesting facts about Möbius transformations, in no particular order:
- They form a mathematical group. The composition of two Möbius transformations is another Möbius, and the inverse of a Möbius is another Möbius!
- Although it has four complex parameters (eight components total), a Möbius has only six degrees of freedom. That’s because an overall complex factor multiplied into all the parameters has no effect. In other words, parameters $(a, b, c, d)$ and $(ua, ub, uc, ud)$ specify the same transformation, for any $u \neq 0 \in \mathbb{C}$.
- Möbius transformations generally map lines to circles, and circles to other circles. (And, occasionally, circles to lines.)
- Möbius transformations can be defined in higher dimensions as well, and they behave analogously, with (hyper)planes mapping to (hyper)spheres.
- In 3D and higher, Möbius transformations are the only conformal maps that exist. In 2D, they’re just a small subset of a much richer collection of conformal maps.
The six degrees of freedom of a 2D Möbius are just enough that we can construct a transformation to map any three chosen points to three others. This makes it tempting to think that we could use them for texture mapping, by applying a Möbius to each triangle of a 3D model.
Unfortunately, the mapping will not in general be continuous from one triangle to the next: the shared edge will be mapped to different circles by each triangle’s Möbius, and there are no more degrees of freedom available to try to fix it. There have been some papers, like this one, trying to patch together piecewise Möbius transformations with least-squares optimization, to produce approximate conformal maps.
There’s a good deal more that could be said about Möbius transformations. However, let’s move on for now and look at a broader set of conformal maps.
Holomorphic Functions
From this point forward, we’ll restrict ourselves to the 2D case. Given that Möbius transformations aren’t quite as flexible as we might like, how can we construct other types of conformal maps?
It’s no accident that we used complex numbers to define the Möbius transformation in the previous section. Complex numbers, in fact, are intimately linked to conformal maps in 2D.
Why is this? If you recall, I mentioned earlier that one way to define a conformal map is that it can be approximated to first order near any point as a similarity transformation. Well, multiplication by a (nonzero) complex number implements a 2D similarity transformation: if $z = re^{i\theta}$, then multiplication by $z$ will scale by $r$ and rotate by $\theta$.
As you may have guessed, “approximated to first order near a point” is a long-winded way of talking about derivatives. So, what we’re saying is that for a function on $\mathbb{C}$ to be a conformal map, its derivative at any point should act as a complex number. In other words, it should be complex differentiable. Functions that satisfy this requirement are called holomorphic functions.
I should note that being complex-differentiable is different—and much more restrictive—than just being differentiable as a vector function on $\mathbb{R}^2$. In other words, it’s not enough for the $x$ and $y$ components of a mapping to individually be differentiable. As seen before, the derivative at each point must take the form of a similarity transform; formally, the $x$ and $y$ components must satisfy the Cauchy–Riemann equations, which state that the mapping’s tangent and bitangent vectors must be orthogonal, and of equal length, at each point. Only when these conditions are satisfied can you interpret the mapping as a differentiable complex function of a complex variable.
Fortunately, the basic differentiation rules we learn in school for real-valued functions do carry over to complex functions! In particular, all the basic arithmetic operations on complex numbers are differentiable. So, to make a holomorphic function, all we have to do is write down an algebraic formula—pretty much whatever we like—for a complex function $f(z)$. These functions will always produce conformal maps, by construction.
We can also use exp, log, and trig functions, as well as many other special functions; they can be can be extended into the complex domain and are holomorphic too. However, there are a few operations we can’t use: the complex conjugate, magnitude, argument, or real or imaginary parts of a complex number. Those aren’t holomorphic, it turns out. As long as we follow these rules, any function we build will be holomorphic and therefore conformal.
So, okay! We know a lot about how to build functions to accomplish specific things. In fact, we can try taking functions we’ve already got experience with, and just extending them to the complex domain. For instance, take a 1D cubic Bézier curve with control points $c_0, c_1, c_2, c_3$: $$ B(t) = (1-t)^3 c_0 + 3(1-t)^2 t c_1 + 3(1-t) t^2 c_2 + t^3 c_3 $$ We’ll use the same formula, but make everything complex numbers—both the control points and the input variable. $$ B(z) = (1-z)^3 c_0 + 3(1-z)^2 z c_1 + 3(1-z) z^2 c_2 + z^3 c_3 $$ Let’s see what it looks like! Here I’ve set it up to produce the identity map with a little bit of animated (complex) wiggle in the tangents at the endpoints, 0 and 1.
Huh. Well, it’s doing…something. The mapping does seem to be conformal, for the most part—right angles are staying right angles. But why are we seeing the unit square getting duplicated and kinda merging with itself into curvy 8-sided and 12-sided figures? Why do the grid lines seem to break and reconnect all the time? This is interesting to look at, but doesn’t seem too useful for texture mapping. What’s going on?
Invertibility And Critical Points
The problem comes down to invertibility. When we get this “multiple copies” phenomenon, what we’re seeing is the complex function mapping multiple regions of its domain (the Shadertoy’s screen space) to the same region of its range (the coordinate grid being visualized). In other words, the function isn’t one-to-one—and therefore it fails to be invertible.
Stepping back to real-valued functions for a moment may help clarify. Here’s the graph of the real function $f(x) = x^2$:
It’s a parabola, of course. It’s also one of the simplest examples of a non-invertible function. Why? Because inputs $x$ and $-x$ both map to the same value, $x^2$. If you squint at it a bit, you can see the graph as being made up of two distorted copies of the positive half of the real line.
The complex extension of this function $f(z) = z^2$, works the same way—but a bit more dramatically, it gives us two distorted copies of the entire complex plane, squished together!
Now, a funny thing happens when we look at higher powers. When we restrict ourselves to the real numbers only, $x^3$ is invertible, $x^4$ is not, $x^5$ is, and so on: odd powers are invertible, while even ones aren’t. Even powers all map $x$ and $-x$ to the same value, while odd powers maintain the distinction.
However, when extended to the complex plane, $z^n$ always fails to be invertible unless $n = 1$! In fact, graphing $z^n$ gives you $n$ copies of the plane, squished together into wedges around the origin. All of the copies are conformal mappings—but their scale becomes increasingly extreme as you approach the origin, and the function is not strictly conformal at the origin.
An example for the $n = 3$ case, which you can verify by working out the calculations if you like: $$ \begin{aligned} 1^3 &= 1 \\ (-\tfrac{1}{2} + \tfrac{\sqrt{3}}{2} i)^3 &= 1 \\ (-\tfrac{1}{2} - \tfrac{\sqrt{3}}{2} i)^3 &= 1 \\ \end{aligned} $$ Three distinct complex numbers, when cubed, all give the same result of 1.
In general, a holomorphic function will fail to be invertible wherever it has a critical point—a point where its derivative equals zero. In the vicinity of such a place, the function will locally behave like $z^n$: it will have $n$ copies of the surrounding region of the complex plane, squished together into wedges around the critical point. Here, $n$ is one plus the order (aka multiplicity) of the zero in the derivative.
This is what was going on in the Bézier example from the previous section. Since it was a cubic polynomial, its derivative is quadratic, and quadratic polynomials have two zeros. So the cubic Bézier curve has two critical points, which move around the plane as the curve’s parameters change. When the critical points get too close to the region of interest (the unit square, say), we can see two or even three copies of that region mushed together.
Compulsory Criticality
If we want to build holomorphic functions that are guaranteed to be invertible, we need to avoid critical points, i.e. zeros of the derivative. Unfortunately, this turns out to be more challenging than you might expect.
It’s easy to make a real polynomial that doesn’t have any zeros, such as $f(x) = x^2 + 1$. Correspondingly, it’s easy to make a real polynomial that’s everywhere invertible, by taking the integral of one that doesn’t have any zeros: $\int (x^2 + 1) \, dx = \tfrac{1}{3}x^3 + x$, for example.
However, a crucial difference between the real and complex domains comes into play here: while zeros are optional for real polynomials, they are mandatory for complex ones. A complex polynomial of degree $n \geq 1$ always has exactly $n$ zeros (counted with multiplicity). For example, the zeros of $z^2 + 1$ are at $z = \pm i$. Thus, a complex polynomial of degree $n \geq 2$ always has at least one critical point, and possibly up to $n - 1$ of them.
In other words, it’s impossible for complex polynomials of degree 2 or higher to be globally invertible.
Polynomials aren’t the only functions out there, though. What about rational functions? They obey a similar dictum: if a rational function is degree $p$ over degree $q$, then there are potentially $p + q - 1$ critical points (remember the quotient rule)—not to mention anywhere from 1 to $q$ poles, where the denominator goes to zero and the rational function blasts off to infinity. Incidentally, poles behave similarly to critical points in some ways: they come in different orders, and a pole of order $n$ will have $n$ copies of the complex plane around it. So poles are another way to break invertibility.
This leads to the somewhat depressing conclusion that the only polynomial or rational complex functions that are everywhere invertible are those that have degree at-most-1 over degree at-most-1. In other words: Möbius transformations.
Conclusion
So, polynomial and rational functions aren’t good enough—we’d need to dig deeper if we’re to find a class of invertible holomorphic functions more powerful than Möbius. One possibility might be to define $f(z) = \int e^{g(z)} \, dz$, where $g(z)$ is some holomorphic function without poles. Then $f(z)$ will have neither poles nor critical points, since its derivative is $e^{g(z)}$. (The complex exponential function, like the real version, is everywhere nonzero.)
Now, if we step back for a moment, we don’t necessarily need global invertibility. If we’re mainly interested in some bounded region—such as the unit square, for texture mapping—then it may well be sufficient for our purposes to maintain local invertibility there. This could be done by keeping critical points and poles far enough from the region of interest that they don’t weird things out too much. That still seems like a challenging juggling act to perform, though—and moreover, the more degrees of freedom we have in our function, the more critical points or poles we probably have to worry about.
In the course of reading up on this subject, I also found another paper that takes a quite different approach—based on Cauchy’s integral formula—to constructing conformal maps. I might write about that in another post sometime—there’s a lot more to this rabbit hole of math, and it’s interesting stuff, but ultimately it doesn’t seem very practical.
For more reading on the theory of holomorphic functions, see Terry Tao’s complex analysis course notes. (Be warned, it’s a graduate-level course and the notes are pretty dense and formal.)