I - Introduction

Hello fellow life forms!

In today’s post, we take a look at the theory needed for understanding the famous tangent-space mapping, which is the basis for most meso-scale rendering techniques. This post is not intended to be an in-depth mathematical treatment of tangent space, manifolds, mappings or anything like that. It is intended to lay the groundwork in a reasonable manner, so that when I mention Tangent Space or something related to it, you can easily visualize what I mean.

Figure 1
The Blue Marble - December 7, 1972. NASA

So, let’s start by defining what we mean by Tangent Space (in a loose manner): let Ts(x)T_s(x) be a real vector space related to point xx on an object. We call it a Tangent Space when such a space contains all possible tangent directions passing through xx. Think about a sphere SS: at each point psp_s we can define three vectors: one along the horizontal, one into the horizon and another along the vertical, such that they tell how much we walk in each of those directions; if we combine these, we walk tangent to the sphere. If you are used to a little Calculus, you already know these vectors are nothing more than partial derivatives (∂S∂x,∂S∂y,∂S∂z)(\frac{\partial S}{\partial x}, \frac{\partial S}{\partial y}, \frac{\partial S}{\partial z}) along xx, yy and zz, respectively. We call the combination of these vectors the Gradient ∇⃗(S)\vec{\nabla} (S) and it defines the tangent plane at each point xsx_s. And yes, it is the same gradgrad you see in ML, Optimization and Physics. Thus, mathematically, we have:

∇⃗(xs,ys,zs).(xs,ys,zs)=0(1)\vec{\nabla} (x_s, y_s, z_s) . (x_s, y_s, z_s) = 0 \tag 1

Well, I believe that with this little introduction you were able to at least get a feel for what a tangent space is! I took a more continuous route to give the example, but in this series we’re gonna stick exclusively to discrete objects, i.e. triangles. Continuous methods are useful and are the right tool for other mathematical models [1], but we aren’t going to look at those here! So, let’s see how we can represent our Tangent Space in terms of vertices and triangles only.

II - Tangent Space

Look at the figure below. Assume we have a discrete sphere SdS_{d} (in blue) and a plane π\pi (in red); we can define a general vertex viv_i along with two vectors T⃗\vec{T} and B⃗\vec{B}, the tangent and bitangent (the term binormal is a mistake [2]), respectively, such that:

vi∈(Sd∩π)(2) v_i \in ( S_{d} \cap \pi) \tag 2 T⃗∧B⃗∈π(3) \vec{T} \land \vec{B} \in \pi \tag 3 T⃗⊥B⃗(4) \vec{T} \perp \vec{B} \tag 4 N⃗=T⃗×B⃗(5) \vec{N} = \vec{T} \times \vec{B} \tag 5

BEWARE! ∧\land in (3) is the set notation for and, not the wedge product from Grassmann algebra.

Figure 1
Figure 1: The Tangent Space
If we were to get back to the previous example, we could make a reasonable approximation near viv_i, such that T⃗≈∂S∂x\vec{T} \approx \frac{\partial S}{\partial x} and B⃗≈∂S∂y\vec{B} \approx \frac{\partial S}{\partial y}. Of course, the more we walk along these vectors, the more error we get; nothing new here! It is clear we are going to get error; we’ve been approximating things since the definition of our sphere. The point to take away is the following: the more tessellated your mesh is, the smaller the plane is, and so is the error. But wait! Wait … before you go nuts subdividing all your meshes into oblivion, there is something I need to mention. This is a blog focused on rendering engineering, isn’t it? So, I’m not just gonna say this is an implementation problem and move on, nope! As with everything in graphics/rendering, this is all about visual perception; we’re not trying to run a precise simulation here, our goal is one such that our final render looks as amazing as possible in the smallest time available! It is worth mentioning that the Bump Mapping group can be seen as a compression technique in itself: you’re mapping true geometry to heightfield representations of small areas! So don’t defy its purpose; maintain a good balance between geometry count and normal-map data resolution.

III - Calculating the Tangent and Bi-Tangent Vectors

Figure 2
Figure 2: The Tangent Space given a Triangle

OK! Without further ado, let’s get down to the math! This is what you are here for, right? Look at figure 2, above. Assume we have a generic vertex inside our triangle, let’s call it V(u,v)V(u, v), such that:

V(u,v)=vo⃗+(u−u0)u^+(v−v0)v^(6) V(u, v) = \vec{v_o} + (u - u_0)\hat{u} + (v - v_0)\hat{v} \tag 6

Equation (6) is nothing more than a vector inside our triangle defined in terms of v0⃗\vec{v_0} as the starting point for the tail and the tip ending at some generic point (inside the triangle!) with coordinates (u,v)(u, v). Let’s plug in v1v_1 to see what we get:

V(u1,v1)=v0⃗+(u1−u0)u^+(v1−v0)v^ V(u_1, v_1) = \vec{v_0} + (u_1 - u_0)\hat{u} + (v_1 - v_0)\hat{v} V(u1,v1)=v0⃗+(v1⃗−v0⃗) V(u_1, v_1) = \vec{v_0} + (\vec{v_1} - \vec{v_0}) V(u1,v1)=v1⃗ V(u_1, v_1) = \vec{v_1}

If you are still not convinced, now would be a good moment to stop, grab a piece of paper and a pen and draw the geometry!

Rearranging equation (6), we get:

V(u,v)−vo⃗=(u−u0)u^+(v−v0)v^ V(u, v) - \vec{v_o} = (u - u_0)\hat{u} + (v - v_0)\hat{v} V(u,v)−vo⃗=uu^+vv^−(u0u^+v0v^) V(u, v) - \vec{v_o} = u\hat{u} + v\hat{v} - (u_0\hat{u} + v_0\hat{v}) V(u,v)−vo⃗=uu^+vv^−v0⃗ V(u, v) - \vec{v_o} = u\hat{u} + v\hat{v} - \vec{v_0}

Adding v0⃗\vec{v_0} on both sides, we have:

V(u,v)=uu^+vv^(7) V(u, v) = u\hat{u} + v\hat{v} \tag 7

Applying (7) to both v1⃗\vec{v_1} and v2⃗\vec{v_2}:

V1(u1,v1)=v1⃗=u1u^+v1v^V2(u2,v2)=v2⃗=u2u^+v2v^(8) V_1(u_1, v_1) = \vec{v_1} = u_1\hat{u} + v_1\hat{v} \\ V_2(u_2, v_2) = \vec{v2} = u_2\hat{u} + v_2\hat{v} \tag 8

Wait a minute … We now have a 2x2 system of equations:

(V1V2)=(u1v1u2v2)(u^v^)(9) \begin{pmatrix} V_1 \\ V_2 \end{pmatrix} = \begin{pmatrix} u_1 & v_1 \\ u_2 & v_2 \end{pmatrix} \begin{pmatrix} \hat{u} \\ \hat{v}\end{pmatrix} \tag 9

Let’s see what we know about it! We already know the values of both V1{V_1} and V2{V_2}. We also know u1u_1, u2u_2, v1v_1 and v2v_2, they are given! So… what are we left with? Yeah! You guessed it! It is u^\hat{u} and v^\hat{v}. Solving it using Cramer’s Rule, we get:

(u^v^)=1u1v2−u2v1(v2−v1−u2u1)(V1V2)(10) \begin{pmatrix} \hat{u} \\ \hat{v} \end{pmatrix} = \frac{1}{u_1v_2 - u_2v_1}\begin{pmatrix} v_2 & -v_1 \\ -u_2 & u_1 \end{pmatrix} \begin{pmatrix} V_1 \\ V_2 \end{pmatrix} \tag{10}

So, there you go! We now have two vectors u^\hat{u} and v^\hat{v} that follow all the constraints we established in section II. We are done then, right? Well… not exactly. You may have noticed that at no point did I mention the vectors T⃗\vec{T} and B⃗\vec{B}. So, what’s up with that? Well, as it turns out, u^\hat{u} and v^\hat{v} do not represent the tangent vectors for a vertex in tangent space, but instead they represent the tangent vectors for a triangle in texture-space! Let’s see how we can fix that.

IV - Practical Considerations

-> First, we must go from a triangle to a vertex. To do so, we must first analyze a vertex and what is adjacent to it. If you look at a peak vertex, you can see that it represents (approximately) a region in which we should have a smooth surface. Thus, to correctly represent the information present at this point, we must average the information of all kk triangles that share this vertex viv_i:

vi′=∑i=0kvi∣∣∑i=0kvi∣∣(11) v_{i}' = \frac{\sum_{i=0}^{k}v_i}{||{\sum_{i=0}^{k}v_i}||} \tag{11}
Figure 3
Figure 3: Catmull–Clark subdivision - If you look at a and then d, you can clearly see how a polygon that doesn’t have enough subdivisions will need its data averaged at each vertex, so it better represents the intended values.

-> Second, after the previous process of smoothing the vertices and then linearly interpolating them for pixel shader consumption, we lose property (4) from section II. Therefore, we should also use the Gram–Schmidt process to keep our vectors perpendicular to each other!

Finally! We now have two vectors T⃗\vec{T} and B⃗\vec{B}, such that all properties from section II are preserved and we can safely use them to transform between world/object and tangent spaces for our needed calculations. But this is a topic for the next post! See you there, bye!

Live long and prosper!!!

References

1 - (1978)Simulation of Wrinkled Surfaces - Jim Blinn - Association for Computing Machinery, Inc.

2 - (2011)Mathematics for 3D Game Programming and Computer Graphics, Third Edition - Eric Lengyel - Cengage Learning PTR