컬링
고친 사람 github-actions[bot]
컬링은 화면에 안 나타날 것을 그리기 전에 골라 버리는 일입니다. 카메라 뒤에 선 건물, 앞의 벽에 완전히 가려진 적, 우리 쪽을 등진 삼각형이 그 대상입니다. 버린 만큼 뒤에 남은 단계가 할 일이 줄어듭니다.
쉽고 빠른 이해
컬링은 그릴 목록에서 안 보일 것을 미리 빼는 일입니다. 벽 뒤에 선 적 캐릭터는 화면에 한 점도 나오지 않으므로 목록에서 빼도 화면은 같습니다.
빼지 않으면 그 캐릭터의 삼각형을 전부 계산한 뒤에야 가려졌다는 것이 드러납니다. 계산은 다 하고 결과는 내버리는 셈이라 한 화면을 그리는 시간만 길어집니다.
어떻게 도는가:
- 카메라가 보는 범위 밖에 있는 물체를 뺍니다
- 앞의 물체에 완전히 가려진 물체를 뺍니다
- 남은 물체에서 카메라를 등진 삼각형을 뺍니다
대가가 있습니다. 「보이나」를 따지는 계산 자체에 시간이 듭니다. 판정이 어긋나 보이는 것을 빼면 화면에 구멍이 납니다. 그래서 판정은 헐겁게 잡고, 애매하면 남기는 쪽으로 기웁니다.
상세
창가에 서서 길 가는 사람을 세어 봅니다. 창틀에 들어온 사람만 세면 됩니다 — 앞 건물에 가려진 사람은 애초에 셈에 없습니다. 한 발짝 옆으로 비켜서면 셀 사람이 그 자리에서 다시 정해집니다.
컬링은 「안 보이는 것은 계산하지 않는다」는 한 가지 생각을 여러 규모에 되풀이해 적용한 것입니다. 고르는 일은 화면 한 장을 그릴 때마다 처음부터 다시 합니다. 카메라가 한 걸음 움직이면 방금 버린 물체가 도로 보이기 때문입니다.
물체 단위와 삼각형 단위
컬링이라는 이름은 두 규모에 같이 쓰입니다. 하나는 물체 하나를 통째로 그릴지 말지 정합니다. 다른 하나는 삼각형 하나를 칠할지 말지 정합니다.
정하는 쪽도 다릅니다. 물체 단위는 장면을 관리하는 코드가 CPU(Central Processing Unit, 중앙 처리 장치)에서 미리 추려 냅니다. 삼각형 단위는 GPU(Graphics Processing Unit, 그래픽 처리 장치)가 그리는 도중에 정해진 규칙대로 걸러 냅니다.
두 규모는 순서대로 겹쳐 걸립니다. 앞의 체가 굵고 뒤로 갈수록 촘촘해집니다.
flowchart TD
A["장면에 놓인 물체 전부"]
subgraph SC["CPU · 물체 단위"]
B["시야 밖 물체를 뺀다"]
C["가려진 물체를 뺀다"]
end
subgraph SG["GPU · 삼각형 단위"]
D["남은 물체의 삼각형"]
E["등진 삼각형을 뺀다"]
end
A --> B
B --> C
C --> D
D --> E
E --> F["래스터화로 넘어간다"]
그림의 마지막 칸에 있는 래스터화는 도형이 화면의 어느 픽셀 칸을 덮는지 찾는 절차입니다. 컬링이 하는 일은 이 절차에 들어가는 개수를 줄이는 것입니다.
세 번의 거르기를 그림 순서대로 하나씩 봅니다.
시야에서 벗어난 물체
카메라가 한 번에 담는 공간은 정해져 있습니다. 화면 네 변에서 뻗어 나간 네 면이 그 공간의 옆을 두릅니다.
앞뒤는 두 면이 막습니다. 너무 가까운 것을 자르는 면을 근평면이라고 합니다. 너무 먼 것을 자르는 면은 원평면입니다.
이 여섯 면으로 둘러싸인 공간이 뷰 절두체입니다. 줄여서 절두체라고 부릅니다.
flowchart TD
subgraph FR["뷰 절두체"]
NP["근평면"]
FP["원평면"]
SP["화면 네 변에서 뻗은 옆면 넷"]
IN["안에 든 물체"]
end
O1["카메라 뒤 물체"] --> CUT["뺀다"]
O2["화면 왼쪽으로 벗어난 물체"] --> CUT
O3["원평면 너머 물체"] --> CUT
IN --> KEEP["남긴다"]
절두체 바깥에 통째로 놓인 물체는 계산해도 화면에 안 나옵니다. 이것을 빼는 것이 절두체 컬링입니다. 뒤쪽에 있는 물체뿐 아니라 화면 왼쪽으로 한참 벗어난 물체, 너무 멀어 원평면 너머로 나간 물체도 같이 빠집니다.
물체의 삼각형을 하나씩 따져 보면 판정에만 시간이 다 듭니다. 그래서 물체를 통째로 감싸는 단순한 도형을 하나 씌워 두고 그것만 따집니다. 이 도형을 바운딩 볼륨이라고 합니다. 구 하나나 직육면체 하나면 여섯 면과의 안팎 판정이 몇 번의 곱셈으로 끝납니다.
바운딩 볼륨은 물체보다 크므로 판정이 헐거워집니다. 물체는 밖에 있는데 감싼 구가 절두체에 살짝 걸치면 남게 됩니다. 이 헐거움은 손해가 아닙니다 — 남는 쪽으로만 틀리기 때문입니다.
물체가 수만 개면 바운딩 볼륨 판정조차 부담이 됩니다. 그래서 장면을 미리 격자나 나무 모양으로 갈라 두고, 가지째 절두체 밖이면 그 아래를 통째로 건너뜁니다. 이렇게 갈라 두는 구조를 공간 분할이라고 부릅니다.
flowchart TD
R["장면"] --> G1["가지 1 · 절두체 밖"]
R --> G2["가지 2 · 절두체에 걸침"]
R --> G3["가지 3 · 절두체 안"]
G1 --> SKIP["이 밑 전부 건너뜀"]
G2 --> C2["아래 가지를 하나씩 판정"]
G3 --> C3["아래 가지를 하나씩 판정"]
가지 하나를 접을 때마다 그 아래에 매달린 물체 전부가 판정에서 빠집니다. 판정 횟수가 물체 개수가 아니라 가지 개수를 따라가는 것이 이 구조의 이득입니다.
앞의 물체에 가려진 물체
절두체 안에 들어와 있어도 앞의 벽이나 건물에 완전히 가려지면 화면에 안 나옵니다. 이런 물체를 빼는 것이 오클루전 컬링입니다. 실내나 도시처럼 막힌 곳이 많은 장면에서 빠지는 양이 가장 큽니다.
셋 중에 판정이 가장 까다롭습니다. 절두체 안인지는 물체 하나만 보면 되지만, 가려졌는지는 다른 물체와의 관계라서 그 물체만 봐서는 알 수 없습니다. 카메라가 조금만 움직여도 가림 관계가 바뀝니다.
푸는 방식은 크게 둘입니다. 하나는 장면을 만들 때 미리 계산해 두는 것입니다. 카메라가 이 구역에 있으면 어느 물체가 보일 수 있는지를 목록으로 만들어 두고, 그릴 때는 목록만 읽습니다. 벽으로 나뉜 실내처럼 구조가 바뀌지 않는 장면에 맞습니다.
다른 하나는 그리면서 정하는 것입니다. 큰 물체 몇 개를 먼저 그려 앞뒤 정보를 얻은 다음, 나머지 물체가 그 뒤에 숨는지 물어봅니다. 물체가 돌아다니는 장면에도 쓸 수 있는 대신 판정 비용을 매 화면마다 치릅니다.
sequenceDiagram
participant S as 장면 코드
participant G as GPU
S->>G: 큰 물체 몇 개를 먼저 그린다
G-->>S: 앞뒤 정보가 생겼다
S->>G: 이 물체가 그 뒤에 숨나
G-->>S: 숨는다 · 안 숨는다
Note over S,G: 숨으면 그릴 목록에서 뺀다
물어보고 답을 받는 왕복이 화면 한 장마다 되풀이됩니다. 미리 계산해 두는 방식에는 이 왕복이 없습니다.
카메라를 등진 삼각형
속이 비치지 않는 닫힌 입체를 생각해 봅니다. 주사위든 사람 모형이든, 카메라를 등진 면은 같은 입체의 앞면에 반드시 가려집니다. 그래서 등진 면은 계산해 봐야 화면에 한 점도 남지 않습니다.
물체는 삼각형 여럿을 이어 붙여 만듭니다. 삼각형의 꼭짓점 셋을 정점이라고 합니다. 세 정점에는 첫째·둘째·셋째 순서가 매겨져 있습니다. 그 순서대로 이으면 삼각형이 도는 방향이 나옵니다.
등졌는지는 세 정점이 화면에서 도는 방향으로 판정합니다. 정점이 도는 이 방향이 감김 순서입니다.
같은 삼각형이라도 반대편에서 보면 도는 방향이 뒤집힙니다. 그래서 방향 하나로 앞뒤가 갈립니다.
방향은 부호 하나로 나옵니다. 세 정점을 화면 좌표로 놓고 두 변을 곱해 뺀 값 s 의 부호가
그 방향입니다.
flowchart TD
subgraph FRONT["카메라를 향한 앞면 · 반시계 · s > 0"]
A1["A"] --> B1["B"] --> C1["C"] --> A1
end
subgraph BACK["카메라를 등진 뒷면 · 시계 · s < 0"]
A2["A"] --> C2["C"] --> B2["B"] --> A2
end
같은 삼각형인데 정점을 잇는 차례만 뒤집혔습니다.
아래에서 ax 와 ay 는 정점 A 의 화면 좌표입니다.
// 세 정점이 도는 방향을 부호로 얻는다
s = (bx-ax)*(cy-ay) - (by-ay)*(cx-ax);
s > 0 // 반시계 — 카메라를 향한 앞면
s < 0 // 시계 — 카메라를 등진 뒷면
어느 쪽을 앞면으로 볼지는 그리는 사람이 고릅니다. 반시계를 앞면으로 두는 관례가 흔하지만 설정으로 뒤집을 수 있습니다. 버릴 쪽도 앞면과 뒷면 중에 고릅니다.
이 거르기를 뒷면 컬링이라고 부릅니다. 닫힌 입체를 이루는 삼각형은 대략 절반이 카메라를 등지므로, 끄고 켜는 것만으로 그릴 삼각형이 크게 줄어듭니다.
다만 닫힌 입체가 아니면 이 가정이 깨집니다. 나뭇잎을 그린 얇은 판이나 종잇장처럼 양면이 다 보이는 물체는 뒷면을 버리면 한쪽에서 볼 때 사라집니다. 그런 물체에는 뒷면 컬링을 끕니다.
컬링과 깊이 테스트의 갈림
가린 것을 걸러 낸다는 점에서는 깊이 테스트도 같은 일을 합니다. 갈리는 것은 언제 거르느냐 입니다.
깊이 테스트는 래스터화가 끝난 뒤에 묻습니다. 앞서 그린 것의 깊이를 화면 칸마다 적어 둡니다. 새 후보가 거기 적힌 깊이보다 뒤면 버립니다.
래스터화는 도형이 덮는 픽셀 칸마다 「이 픽셀에 칠할 후보」를 하나씩 만듭니다. 이 후보 하나가 프래그먼트입니다. 깊이 테스트가 버리는 후보도 프래그먼트까지는 이미 만들어진 뒤입니다.
같은 칸을 여러 물체가 겹쳐 칠하는 것을 오버드로라고 합니다. 깊이 테스트는 늦게 걸러서 오버드로를 줄이지 못합니다. 컬링은 그 계산이 시작되기 전에 후보를 통째로 빼서 오버드로 자체를 줄입니다.
flowchart TD
R["래스터화"] --> P["프래그먼트 후보가 만들어진다"]
P --> T["깊이 테스트"]
T --> BK["뒤면 버린다 · 여기까지 계산은 이미 했다"]
T --> FT["앞이면 칠한다"]
BK --> OD["오버드로"]
「뒤면 버린다」로 가는 갈래가 오버드로입니다. 화면에는 한 점도 안 남지만 래스터화와 프래그먼트 만들기는 이미 끝난 뒤입니다.
둘은 서로를 대신하지 않습니다. 컬링은 헐거운 대신 싸고, 깊이 테스트는 정밀한 대신 늦습니다. 실제 그리기 흐름에는 둘 다 들어갑니다.
컬링이 치르는 대가
컬링은 공짜가 아닙니다. 「보이나」를 묻는 계산에도 시간이 듭니다. 장면에 물체가 몇 개 없거나 가리는 벽이 거의 없으면, 걸러 내서 아끼는 양보다 판정에 드는 양이 커질 수 있습니다. 특히 오클루전 컬링은 넓은 벌판처럼 트인 장면에서 손해로 돌아섭니다.
판정이 어긋났을 때의 결과는 방향에 따라 전혀 다릅니다.
| 판정 | 무슨 일이 일어나나 |
|---|---|
| 안 보이는데 남겼다 | 쓸데없이 그립니다. 화면은 맞게 나옵니다 |
| 보이는데 버렸다 | 물체가 사라지거나 뚫려 보입니다. 화면이 깨집니다 |
한쪽으로만 틀리는 것이 안전하므로 컬링의 판정은 모두 헐거운 쪽으로 잡습니다. 확실히 안 보일 때만 버리고, 애매하면 남깁니다. 바운딩 볼륨이 물체보다 크게 잡히는 것도 같은 까닭입니다.
한계도 있습니다. 컬링은 그릴 개수를 줄일 뿐 하나를 그리는 비용은 안 줄입니다. 화면 가득 찬 물체 하나가 무거우면 컬링으로는 손댈 것이 없습니다. 그쪽은 멀리 있는 물체를 단순한 모형으로 바꿔 그리는 레벨 오브 디테일이 맡습니다.
관련 항목
컬링의 하위 종류
백페이스 컬링 · 절두체 컬링 · 오클루전 컬링 · 포털 컬링 · 디테일 컬링 · 작은 삼각형 컬링 · 클러스터 컬링 · 후면 제거
컬링 판정에 쓰는 기하 요소
감김 순서 · 법선 · 뷰 절두체 · 근평면 · 원평면 · 바운딩 볼륨 · 바운딩 박스 · 바운딩 스피어 · 정점
컬링 대상을 미리 갈라 두는 공간 분할 구조
공간 분할 · 옥트리 · 쿼드트리 · BSP 트리 · BVH · 씬 그래프 · 잠재 가시 집합 · 포털
컬링이 놓이는 그리기 단계
그래픽스 파이프라인 · 정점 셰이더 · 프리미티브 조립 · 클리핑 · 래스터화 · 프래그먼트 · 드로 콜
컬링과 같은 목적으로 그릴 양을 줄이는 기법
레벨 오브 디테일 · 임포스터 · 인스턴싱 · 배칭 · 얼리 Z · Z 프리패스 · 디퍼드 셰이딩
컬링이 줄이려는 낭비와 그 지표
오버드로 · 필레이트 · 프레임 시간 · 깊이 테스트 · GPU 바운드 · CPU 바운드
컬링을 설정으로 노출하는 그래픽 API
OpenGL · Vulkan · Direct3D · Metal · WebGL
컬링이 쓰이는 상위 분야
다른 이름: Culling