Introduction

Every video game car, movie creature, and virtual building is covered in textures — flat images wrapped around 3D geometry so convincingly that the seams disappear. The trick that makes it work is deceptively simple: texture mapping.

The core idea is to give every vertex of a 3D mesh a pair of coordinates (u,v)(u, v) in the range [0,1]2[0, 1]^{2} that point into a flat image called the texture. When the GPU rasterizes a triangle — converts it to screen pixels — it smoothly interpolates the (u,v)(u, v) values across the triangle's interior, then looks up the color in the texture at each interpolated coordinate.

But there is a catch. Naive linear interpolation of UV coordinates in screen space produces visible distortion: grid lines bow, text warps, and checkerboard patterns pinch toward the horizon. The fix is perspective-correct interpolation, which divides by the depth ww before interpolating and multiplies back after. This one adjustment is what separates the blocky software renderers of the early 1990s from the crisp texturing of every GPU since.

Texture mapping was formalized by Ed Catmull in 1974 in his PhD thesis at the University of Utah — the same thesis that introduced the z-buffer. Both ideas became so foundational that today's rasterization pipelines are built on them.

Try It: Unfold the Cube

A cube has six square faces. In a texture atlas they are laid out as flat patches, each occupying a region of the [0,1]2[0,1]^{2} UV square. Click any face of the cube below to highlight it and see which UV island it corresponds to on the right.

<!-- {{c_main_layout}} -->
<div class="container">
  <div class="panel">
    <p class="label">{{label_3d}}</p>
    <canvas id="cube3d" width="200" height="200" title="{{title_cube}}"></canvas>
    <p class="hint">{{hint_click}}</p>
  </div>
  <div class="panel">
    <p class="label">{{label_uv}}</p>
    <canvas id="uvmap" width="200" height="200" title="{{title_uv}}"></canvas>
    <p class="hint">{{hint_uv}}</p>
  </div>
</div>
<div class="info" id="info">{{msg_select}}</div>
<button id="resetBtn" type="button">{{btn_reset}}</button>
/* {{c_layout}} */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #fff; }
.container { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; padding: .5rem; }
.panel { display: flex; flex-direction: column; align-items: center; }
.label { font-weight: 700; font-size: .85rem; margin: 0 0 .3rem; color: #1d3557; }
canvas { border: 1px solid #cdd9e3; border-radius: 8px; cursor: pointer; background: #f4f7fa; }
.hint { font-size: .78rem; color: #666; margin: .3rem 0 0; max-width: 200px; text-align: center; }
.info { text-align: center; font-size: .9rem; font-weight: 600; margin: .6rem 0; min-height: 1.4em; color: #1d3557; }
button { display: block; margin: 0 auto; font: 600 14px system-ui; padding: .4rem .9rem;
         border: 1px solid #1d3557; background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
// Code not found

Notice how each face is a simple square in UV space. The UV layout shown here is the classic cross unfold — one of many valid unfoldings. Any point on the 3D cube maps to exactly one pixel of the texture via its (u,v)(u, v) coordinates, and when the GPU rasterizes a triangle it interpolates those coordinates across every screen pixel inside the triangle.

The Real Complexity

The straightforward version of texture mapping is two steps: (1) assign (u,v)(u, v) to vertices, (2) interpolate. But several layers of engineering sit underneath:

Perspective-correct interpolation. In screen space, a distant point on a triangle is compressed relative to a nearby one, so linear interpolation of (u,v)(u, v) is wrong. The correct formula interpolates u/wu/w and v/wv/w (where ww is the homogeneous depth) and then divides by 1/w1/w recovered at each pixel:

upixel=(u0/w0)λ0+(u1/w1)λ1+(u2/w2)λ2(1/w0)λ0+(1/w1)λ1+(1/w2)λ2u_{\text{pixel}} = \frac{\,(u_0/w_0)\lambda_0 + (u_1/w_1)\lambda_1 + (u_2/w_2)\lambda_2\,}{(1/w_0)\lambda_0 + (1/w_1)\lambda_1 + (1/w_2)\lambda_2}

where λ0,λ1,λ2\lambda_0, \lambda_1, \lambda_2 are the barycentric coordinates of the pixel and w0,w1,w2w_0, w_1, w_2 are the vertex depths. Without this fix, textures shear toward the vanishing point.

Mipmaps. When a textured surface is far away, one screen pixel covers many texels. Sampling just the nearest texel produces aliasing — flickering, moiré patterns. A mipmap is a precomputed pyramid of down-scaled copies of the texture (each level half the size of the previous). The GPU selects the mipmap level d=log2(max((u,v)/x,(u,v)/y)T)d = \log_2(\max(\|\partial(u,v)/\partial x\|, \|\partial(u,v)/\partial y\|) \cdot T) that best matches the pixel footprint, then blends between adjacent levels (trilinear filtering).

UV seams and atlasing. A single mesh can have hundreds of UV islands — disconnected patches that pack into one texture atlas. Generating a good atlas (minimizing wasted space, avoiding seam stretching) is a packing problem related to bin packing. Modern tools like xAtlas and Blender's smart UV project solve it automatically.

Normal and displacement maps. A flat polygon can look bumpy if you texture-map a normal map — an image encoding surface normals per texel — and use those normals in the lighting equation instead of the true polygon normal. Displacement maps go further and actually move vertices, adding real geometric detail.

Where It Matters

UV texture mapping is the engine behind visual richness in virtually every domain that renders 3D content:

  • Video games: a single low-polygon mesh can look highly detailed when wrapped in a high-resolution diffuse, specular, normal, and emissive texture set. Modern PBR (Physically Based Rendering) workflows layer six or more texture maps per material.
  • Film VFX: photorealistic creatures and environments are built from millions of polygons, each UV-mapped to multi-gigabyte texture atlases painted by hand or generated by photogrammetry.
  • Augmented and virtual reality: real-world photographs are projected onto 3D geometry reconstructed from depth sensors. The UV parameterization determines how cleanly the photo aligns to the mesh.
  • Medical imaging: CT or MRI volume data is mapped onto patient-specific 3D organ meshes for surgical planning and simulation, using the same barycentric interpolation.
  • Digital fabrication: UV unfolding is used to flatten 3D surfaces before cutting patterns from flat sheet material — the seam placement problem is essentially the same as in game asset production.

The algorithm itself is the same across all these domains: find a (u,v)(u, v) parameterization of the surface that minimizes distortion, then sample a texture image at those coordinates using perspective-correct interpolation.

Conclusion

Texture mapping is one of those rare ideas that solved a problem so cleanly it has never needed replacing. A flat image, a pair of coordinates per vertex, and one division by depth — that is essentially all it takes to clothe an entire virtual world.

Ed Catmull's insight in 1974 was that the 3D and 2D problems could be separated: design the geometry freely, paint the surface freely, and connect them through UV coordinates at render time. Fifty years later, every GPU on the planet still works this way.

The next time you look at a video game character's jacket, a movie dragon's scales, or a virtual building's brick wall, you are seeing the same barycentric lookup that runs inside millions of triangles per second — perspective-correct, mipmap-filtered, and still fundamentally the same idea Catmull wrote down half a century ago.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/texture-mapping/Content licensed under CC BY-NC 4.0.