스텐실 테스트
고친 사람 github-actions[bot]
스텐실 테스트는 화면의 칸마다 적어 둔 표식을 보고 그릴 칸을 고르는 검사입니다. 그리려는 물체의 모양과 상관없이 먼저 정해 둔 구역 안으로만 색이 들어갑니다. 거울에 비친 상이나 바닥에 드리운 그림자처럼 구역이 먼저 정해지는 장면을 이렇게 만듭니다.
쉽고 빠른 이해
스텐실 테스트는 화면 칸마다 작은 수를 하나씩 적어 둡니다. 그 수가 조건에 맞는 칸에만 색을 쓰게 하는 검사입니다. 벽에 걸린 거울을 그린다면 거울 유리가 덮은 칸에만 표식을 심어 둡니다. 거울 속 장면은 그 칸 밖으로 한 점도 못 나갑니다.
이게 없으면 구역을 모양으로 잘라 내야 합니다. 그릴 물체마다 거울 테두리와 겹치는 부분을 계산해 잘라 내야 합니다. 테두리가 복잡해질수록 그 계산이 불어납니다.
어떻게 도나:
- 화면 칸마다 표식을 담는 메모리를 하나 더 둡니다
- 구역이 될 모양을 먼저 그려 그 칸들에 표식을 심습니다. 색은 쓰지 않습니다
- 이어서 장면을 그립니다. 이때 표식이 조건에 맞는 칸에만 색을 쓰게 합니다
대가는 메모리와 그리기 횟수입니다. 화면 칸 수만큼 표식을 들고 있어야 합니다. 구역을 심는 만큼 장면을 여러 번 그리게 됩니다. 표식은 통과와 탈락만 가르므로 구역의 가장자리가 톱니처럼 각집니다.
구역이 사각형이면 이 검사까지 안 갑니다. 화면의 사각형 범위를 잘라 내는 더 싼 수단이 따로 있습니다.
상세
칸에 적어 두는 구역
지하 주차장 입구에는 층마다 남은 자리 수가 숫자로 떠 있습니다. 차가 드나들 때마다 그 수는 1씩 줄었다 늘었다 합니다. 운전자는 층 안을 들여다볼 것 없이 그 숫자만 보고 어느 층으로 갈지 정합니다.
화면에 그릴 때도 같은 일을 합니다. 칸마다 적힌 수가 그 칸에 색을 쓸지를 가릅니다. 구역을 먼저 정해 두면 무엇을 어떻게 그리든 그 구역 밖으로는 색이 안 나갑니다.
구역을 이렇게 따로 들고 있지 않으면 물체의 모양을 직접 잘라야 합니다. 거울 테두리를 따라 장면을 오려 내려면 그릴 삼각형마다 테두리와 겹치는 부분을 구해 쪼개야 합니다. 테두리가 곡선이거나 구멍이 뚫려 있으면 쪼갤 조각이 급격히 늘어납니다.
칸마다 표식을 담아 두는 메모리
화면 한 장을 칸마다 색으로 적어 둔 메모리를 색 버퍼라고 부릅니다. 스텐실 테스트는 이것과 같은 크기의 메모리를 하나 더 씁니다. 이 메모리는 칸마다 색 대신 작은 정수를 적습니다. 이름은 스텐실 버퍼입니다.
이 문서에서 표식이라 부르는 것이 그 정수입니다. 한 칸에 하나씩 적힙니다. 그 칸에 무엇을 그릴지는 이 수가 가릅니다.
여기 적히는 수에는 정해진 뜻이 없습니다. 거리를 담는 깊이 버퍼와 갈리는 대목입니다. 무엇을 1로 두고 무엇을 2로 둘지는 그리는 쪽이 정합니다. 검사도 그 약속대로 설정합니다.
한 장을 그리기 시작할 때 이 메모리는 대개 0으로 채웁니다. 아직 아무 구역도 안 정한 상태를 뜻하는 값이 하나 필요하기 때문입니다.
스텐실 버퍼는 흔히 깊이 버퍼와 한 덩이로 묶여 한 칸을 나눠 씁니다. 두 값이 같은 칸에서 같은 때에 읽히고 쓰여서, 따로 두면 메모리를 두 번 다녀와야 하기 때문입니다.
block-beta
columns 1
a["색 버퍼 · 그 칸의 색"]
block:묶음
columns 1
b["깊이 버퍼 · 그 칸의 거리"]
c["스텐실 버퍼 · 그 칸의 표식"]
end
화면 한 칸에 이렇게 세 값이 나란히 놓입니다. 아래 둘이 한 덩이로 묶인 쪽입니다.
표식을 견주는 규칙
검사를 받는 것은 물체도 삼각형도 아니고, 칸 하나를 차지하겠다고 나선 후보 하나입니다. 이 후보를 프래그먼트라고 부릅니다. 한 칸에 후보가 여럿 올 수 있습니다. 검사는 후보 하나마다 한 번씩 돕니다.
견주는 값은 둘입니다. 칸에 적힌 표식과, 그리는 쪽이 미리 정해 둔 참조값입니다. 참조값은 이번 그리기가 어느 구역을 뜻하는지 가리키는 수입니다. 거울 구역을 1로 심어 두었다면 참조값에 1을 두고 표식이 1인 칸만 통과시킵니다.
여기에 마스크를 하나 더 둡니다. 마스크는 두 수의 어느 비트를 볼지 고르는 값입니다. 마스크에서 꺼 둔 비트는 표식과 참조값 양쪽 모두에서 가려져 견주기에 안 들어갑니다.
flowchart TD
A["칸의 표식"] --> M1["마스크가 고른 비트만 남는다"]
B["참조값"] --> M2["마스크가 고른 비트만 남는다"]
M1 --> C{"정해 둔 규칙으로 견준다"}
M2 --> C
C -->|맞다| D["통과"]
C -->|아니다| E["탈락"]
견주기 한 번은 이렇게 돕니다. 표식과 참조값이 나란히 마스크를 지납니다. 남은 비트끼리만 맞대 봅니다.
마스크가 있는 까닭은 한 칸의 표식을 여러 구역이 나눠 쓰기 때문입니다. 아래쪽 비트로는 거울 구역을 표시하고 위쪽 비트로는 다른 구역을 표시해 두면, 검사할 때 자기 몫의 비트만 열어 봅니다.
block-beta columns 2 a["위쪽 비트 · 다른 구역"] b["아래쪽 비트 · 거울 구역"]
표식 한 칸을 이렇게 나눠 두면 구역 둘을 한 메모리에 담을 수 있습니다.
견주는 방식 자체도 골라 둡니다. 표식은 수라서 같고 다름뿐 아니라 크기도 견줄 수 있습니다.
| 통과시키는 규칙 | 언제 고르나 |
|---|---|
| 표식이 참조값과 같을 때 | 심어 둔 구역 안에만 그릴 때 |
| 표식이 참조값과 다를 때 | 구역을 뺀 나머지에만 그릴 때 |
| 표식이 참조값보다 클 때 | 표식이 어느 수를 넘은 칸만 고를 때 |
| 언제나 통과 | 색은 안 건드리고 표식만 심을 때 |
| 언제나 탈락 | 이번 그리기로는 화면을 하나도 안 바꿀 때 |
「언제나 통과」와 「언제나 탈락」이 검사답지 않게 보일 수 있습니다. 검사에 떨어진 후보도 그 칸의 표식은 바꿀 수 있습니다. 색을 한 점도 안 쓰고 구역만 심으려 할 때 이런 규칙을 고릅니다.
통과와 탈락에 따른 표식 고치기
후보 하나의 운명은 두 검사가 함께 정합니다. 스텐실 테스트가 먼저 돕니다. 통과한 후보만 깊이 테스트로 넘어갑니다. 깊이 테스트는 칸에 적힌 거리와 후보의 거리를 견주어 앞에 있는 것만 남기는 검사입니다.
그래서 결과가 세 갈래로 갈립니다. 스텐실에서 떨어진 후보, 스텐실은 통과했는데 깊이에서 떨어진 후보, 둘 다 통과한 후보입니다. 갈래마다 표식을 어떻게 고칠지를 따로 정해 둡니다.
flowchart TD
A["후보 하나 도착"] --> B["그 칸의 표식을 읽는다"]
B --> C{"표식 견주기를 통과했나"}
C -->|아니다| D["색을 안 쓴다 · 스텐실에서 떨어졌을 때의 규칙으로 표식을 고친다"]
C -->|그렇다| E{"깊이 테스트를 통과했나"}
E -->|아니다| F["색을 안 쓴다 · 깊이에서 떨어졌을 때의 규칙으로 표식을 고친다"]
E -->|그렇다| G["색을 쓴다 · 둘 다 통과했을 때의 규칙으로 표식을 고친다"]
탈락한 후보가 표식을 고칠 수 있다는 것이 이 검사의 핵심입니다. 화면에는 아무 색도 안 남습니다. 대신 그 칸에 무슨 일이 있었는지가 기록으로 남습니다.
표식을 고치는 방법은 몇 가지로 정해져 있습니다.
| 고치는 방법 | 무엇을 하나 |
|---|---|
| 그대로 둔다 | 표식을 안 건드린다 |
| 참조값으로 바꾼다 | 구역을 심을 때 쓴다 |
| 0으로 지운다 | 구역을 걷어낼 때 쓴다 |
| 1 늘린다 | 그 칸을 덮은 횟수를 센다 |
| 1 줄인다 | 세어 둔 횟수를 되돌린다 |
늘리기와 줄이기가 있어서 스텐실 버퍼는 구역 표시뿐 아니라 셈판으로도 쓰입니다. 한 물체의 앞면을 그릴 때 표식을 1 늘립니다. 뒷면을 그릴 때 1 줄입니다. 남은 수가 그 칸이 물체 속에 들어 있는지를 알려 줍니다.
flowchart TD
A["표식이 0인 칸"] --> B["앞면이 와서 1 늘어난다 · 표식 1"]
B --> C{"그 칸에서 뒷면도 세어지나"}
C -->|세어진다| D["1 줄어든다 · 표식 0 · 물체 밖"]
C -->|안 세어진다| E["표식 1로 남는다 · 물체 안"]
셈이 0으로 돌아온 칸은 물체 밖입니다. 0이 아닌 채로 남은 칸은 물체 속입니다.
이 셈을 그림자에 쓴 것이 그림자 볼륨입니다. 빛을 막은 물체 뒤로 그늘이 뻗는 구역을 기둥처럼 세워 그립니다. 그 그림자 기둥의 앞뒤 면을 이렇게 세면 그림자가 덮은 칸만 남습니다.
구역을 만드는 두 번의 그리기
스텐실 테스트는 한 번 그려서 끝나는 검사가 아닙니다. 구역을 심는 그리기와 그 구역 안에 장면을 넣는 그리기가 따로 있습니다. 같은 화면에 두 번 그립니다. 첫 번째 그리기는 색을 한 점도 안 남깁니다.
flowchart TD
subgraph P1["1차 그리기 · 구역 심기"]
A["색 쓰기를 꺼 둔다"] --> B["거울 유리 모양을 그린다"]
B --> C["덮인 칸의 표식을 1로 바꾼다"]
end
subgraph P2["2차 그리기 · 장면 채우기"]
D["거울 속 장면을 그린다"] --> E["표식이 1인 칸만 통과시킨다"]
E --> F["통과한 후보의 색을 쓴다"]
end
P1 --> P2
이렇게 나누면 두 그리기가 서로를 모르고도 맞물립니다. 거울 유리의 모양이 아무리 복잡해도 2차 그리기는 칸의 표식만 보면 됩니다. 거울 속 장면이 무엇이든 1차 그리기는 달라지지 않습니다.
구역을 정하는 방식은 쓰임마다 다릅니다.
| 쓰는 곳 | 무엇으로 구역을 정하나 |
|---|---|
| 거울과 포털 | 거울 유리나 문틀이 덮은 칸 |
| 화면 오려 내기 | 창이나 말풍선 같은 테두리 모양 |
| 그림자 볼륨 | 그림자 기둥의 앞뒤 면을 세어 남은 수 |
| 데칼 | 스티커를 붙일 벽면이 덮은 칸 |
| 윤곽선 그리기 | 물체가 덮은 칸을 뺀 나머지 |
윤곽선 그리기는 구역을 거꾸로 씁니다. 물체가 덮은 칸에 표식을 심습니다. 그다음 그 물체를 조금 키워 다시 그리되, 표식이 없는 칸만 통과시킵니다. 그러면 원래 모양 둘레에만 색이 남아 테두리가 됩니다.
이 검사로는 안 되는 것
표식 견주기의 답은 통과 아니면 탈락입니다. 절반만 통과시켜 가장자리를 부드럽게 만드는 일은 이 검사로 안 됩니다. 구역의 경계가 칸 단위로 각집니다. 비스듬한 테두리는 계단처럼 보입니다.
표식은 화면 칸에 매여 있습니다. 카메라가 움직여 화면을 다시 그리면 구역도 다시 심어야 합니다. 한 번 심어 두고 여러 장면에 걸쳐 쓰는 것이 아니라, 매 장면마다 1차 그리기를 다시 도는 것입니다.
구역이 사각형이면 이 검사까지 안 가도 됩니다. 화면의 사각형 범위 밖을 잘라 내는 시저 테스트가 따로 있습니다. 그쪽은 버퍼도 1차 그리기도 필요 없습니다. 스텐실 테스트는 사각형으로 표현이 안 되는 구역을 맡습니다.
관련 항목
스텐실 테스트가 놓이는 처리 단계
그래픽스 파이프라인 · 래스터화 · 프래그먼트 셰이더 · 프래그먼트별 테스트 · 출력 병합기 · 프래그먼트
스텐실 테스트와 나란히 도는 다른 검사
깊이 테스트 · 시저 테스트 · 알파 테스트 · 깊이 경계 테스트 · 컬링
스텐실 테스트가 읽고 쓰는 저장소
스텐실 버퍼 · 깊이 버퍼 · 색 버퍼 · 프레임버퍼 · 렌더 타깃 · 깊이 스텐실 형식
표식을 견주고 고치는 설정값
스텐실 참조값 · 스텐실 마스크 · 스텐실 쓰기 마스크 · 스텐실 연산 · 비교 함수
스텐실 테스트로 만드는 화면 효과
그림자 볼륨 · 포털 렌더링 · 거울 반사 · 데칼 · 윤곽선 그리기 · 클리핑 마스크
같은 구역 제한을 다른 방법으로 거는 수단
시저 사각형 · 클립 평면 · 프래그먼트 버리기 · 알파 마스킹 · 깊이 클램프
화면을 여러 번 그려 버퍼를 쌓는 기법
멀티패스 렌더링 · 디퍼드 셰이딩 · Z 프리패스 · 오버드로
스텐실 테스트를 제공하는 그래픽 API
OpenGL · Vulkan · Direct3D · Metal · WebGL · WebGPU
스텐실 테스트가 속하는 상위 분류
다른 이름: stencil test · 스텐실 검사 · 스텐실 마스킹