UV unwrapping

UV 언랩핑

Cutting a 3D surface apart along seams and laying it flat on a 2D plane — this decides where a texture lands on the surface.

Also known as: UV mappingSeamsUV layout
···
html
<div class="uv-scene">
  <div class="uv-cube" id="uv-cube">
    <div class="uv-face f0"></div><div class="uv-face f1"></div><div class="uv-face f2"></div>
    <div class="uv-face f3"></div><div class="uv-face f4"></div><div class="uv-face f5"></div>
  </div>
  <span class="uv-cap">seams → flat UV layout</span>
</div>
css
.uv-scene{position:relative;width:100%;height:100%;display:grid;place-items:center;perspective:900px}
.uv-cube{position:relative;width:1px;height:1px;transform-style:preserve-3d}
.uv-face{position:absolute;top:0;left:0;display:block;border:1px solid #ffffff66;background-image:linear-gradient(#ffffff2a 1px,transparent 1px),linear-gradient(90deg,#ffffff2a 1px,transparent 1px);background-size:25% 25%;border-radius:2px}
.f0{background-color:#ff6b6b}.f1{background-color:#ffa94d}.f2{background-color:#51cf66}.f3{background-color:#339af0}.f4{background-color:#cc5de8}.f5{background-color:#20c997}
.uv-cap{position:absolute;left:0;right:0;bottom:5%;text-align:center;font-size:clamp(9px,2.6vmin,13px);color:var(--muted)}
js
const stage = document.querySelector('.uv-scene');
const cube = document.getElementById('uv-cube');
const faces = [...cube.children];
const FOLD = [
  { rx: 0, ry: 0 }, { rx: 0, ry: 180 }, { rx: 0, ry: 90 },
  { rx: 0, ry: -90 }, { rx: 90, ry: 0 }, { rx: -90, ry: 0 },
];
const NET = [
  { x: 0, y: 0 }, { x: 4, y: 0 }, { x: 2, y: 0 },
  { x: -2, y: 0 }, { x: 0, y: -2 }, { x: 0, y: 2 },
];
let S = 30;
function layout() {
  const box = stage.getBoundingClientRect();
  S = Math.max(16, Math.min(box.width, box.height) * 0.095);
  faces.forEach((f) => { f.style.width = S * 2 + 'px'; f.style.height = S * 2 + 'px'; });
}
addEventListener('resize', layout); layout();
function lerp(a, b, p) { return a + (b - a) * p; }
function ease(p) { return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2; }
function frame(t) {
  const cycle = 9000;
  const local = (t % cycle) / cycle;
  let p;
  if (local < 0.22) p = 0;
  else if (local < 0.42) p = ease((local - 0.22) / 0.2);
  else if (local < 0.62) p = 1;
  else if (local < 0.82) p = 1 - ease((local - 0.62) / 0.2);
  else p = 0;
  faces.forEach((f, i) => {
    const fold = FOLD[i], net = NET[i];
    const rx = lerp(fold.rx, 0, p);
    const ry = lerp(fold.ry, 0, p);
    const tz = lerp(S, 0, p);
    const tx = lerp(0, net.x * S, p);
    const ty = lerp(0, net.y * S, p);
    f.style.transform = `translate(-50%,-50%) translate3d(${tx}px, ${ty}px, 0) rotateX(${rx}deg) rotateY(${ry}deg) translateZ(${tz}px)`;
  });
  cube.style.transform = `rotateX(${-18 * (1 - p)}deg) rotateY(${((t * 0.012) % 360) * (1 - p)}deg)`;
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

A texture image is just a flat rectangle (U, V coordinates from 0 to 1), so wrapping it onto a bumpy 3D surface without distortion means first deciding how to cut that surface so it lies flat. Those cut lines are seams, and the result of unfolding along them is the UV layout.

The demo shows the principle on a single cube: unfold its six faces along their seams and the box becomes a flat cross-shaped layout (a “net”). The grid on each face marks which pixel of a texture lands where on the surface.

A sphere or an organic shape needs its seams placed far more carefully than a cube to avoid stretching and overlaps. Blender’s UV Editor, Maya’s UV Editor, and Cinema 4D’s BodyPaint all let you mark seams, unwrap — automatically or by hand — then apply a checkerboard texture so any stretching, squares that go lopsided, is visible at a glance.

When to use

Revisit the UVs when a texture looks stretched on a surface, or a seam shows up as a visible crack.