미스 셰이더
고친 사람 github-actions[bot]
미스 셰이더는 광선이 아무것에도 맞지 않았을 때 도는 프로그램입니다. 그래픽 카드가 광선을 쏘아 장면을 그릴 때 빗나간 광선의 뒷처리를 맡습니다. 대개 그 점에 하늘색 같은 배경을 칠합니다.
쉽고 빠른 이해
미스 셰이더는 빗나간 광선의 결과를 정해 주는 짧은 프로그램입니다. 화면의 한 점에서 쏜 광선이 하늘로 빠져나가면 이 프로그램이 돌아 그 점을 하늘색으로 칠하게 합니다.
이게 없으면 빗나간 광선은 아무 값도 없이 돌아옵니다. 맞은 물체가 없으니 물체 쪽 프로그램이 돌 수 없기 때문입니다. 「안 맞았을 때 할 일」을 따로 적어 두는 칸이 필요합니다.
어떻게 도나:
- 광선을 쏠 때 결과를 담을 작은 상자를 같이 실어 보냅니다
- 그래픽 카드가 광선 앞의 물체를 찾다가 하나도 못 찾습니다
- 미스 셰이더가 돌아 상자에 배경색을 적습니다. 상자는 광선을 쏜 쪽으로 돌아갑니다
대가는 크지 않습니다. 맞은 점이 없어 계산할 것이 적기 때문입니다. 다만 하늘이 넓게 보이는 장면에서는 빗나가는 광선이 많아 이 프로그램이 그만큼 자주 돕니다.
이 프로그램은 「맞았을 때」와 「빗나갔을 때」 할 일을 따로 묶어 그래픽 카드에 넘기는 방식에서 씁니다. 광선을 쏜 코드가 결과를 곧바로 받아 직접 가르는 방식에는 이 프로그램이 없습니다.
상세
이 절은 미스 셰이더가 언제 불리는지부터 봅니다. 그다음 무엇을 받고 무엇을 내놓는지, 광선의 종류마다 미스 셰이더를 어떻게 갈라 두는지를 따라갑니다. 예로는 하늘 아래 나무 바닥이 깔리고 전등 하나가 켜진 장면을 줄곧 씁니다.
웹 서버의 라우터를 떠올려 보면 됩니다. 요청 주소가 등록된 경로 하나와 맞으면 그 경로의 핸들러가 돕니다. 어느 경로와도 안 맞으면 미리 정해 둔 404 핸들러가 돌아 「찾을 수 없음」을 돌려줍니다. 미스 셰이더는 광선의 404 핸들러에 해당합니다.
광선과 셰이더
GPU(Graphics Processing Unit, 그래픽 처리 장치)는 같은 계산을 동시에 아주 많이 돌리는 칩입니다. 화면의 점 수백만 개의 색을 한꺼번에 정하려고 만들었습니다.
셰이더는 GPU 에서 도는 작은 프로그램입니다. 점 하나의 색을 셈하는 식의 계산을 셰이더로 적어 GPU 에 넘깁니다.
레이 트레이싱은 눈에서 화면의 점 하나를 지나는 직선을 쏘아 그 점의 색을 정하는 그리기 방식입니다. 이 직선을 광선이라고 부릅니다. 광선이 나무 바닥에 맞으면 그 점에 바닥이 칠해집니다.
광선을 쏘는 데 쓰는 셰이더들은 레이 트레이싱 파이프라인이라는 한 덩어리로 묶어 GPU 에 넘깁니다. 이 덩어리 안에서 셰이더는 하는 일에 따라 종류가 갈립니다. 광선을 쏠 때, 맞았을 때, 빗나갔을 때 저마다 다른 셰이더가 돕니다.
| 셰이더 | 언제 도나 |
|---|---|
| 광선 생성 셰이더 | 화면의 점마다 한 번. 광선을 만들어 쏜다 |
| 최근접 적중 셰이더 | 광선이 물체에 맞아 가장 가까운 적중이 정해졌을 때 |
| 미스 셰이더 | 광선이 아무것에도 안 맞았을 때 |
최근접 적중 셰이더는 물체마다 따로 둡니다. 물체마다 표면을 셈하는 법이 다르기 때문입니다. 나무 바닥은 결무늬를, 유리는 비쳐 오는 빛을 셈합니다. 어느 물체에 맞을지는 쏴 봐야 알므로, GPU 가 광선이 맞은 뒤에 그 물체의 셰이더를 골라 돌립니다.
미스 셰이더는 그 반대편을 맡습니다. 맞은 물체가 없을 때 돌릴 셰이더를 따로 정해 둔 것입니다.
빗나감이 정해지는 때
광선은 끝없이 뻗지 않습니다. 쏘는 쪽이 시작 거리와 끝 거리를 정해 줍니다. GPU 는 그 둘 사이에서만 물체를 찾습니다. 이 두 거리 사이를 광선의 구간이라고 부르겠습니다.
그래서 빗나감은 「장면에 아무것도 없다」가 아닙니다. 「구간 안에 맞은 물체가 없다」입니다. 끝 거리 너머에 산이 있어도 구간 밖이면 그 광선은 빗나간 것입니다.
광선이 물체와 겹쳤는데도 빗나감으로 끝나는 경우가 하나 더 있습니다. 겹쳤다고 언제나 적중은 아닙니다. 나뭇잎은 흔히 잎 모양만 남기고 나머지는 투명한 사각형 그림으로 붙입니다. 광선이 그 투명한 데를 지나면 사각형과는 겹쳤어도 잎에 맞은 것은 아닙니다.
이런 겹침을 가려내는 셰이더가 임의 적중 셰이더입니다. 광선이 물체와 겹칠 때마다 돌아 이 겹침을 적중으로 칠지 정합니다. 겹침을 전부 거절하면 남는 적중이 없으므로 미스 셰이더가 돕니다.
flowchart TD
A["광선 생성 셰이더 · 광선을 쏜다"] --> B["GPU 가 구간 안에서 물체를 찾는다"]
B --> C{"받아들인 적중이 남았나"}
C -->|"남았다"| D["최근접 적중 셰이더"]
C -->|"없다"| E["미스 셰이더"]
D --> F["광선을 쏜 셰이더로 돌아간다"]
E --> F
광선 하나에서 최근접 적중 셰이더와 미스 셰이더는 둘 중 하나만 돕니다. 어느 쪽이 돌든 끝나면 광선을 쏜 셰이더로 돌아갑니다. 이 갈림 덕분에 쏘는 쪽은 「맞았나」를 따로 묻지 않고 돌아온 결과만 읽으면 됩니다.
받는 것과 내놓는 것
광선을 쏠 때는 값을 담을 작은 상자를 같이 실어 보냅니다. 이 상자를 페이로드라고 부릅니다. 맞은 쪽이든 빗나간 쪽이든 결과를 이 상자에 적습니다. 광선을 쏜 셰이더는 돌아온 상자를 읽습니다.
미스 셰이더가 내놓는 것은 이 페이로드 하나입니다. 화면에 직접 색을 쓰지 않습니다. 상자에 배경색을 적어 두면 화면에 쓰는 일은 광선을 쏜 셰이더가 합니다.
미스 셰이더가 읽을 수 있는 것은 광선 자신입니다. 광선이 어디서 출발해 어느 방향으로 갔는지는 알 수 있습니다. 맞은 점이 없으므로 맞은 거리나 물체 번호, 표면의 방향 같은 값은 없습니다.
방향만으로도 할 수 있는 일이 꽤 됩니다. 위로 간 광선은 파랗게, 수평선 쪽으로 간 광선은 하얗게 칠하면 하늘이 생깁니다. 방향을 좌표 삼아 하늘 사진에서 한 점을 뽑아 오기도 합니다. 이렇게 사방의 배경을 미리 찍어 둔 그림을 환경 맵이라고 부릅니다.
코드 한 벌
셰이더를 적는 언어는 그래픽 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)마다 다릅니다. 아래는 그중 HLSL(High-Level Shading Language, 고수준 셰이딩 언어)로 적은 미스 셰이더입니다. 광선이 위로 갈수록 파래지는 하늘을 냅니다.
[shader("miss")]
void SkyMiss(inout ColorPayload p)
{
float3 d = WorldRayDirection();
float t = 0.5 * (d.y + 1.0); // 0.8
p.color = lerp(WHITE,BLUE,t); // 파랑 80%
}
오른쪽 주석은 방향이 (0, 0.6, 0.8) 인 광선 하나가 돌 때 그 줄이 쥔 값입니다. 비스듬히 위로 간 광선입니다. 첫 줄의 [shader("miss")] 는 이 함수가 미스 셰이더라고 표시합니다. inout 은 페이로드를 읽기도 하고 고쳐 쓰기도 한다는 뜻입니다.
WorldRayDirection() 은 광선의 방향을 길이 1 인 화살표로 돌려줍니다. 위쪽 성분 d.y 는 -1 에서 1 사이 값입니다. t 를 셈하는 줄이 이를 0 에서 1 사이 값으로 옮깁니다.
lerp 는 두 색을 그 비율로 섞습니다. WHITE 와 BLUE 는 미리 정해 둔 하양과 파랑입니다. 바로 위를 본 광선은 파랑이 됩니다. 수평선을 본 광선은 하양과 파랑의 중간이 됩니다.
광선 종류마다 다른 미스 셰이더
한 장면에서 쏘는 광선이 한 종류만은 아닙니다. 점의 색을 보려는 광선이 있습니다. 맞은 점에서 전등 쪽으로 쏘아 그림자 안인지 보려는 광선도 있습니다. 앞의 것을 색 광선, 뒤의 것을 그림자 광선이라고 부릅니다.
두 광선은 빗나갔을 때 할 일이 다릅니다. 색 광선이 빗나가면 하늘색을 적어야 합니다. 그림자 광선이 빗나가면 「전등까지 막은 것이 없다」를 적어야 합니다. 그래서 한 파이프라인에 미스 셰이더를 여럿 넣어 둡니다.
어느 미스 셰이더를 돌릴지는 광선을 쏘는 쪽이 정합니다. 광선을 쏘는 함수에 미스 번호를 같이 넣습니다. 빗나가면 GPU 가 그 번호의 미스 셰이더를 찾아 돌립니다. 아래 표에서는 색 광선에 0번을, 그림자 광선에 1번을 넣어 쏩니다.
| 광선 | 미스 번호 | 미스 셰이더가 적는 것 |
|---|---|---|
| 색 광선 | 0 | 하늘색 |
| 그림자 광선 | 1 | 빛이 닿는다 |
GPU 가 번호로 셰이더를 찾는 표가 셰이더 바인딩 테이블입니다. 미스 번호는 이 표의 미스 구역에서 몇째 칸인지를 가리킵니다.
이 표는 셰이더 종류마다 구역을 나눕니다. 광선 생성 · 미스 · 적중 · 호출 가능의 네 구역입니다. 마지막의 호출 가능 셰이더는 다른 셰이더가 함수처럼 불러 쓰는 셰이더입니다.
그림자 광선의 미스
그림자 광선은 미스 셰이더가 주인공이 되는 광선입니다. 이 광선이 알고 싶은 것은 「막혔나」 하나뿐입니다. 무엇에 막혔는지, 그 물체가 무슨 색인지는 필요 없습니다.
그래서 흔히 이렇게 짭니다. 페이로드를 「막혔다」로 채워서 쏩니다. 미스 셰이더만 이 값을 「빛이 닿는다」로 고칩니다. 맞으면 아무도 안 고치므로 「막혔다」가 남습니다.
아래 그림은 색 광선이 나무 바닥에 맞은 다음 장면입니다. 바닥의 최근접 적중 셰이더가 맞은 점에서 전등 쪽으로 그림자 광선을 쏩니다.
sequenceDiagram
participant 바닥 as 바닥의 최근접 적중 셰이더
participant GPU as GPU 의 물체 찾기
participant 미스 as 그림자 미스 셰이더
바닥->>GPU: 전등 쪽으로 그림자 광선 · 페이로드는 막혔다
Note over GPU: 전등까지 구간 안에 물체가 없다
GPU->>미스: 미스 1번을 부른다
미스-->>바닥: 페이로드를 빛이 닿는다로 고쳐 돌려준다
Note over 바닥: 바닥에 전등 빛을 더한다
그림자 광선은 구간의 끝 거리를 전등까지의 거리로 잡습니다. 전등 너머의 벽은 그림자와 상관이 없기 때문입니다.
광선을 쏠 때는 GPU 에게 일을 덜어 주는 표시도 붙일 수 있습니다. 「첫 적중을 찾으면 찾기를 멈춰라」와 「최근접 적중 셰이더는 돌리지 마라」가 그렇습니다. 둘을 붙이면 맞은 쪽에서는 셰이더가 하나도 안 돕니다. 미스 셰이더 하나가 그림자 판정 전부를 맡게 됩니다.
미스 셰이더 안에서 부르는 것
미스 셰이더도 광선을 다시 쏠 수 있습니다. 호출 가능 셰이더도 부를 수 있습니다. 다만 하늘색을 적는 일에는 대개 둘 다 필요 없습니다.
광선 처리 도중에 광선을 또 쏘는 것을 재귀라고 부릅니다. 미스 셰이더가 쏜 광선도 이 재귀에 셉니다. 레이 트레이싱 파이프라인은 만들 때 재귀를 몇 번까지 허락할지 한도를 적어 둡니다. 미스 셰이더가 쏘는 광선도 그 한도 안에서만 쏠 수 있습니다.
비용
미스 셰이더는 빗나간 광선마다 한 번 돕니다. 맞은 점이 없으니 표면 색이나 빛을 셈할 일이 없습니다. 그래서 한 번 도는 비용은 대개 셰이더 가운데 가장 작습니다.
도는 횟수는 장면이 정합니다. 사방이 벽인 방 안에서는 빗나가는 광선이 거의 없습니다. 하늘이 화면의 절반을 차지하는 들판에서는 화면 점의 절반에서 미스 셰이더가 돕니다.
그래서 미스 셰이더는 가볍게 두는 편이 흔합니다. 방향으로 색을 섞거나 환경 맵에서 한 점을 읽는 정도로 끝냅니다.
미스 셰이더가 없는 방식
광선을 쏘는 방법이 레이 트레이싱 파이프라인 하나만은 아닙니다. 파이프라인에 속하지 않은 일반 셰이더에서도 광선을 쏘아 결과를 그 자리에서 곧바로 받을 수 있습니다. 이 방식을 레이 쿼리라고 부릅니다.
레이 쿼리에는 미스 셰이더가 없습니다. 맞았는지 빗나갔는지를 결과에서 읽어 코드에서 직접 갈라 씁니다. 그림자처럼 「막혔나」만 물을 때는 이쪽이 셰이더를 따로 묶을 필요가 없어 단순합니다.
미스 셰이더는 빗나간 뒤의 처리를 광선 종류마다 따로 바꿔 끼우고 싶을 때 씁니다. 반사 광선이 빗나가면 환경 맵을, 그림자 광선이 빗나가면 「빛이 닿는다」를 적게 하는 식입니다.
캐시 미스와 다른 「미스」
백엔드 개발자에게 미스라는 말은 캐시 미스로 먼저 들립니다. 찾는 값이 캐시에 없어 원본까지 가야 하는 일입니다. 미스 셰이더의 미스는 이와 관계가 없습니다.
여기서 미스는 과녁을 빗나갔다는 뜻입니다. 광선이 물체를 하나도 못 맞혔다는 말입니다.
관련 항목
미스 셰이더와 함께 파이프라인에 묶이는 셰이더
광선 생성 셰이더 · 최근접 적중 셰이더 · 임의 적중 셰이더 · 교차 셰이더 · 호출 가능 셰이더 · 셰이더
미스 셰이더를 고르고 부르는 구조
레이 트레이싱 파이프라인 · 셰이더 바인딩 테이블 · 셰이더 그룹 · 셰이더 그룹 핸들 · 적중 그룹 · 파이프라인 레이아웃
미스 셰이더가 주고받는 값
광선 페이로드 · 페이로드 크기 · 광선 구간 · 광선 플래그 · 광선 방향
빗나감을 판정하는 앞 단계
광선 순회 · 광선 교차 판정 · 가속 구조 · BVH · 최근접 히트
미스 셰이더가 그려 내는 배경과 판정
환경 맵 · 큐브 맵 · 스카이박스 · 그림자 광선 · 앰비언트 오클루전 · 하늘 렌더링
미스 셰이더 대신 빗나감을 다루는 수단
레이 쿼리 · 인라인 레이 트레이싱 · 컴퓨트 셰이더
미스 셰이더를 적는 언어와 이를 제공하는 그래픽 API
HLSL · GLSL · SPIR-V · DirectX Raytracing · Vulkan · Direct3D · Metal
미스 셰이더가 속하는 상위 분류
광선 추적 · 그래픽스 · 렌더링 · 실시간 렌더링 · GPU
미스라는 이름이 겹치는 헷갈리는 이웃
캐시 미스 · TLB 미스 · 페이지 폴트 · 분기 예측 실패
다른 이름: miss shader · 미스 쉐이더 · 빗나감 셰이더