사전 깊이 정밀도
개념

깊이 정밀도

gabury1고친 사람 github-actions[bot]

깊이 정밀도는 깊이 버퍼가 가까이 붙은 두 면의 앞뒤를 얼마나 잘 가리는지를 나타냅니다. 깊이 버퍼는 화면의 픽셀마다 거리에서 만든 숫자를 적어 두는 메모리입니다. 먼 곳일수록 앞뒤를 가리는 힘이 빠르게 떨어집니다. 이 힘을 가장 크게 좌우하는 것은 카메라가 그리기 시작하는 가장 가까운 거리입니다.

쉽고 빠른 이해

깊이 정밀도는 화면의 픽셀마다 적어 둔 깊이 값이 얼마나 잘게 나뉘는지입니다. 가까운 곳은 아주 잘게 나뉩니다. 먼 곳은 성깁니다. 카메라 코앞 10 센티미터부터 그리는 흔한 24비트 깊이 버퍼라면 1 킬로미터 밖에서 60 센티미터 떨어진 두 면이 같은 값이 되기도 합니다.

이게 모자라면 두 면의 앞뒤 판정이 픽셀마다 달라집니다. 화면에 얼룩덜룩한 무늬가 생기고 카메라가 움직일 때마다 무늬가 꿈틀댑니다.

  1. 깊이 값은 거리를 그대로 옮기지 않고 거리의 역수에 가까운 꼴로 만들어집니다
  2. 그래서 깊이 값의 눈금이 가까운 쪽에 몰리고 먼 쪽은 성깁니다
  3. 그리기 시작하는 가장 가까운 거리를 카메라에 붙일수록 눈금이 더 앞으로 쏠립니다

되찾는 데는 대가가 따릅니다. 가장 가까운 거리를 뒤로 밀면 코앞의 물체가 잘립니다. 비트를 늘리면 메모리를 더 씁니다. 깊이 값을 뒤집어 담으면 앞뒤를 비교하는 방향까지 함께 뒤집어야 합니다.

상세

이 절은 깊이 정밀도가 무엇을 재는지, 왜 거리마다 달라지는지, 무엇으로 되찾는지를 봅니다. 숫자는 모두 24비트 깊이 버퍼로 셉니다.

눈금이 앞쪽에 몰린 줄자

눈금이 앞쪽에 몰린 줄자를 떠올려 보겠습니다. 처음 한 뼘은 1 밀리미터마다 눈금이 있습니다. 끝으로 갈수록 한 칸이 몇 센티미터씩 벌어집니다.

이 줄자로 끝 쪽에서 길이가 조금 다른 물건 둘을 재면 둘이 같은 칸에 들어갑니다. 줄자는 둘을 같은 길이로 읽습니다. 앞쪽에서라면 쉽게 갈렸을 차이입니다.

깊이 버퍼가 이런 줄자입니다. 깊이 정밀도는 어느 거리에서 눈금 한 칸이 얼마나 넓은지를 말합니다. 칸이 좁으면 정밀도가 높고 칸이 넓으면 낮습니다.

깊이 버퍼와 눈금의 개수

깊이 버퍼는 화면의 픽셀마다 깊이 값을 적어 두는 메모리입니다. 깊이 값은 그 픽셀에 보이는 면까지의 거리에서 만든 숫자입니다. 거리 자체가 아닙니다. 어떻게 만드는지는 아래 「거리에서 깊이 값으로 가는 변환」에서 봅니다.

새로 칠할 색이 오면 그 픽셀에 적힌 깊이 값과 비교합니다. 새 면이 더 가까울 때만 칠합니다. 이 비교를 깊이 테스트라고 부릅니다. 이 덕분에 면을 어떤 순서로 그려도 가까운 면이 남습니다.

픽셀마다 담는 깊이 값은 비트 수가 정해져 있습니다. 흔히 쓰는 24비트면 서로 다른 깊이 값을 약 1,680만 가지 담을 수 있습니다. 이 1,680만 개가 줄자의 눈금입니다.

비트 수를 정하고 나면 눈금 개수는 바뀌지 않습니다. 그때 깊이 정밀도를 가르는 것은 이 눈금을 거리 위에 어떻게 나눠 놓느냐입니다.

거리에서 깊이 값으로 가는 변환

그릴 범위에는 앞끝과 뒤끝이 있습니다. 앞끝을 근평면, 뒤끝을 원평면이라고 부릅니다. 이 둘 사이에 있는 것만 그립니다.

멀리 있는 것이 작게 보이도록 좌표를 바꾸는 일이 원근 투영입니다. 곧은 철길이 멀어질수록 좁아 보이는 것과 같은 효과를 냅니다.

원근 투영은 먼저 점의 좌표를 바꿉니다. 그다음 화면 위치를 그 점까지의 거리로 나눕니다. 먼 점일수록 큰 수로 나누니 화면 가운데로 모여 작게 그려집니다. 이 나눗셈을 원근 나눗셈이라고 부릅니다.

깊이 값도 이 나눗셈을 거칩니다. 그래서 깊이 값은 거리를 따라 곧게 늘지 않습니다. 거리의 역수를 따라 움직입니다. 근평면에서 0, 원평면에서 1 이 되도록 맞춘 뒤 눈금에 맞춰 반올림합니다.

정밀도가 새는 단계는 둘입니다. 원근 나눗셈에서 눈금이 가까운 쪽으로 몰립니다. 반올림에서 비트 수만큼의 눈금 안에 값이 갇힙니다.

역수 꼴을 쓰는 데는 까닭이 있습니다. 삼각형은 꼭짓점 셋에서만 깊이 값을 계산합니다. 그 사이 픽셀은 꼭짓점 값 사이를 일정한 비율로 이어서 채웁니다. 이렇게 곧게 이어 채우는 것을 선형 보간이라고 부릅니다.

화면에 비스듬히 누운 바닥을 떠올려 보겠습니다. 바닥의 픽셀을 아래에서 위로 하나씩 올라가며 보면 거리의 역수는 매번 같은 만큼씩 줄어듭니다. 거리는 그렇지 않습니다. 위로 올라가 멀어질수록 픽셀 하나 사이의 거리 차이가 커집니다.

그래서 역수 꼴로 담아 두면 단순한 선형 보간만으로 사이 픽셀의 깊이 값이 맞게 나옵니다. 정밀도가 고르지 않은 것은 이 편리함의 대가입니다.

가까운 쪽에 몰리는 눈금

근평면이 거리 1, 원평면이 거리 100 에 있는 장면으로 몰림을 재 보겠습니다. 아래 표는 깊이 값의 구간마다 거리가 얼마만큼 담기는지를 보입니다. 마지막 열은 24비트 깊이 버퍼에서 그 구간이 가져가는 눈금 수입니다.

깊이 값 구간 담기는 거리 거리의 폭 눈금 수
0 ~ 0.505 1 ~ 2 1 약 850만
0.505 ~ 0.909 2 ~ 10 8 약 680만
0.909 ~ 1 10 ~ 100 90 약 150만

표의 첫 줄과 마지막 줄을 견줘 보면 몰림이 보입니다. 거리 1 짜리 구간이 눈금의 절반을 가져갑니다. 거리 90 짜리 구간은 열에 하나도 못 받습니다.

눈금이 성긴 곳에서는 조금 떨어진 두 면이 같은 값으로 반올림됩니다. 그러면 깊이 테스트가 둘의 앞뒤를 못 가립니다. 픽셀마다 반올림이 조금씩 다르게 떨어져서 어느 픽셀은 이쪽이, 어느 픽셀은 저쪽이 이깁니다.

화면에는 두 면의 색이 얼룩덜룩 섞인 무늬가 생깁니다. 카메라가 움직이면 무늬도 꿈틀댑니다. 이 현상을 Z 파이팅이라고 부릅니다. 깊이 정밀도가 모자랄 때 눈에 보이는 결과가 이것입니다.

거리마다 눈금 한 칸의 폭

눈금 한 칸이 거리로 얼마인지는 간단한 식으로 어림할 수 있습니다. 원평면이 근평면보다 훨씬 멀면 한 칸의 폭은 거리의 제곱에 비례합니다. 근평면 거리에는 반비례합니다.

여기서는 장면을 바꿉니다. 근평면을 카메라 코앞 0.1 미터에 두고 1 킬로미터 밖까지 보이는 장면입니다. 아래 코드는 24비트 깊이 버퍼에서 눈금 한 칸의 폭을 계산합니다. z 는 카메라에서의 거리, n 은 근평면의 거리입니다. 단위는 미터로 읽습니다.

JavaScript
const step = 1 / 2 ** 24; // 눈금 한 칸
const gap = (z, n) => z * z * step / n;

gap(1, 0.1);     // 약 0.0000006
gap(100, 0.1);   // 약 0.006
gap(1000, 0.1);  // 약 0.6
gap(1000, 1);    // 약 0.06

근평면이 0.1 미터일 때 코앞 1 미터에서는 1 마이크로미터도 안 되는 차이를 가립니다. 1 킬로미터 밖에서는 한 칸이 60 센티미터로 벌어집니다. 그만큼 떨어진 두 면은 같은 값이 됩니다.

마지막 줄은 근평면만 1 미터로 민 것입니다. 1 킬로미터 밖의 칸이 6 센티미터로 좁아졌습니다. 근평면을 열 배 멀리 두면 먼 곳의 칸이 열 배 좁아집니다.

원평면은 이 식에 거의 안 나옵니다. 원평면이 근평면보다 훨씬 먼 동안에는 원평면을 더 밀거나 당겨도 칸의 폭이 거의 그대로입니다. 그래서 정밀도를 되찾을 때 먼저 손대는 값은 근평면입니다.

수를 담는 형식

깊이 값을 담는 형식은 크게 둘입니다. 하나는 0 과 1 사이를 같은 간격으로 자른 정수 눈금입니다. 16비트나 24비트 깊이 버퍼가 이 방식입니다.

다른 하나는 32비트 부동소수점입니다. 부동소수점은 수를 유효숫자와 지수로 나눠 담는 형식입니다. 0 에 가까운 수는 아주 잘게 담고 큰 수는 성기게 담습니다.

근평면 쪽 깊이 값을 0 으로 두는 보통 배치에서는 부동소수점의 촘촘한 쪽이 근평면 쪽에 겹칩니다. 이미 촘촘한 곳이 더 촘촘해질 뿐입니다. 먼 곳은 정수 눈금과 별 차이가 없습니다.

깊이 값을 뒤집어 근평면에 1, 원평면에 0 을 담으면 사정이 바뀝니다. 원근 나눗셈의 몰림은 근평면 쪽에 있습니다. 부동소수점의 촘촘함은 원평면 쪽으로 옵니다. 두 쏠림이 서로를 메워서 거리 전체에 걸쳐 정밀도가 고르게 됩니다. 이 배치를 리버스 Z라고 부릅니다.

뒤집어 담으면 가까운 면이 더 큰 값을 가집니다. 그래서 깊이 테스트도 값이 더 클 때 칠하도록 비교 방향을 함께 뒤집어야 합니다.

정밀도를 되찾는 방법

Z 파이팅이 보이면 흔히 떠올리는 방법이 다섯 가지 있습니다. 아래 표는 각 방법이 무엇을 바꾸고 무엇을 내주는지를 보입니다.

방법 바뀌는 것 내주는 것
근평면을 뒤로 민다 먼 곳의 눈금이 근평면 거리에 비례해 좁아진다 근평면보다 가까운 물체가 잘린다
원평면을 당긴다 거의 안 바뀐다 먼 풍경이 잘린다
비트 수를 늘린다 눈금 개수가 늘어난다 픽셀마다 메모리를 더 쓴다
리버스 Z 로 담는다 먼 곳까지 고르게 촘촘해진다 부동소수점 깊이 버퍼와 뒤집힌 비교 방향이 함께 필요하다
로그 깊이 버퍼를 쓴다 거리의 로그로 담아 몰림을 편다 픽셀마다 깊이 값을 직접 계산해 넣는 코드가 붙는다

첫 줄이 가장 부담이 적어서 대개 이것부터 합니다. 코앞의 물체와 아주 먼 풍경을 한 화면에 함께 그려야 하는 장면이 있습니다. 비행 장면이나 행성 규모의 장면이 그렇습니다. 이런 장면은 근평면만으로 버티기 어려워 표의 마지막 두 줄(리버스 Z · 로그 깊이 버퍼)로 갑니다.

직교 투영에서는 이 몰림이 없습니다. 멀어도 작아지지 않게 그리는 투영이라 원근 나눗셈이 없습니다. 깊이 값이 거리를 따라 곧게 늘어서 눈금이 거리 전체에 고르게 놓입니다.

관련 항목

깊이 정밀도가 속하는 상위 분류

그래픽스 · 3차원 그래픽스 · 렌더링 · 실시간 렌더링 · 렌더 파이프라인 · 래스터화

깊이 정밀도를 좌우하는 변환과 설정값

원근 투영 · 투영 행렬 · 원근 나눗셈 · 근평면 · 원평면 · 뷰 절두체 · 정규화 장치 좌표 · 보간 · 선형 보간

깊이 값을 담고 비교하는 버퍼와 검사

깊이 버퍼 · 깊이 테스트 · 깊이 경계 테스트 · 프레임버퍼 · 스텐실 버퍼 · 프래그먼트 · 픽셀

깊이 정밀도가 모자라 생기는 오류

Z 파이팅 · 깊이 비교 오류 · 섀도 애크니 · 피터 패닝

정밀도를 되찾거나 오류를 비껴가는 기법

리버스 Z · 로그 깊이 버퍼 · 무한 원평면 · 뎁스 바이어스 · 폴리곤 오프셋 · 깊이 클램프

깊이 값을 담는 수 형식

부동소수점 · 고정소수점 · IEEE 754 · 반정밀도 부동소수점 · 정규화 정수

다른 이름: depth precision · Z 정밀도 · 깊이 버퍼 정밀도