한양과학기술고등학교

에듀테크 프로젝트 대시보드

Z-버퍼 & 은면 제거

  Z-버퍼 & 은면 제거 (Hidden Surface Removal)

3D 장면을 2D 화면에 그릴 때, 가려진 면(뒤에 있는 면)이 앞면을 덮어써서는 안 된다. 가장 단순한 방법인 화가 알고리즘은 폴리곤을 먼 것부터 순서대로 칠하는데, 면들이 서로 꿰뚫거나 순환하며 겹치면 어떤 순서로도 올바르게 그릴 수 없다. 현대 GPU는 픽셀마다 깊이값을 저장하는 Z-버퍼(깊이 버퍼)로 이 문제를 해결한다. 아래 데모에서 두 방식의 결과를 나란히 비교해 보자.

화가 알고리즘 vs Z-버퍼

서로 비스듬히 교차하는 삼각형 3장으로 이루어진 장면이다. 슬라이더로 장면을 회전시키면 삼각형들이 서로를 꿰뚫는다. 화가 알고리즘은 삼각형의 평균 깊이로 정렬해 통째로 칠하므로 교차선에서 무너진다. Z-버퍼는 픽셀 단위로 z를 비교하므로 교차선이 정확히 그려진다.

화가 알고리즘 (정렬 후 덮어쓰기)

Z-버퍼 (픽셀별 깊이 비교)

깊이 버퍼 시각화 (밝을수록 가까움)

35°
16 bit
화가 알고리즘 결과의 교차선을 보라. 삼각형이 통째로 앞/뒤로 판정되어 실제 교차 지점과 어긋난다. Z-버퍼 결과는 픽셀마다 독립적으로 판정하므로 깔끔하다.

Z-버퍼 알고리즘

  1. 화면과 같은 크기의 깊이 버퍼를 만들고 모든 칸을 +∞(가장 먼 값)로 초기화한다.
  2. 폴리곤을 순서 상관없이 하나씩 래스터화(픽셀로 쪼개기)한다.
  3. 각 픽셀 (x, y)에서 폴리곤의 깊이 z를 정점값의 보간으로 구한다.
  4. z < depth[x][y] 이면 — 지금까지 그린 것보다 더 가깝다면 — 색을 칠하고 depth[x][y] = z 로 갱신한다. 아니면 버린다.
  5. 모든 폴리곤을 처리하면 각 픽셀에는 가장 앞에 있던 면의 색만 남는다.
// 폴리곤마다, 그 폴리곤이 덮는 픽셀마다 for (px, py) in raster(triangle): z = interpolate_depth(triangle, px, py) // 정점 z의 무게중심 보간 if z < depthBuffer[py][px]: // 더 가까우면 colorBuffer[py][px] = shade(triangle, px, py) depthBuffer[py][px] = z // 깊이 갱신 // else: 이미 더 가까운 면이 있으니 버린다
핵심은 정렬이 필요 없다는 것. 폴리곤을 아무 순서로 던져도 픽셀별 z 비교가 최종 승자를 가려낸다. 비용은 화면 크기만큼의 추가 메모리 1장 (깊이 버퍼).

두 방식 비교

화가 알고리즘

폴리곤을 깊이순 정렬 후 먼 것부터 칠한다. 정렬 O(n log n), 깊이 버퍼 불필요. 교차·순환 겹침을 못 다루고, 겹치는 픽셀을 여러 번 칠하는 오버드로가 크다.

Z-버퍼

픽셀 단위 깊이 비교. 폴리곤 순서 무관, 교차·관통도 정확. 깊이 버퍼 메모리 1장 필요. 거의 모든 GPU가 하드웨어로 내장 (early-Z로 가려진 픽셀은 셰이딩 생략).

Z-파이팅

두 면의 z가 버퍼 정밀도 안에서 구별 안 되면 얼룩이 생긴다. 원근 투영은 가까운 쪽에 정밀도가 몰려 먼 물체에서 심하다. near 평면을 너무 가깝게 두지 않기, polygon offset 등으로 완화.

그 밖의 은면 제거

백페이스 컬링(뒤통수 면 제거), BSP 트리(공간 분할로 정확한 그리기 순서), 레이 캐스팅(광선이 처음 맞는 면) 등. 보통 Z-버퍼와 함께 쓴다.