출력 병합기
출력 병합기는 앞 단계가 계산해 낸 색을 화면에 남길지 말지 정합니다. 남길 색은 이미 칠해져 있던 색과 어떻게 합칠지도 정합니다. 그리기의 맨 끝에 서서 화면 메모리에 값을 써넣는 단계입니다. 앞 단계가 똑같은 색을 내놓아도 이 단계를 어떻게 설정해 두느냐에 따라 화면에 나오는 그림이 달라집니다.
쉽고 빠른 이해
출력 병합기는 그리기의 마지막 단계입니다. 후보 색 하나를 받아 화면 메모리에 쓸지 말지 정합니다. 쓰는 색은 있던 색과 섞습니다. 유리창처럼 반투명한 물체 뒤가 비쳐 보이는 것이 이 단계가 섞어 준 결과입니다.
이게 없으면 셰이더가 내놓은 색이 언제나 화면을 덮습니다. 뒤에 있는 물체가 앞엣것을 지웁니다. 반투명은 표현할 방법이 없습니다. 화면 한 칸을 여러 후보가 동시에 노릴 때 읽고 쓰는 순서가 엉키는 문제도 남습니다.
어떻게 도나:
- 후보를 테스트로 거릅니다. 표식을 보는 스텐실 테스트와 거리를 보는 깊이 테스트를 통과 못 한 후보는 버립니다
- 살아남은 색을 화면에 있던 색과 정해진 비율로 섞습니다
- 섞은 값을 화면 메모리에 써넣습니다. 빨강·초록·파랑 가운데 꺼 둔 것이 있으면 그 색만 건드리지 않습니다
대가는 셋입니다. 후보마다 화면 메모리를 읽고 쓰므로 그리는 양이 늘수록 부담이 커집니다. 섞기는 앞뒤 순서를 타기 때문에 반투명한 물체는 먼 것부터 그려 주어야 결과가 맞습니다. 그리고 계산 방식은 미리 정해진 것 중에서 골라야 해서 셰이더처럼 마음대로 짤 수 없습니다.
상세
이 절은 출력 병합기가 무엇을 받아 무엇을 남기는지부터 봅니다. 그다음 후보를 거르는 테스트와 색을 섞는 규칙, 써넣기를 막는 마스크를 차례로 짚습니다.
마지막 두 소절은 왜 이 단계만 코드를 못 짜 넣는지, 그리고 왜 문서마다 이름이 다르게 나오는지를 다룹니다.
덧칠하는 한 장의 그림
여러 사람이 한 장의 그림에 차례로 덧칠한다고 해 봅시다. 나중에 칠한 색이 남는 것이 보통이지만, 물감이 묽으면 아래 색이 비쳐 나옵니다. 출력 병합기는 이 덧칠을 어떻게 할지 정해 두는 규칙입니다.
다만 그림은 붓이 닿은 곳이 통째로 덮입니다. 화면은 칸 하나하나가 따로 판정됩니다. 화면을 잘게 나눈 칸 하나가 화면에 찍히는 점 하나입니다. 어떤 칸은 새 색으로 덮이고 바로 옆 칸은 있던 색으로 남습니다.
받는 것과 내는 것
래스터화가 도형을 화면 칸에 맞춰 잘게 쪼개면 칸마다 후보가 하나씩 생깁니다. 이 후보를 프래그먼트라고 부릅니다. 프래그먼트는 색뿐 아니라 그 후보가 카메라에서 얼마나 떨어져 있는지 같은 값을 함께 지닙니다.
도형 하나가 화면에 찍히기까지 거치는 단계들의 줄을 그래픽스 파이프라인이라고 부릅니다. 래스터화도 출력 병합기도 그 줄 위에 선 한 단계입니다.
프래그먼트마다 최종 색을 계산하는 것은 프래그먼트 셰이더입니다. 셰이더는 파이프라인 한 단계에서 그래픽 하드웨어가 돌리는 짧은 프로그램입니다. 무엇을 계산할지는 개발자가 직접 짜 넣습니다. 출력 병합기는 셰이더가 내놓은 색 하나를 받습니다.
함께 보는 것은 화면에 낼 값을 담아 둔 메모리 셋입니다. 색 버퍼는 지금까지 칠해진 색을 담습니다. 깊이 버퍼는 칸마다 가장 가까웠던 거리를 담습니다. 스텐실 버퍼는 칸마다 표식을 담습니다.
이 셋을 묶어 프레임버퍼라고 부릅니다. 출력 병합기가 읽고 쓰는 것은 이 묶음이 전부입니다.
flowchart TD
A["래스터화"] --> B["프래그먼트"]
B --> C["프래그먼트 셰이더"]
C --> D["후보 색"]
D --> E["출력 병합기"]
subgraph FB["프레임버퍼"]
F["색 버퍼"]
G["깊이 버퍼"]
H["스텐실 버퍼"]
end
E --> F
E --> G
E --> H
받는 것은 후보 하나이고 내는 것은 세 버퍼에 써넣은 새 값입니다. 출력 병합기는 다음 단계로 넘기는 것이 없습니다. 그리기가 여기서 끝나기 때문입니다.
후보를 거르는 테스트
써넣기 전에 후보가 테스트를 거칩니다. 테스트는 후보의 값을 고치지 않고 통과와 탈락만 가립니다. 탈락한 후보는 버려집니다. 화면에 아무 흔적도 남지 않습니다.
스텐실 테스트는 칸마다 적어 둔 표식과 후보를 견줍니다. 표식이 맞는 칸에만 그리게 합니다. 화면의 일부만 골라 그리거나 거울에 비친 상을 만들 때 씁니다.
깊이 테스트는 후보의 거리와 칸에 적힌 거리를 견줍니다. 후보가 더 가까울 때만 통과시키므로, 뒤에 있는 물체가 앞엣것을 덮어쓰지 못합니다. 이 테스트 덕분에 그리는 순서를 맞추지 않아도 가까운 물체가 앞에 보입니다.
앞에 있는 것이 뒤엣것을 가리게 만드는 처리를 통틀어 은면 제거라고 부릅니다. 깊이 테스트는 그 처리를 칸 하나 단위로 하는 방법입니다. 출력 병합기는 그 방법을 굴리는 단계입니다.
테스트는 통과한 뒤 버퍼를 갱신하기도 합니다. 깊이 테스트를 통과한 후보는 자기 거리를 깊이 버퍼에 적습니다. 뒤이어 오는 후보가 그 값과 견주게 하려는 것입니다.
색을 섞는 규칙
테스트를 통과한 후보는 화면에 있던 색과 합쳐집니다. 이 합치기를 블렌딩이라고 부릅니다. 가장 단순한 규칙은 덮어쓰기입니다. 있던 색을 버리고 새 색만 남깁니다.
반투명을 표현하려면 두 색을 비율로 섞습니다. 후보 색에 얼마를 곱하고 있던 색에 얼마를 곱해 더할지를 계수로 정합니다. 그 비율을 정하는 값이 알파 채널입니다. 알파 채널은 후보 색에 함께 실려 옵니다.
가장 흔한 섞기 규칙은 이렇게 생겼습니다. 알파 값을 바꾸면 같은 계산이 덮어쓰기도 되고 반투명도 됩니다.
결과 = 후보색 × 알파 + 있던색 × (1 − 알파)
알파 1 → 후보색만 남는다 // 덮는다
알파 0 → 있던색만 남는다 // 자국 없다
알파 0.25 → 후보 ¼ · 있던 ¾ // 반투명
덮어쓰기는 따로 있는 규칙이 아니라 알파를 끝까지 올린 경우입니다. 규칙 하나로 두 가지를 다 처리하는 셈입니다.
섞기는 셰이더처럼 코드를 짜 넣는 대목이 아닙니다. 미리 마련된 계수와 연산 중에서 골라 켜는 방식입니다. 왜 그렇게 두었는지는 아래 소절이 받습니다.
써넣기를 막는 마스크
테스트와 섞기를 다 지나도 써넣기를 막을 수 있습니다. 마스크는 어느 버퍼의 어느 성분을 갱신할지 켜고 끄는 스위치입니다.
색은 성분마다 따로 끕니다. 빨강만 켜 두면 초록과 파랑은 있던 값이 남습니다.
깊이 버퍼도 따로 끕니다. 깊이 테스트는 계속 하되 통과한 후보가 자기 거리를 안 적게 하는 것이 반투명한 물체를 그릴 때 흔한 방식입니다.
마스크가 있는 이유는 「무엇을 볼지」와 「무엇을 남길지」가 다른 일이기 때문입니다. 둘을 따로 정할 수 있어야 한 번 그린 화면 위에 덧그리는 일이 가능해집니다.
도는 순서
지금까지 본 셋은 정해진 순서로 이어집니다. 테스트가 먼저 후보를 거릅니다. 살아남은 색만 섞입니다. 마지막에 마스크가 켜 둔 성분만 메모리에 들어갑니다.
flowchart TD
A["후보 색이 들어온다"] --> B["스텐실 테스트"]
B --> C["깊이 테스트"]
B -->|탈락| X["버린다"]
C -->|탈락| X
C --> D["있던 색과 섞는다"]
D --> E["마스크가 켜 둔 성분만 써넣는다"]
테스트가 섞기보다 앞인 데는 이유가 있습니다. 어차피 버릴 후보를 섞어 봐야 계산만 버립니다. 가려진 물체가 앞엣것과 섞여 버리면 그림도 어긋납니다.
고정된 단계인 까닭
정점 셰이더와 프래그먼트 셰이더는 개발자가 코드를 짜 넣습니다. 출력 병합기는 그렇지 않습니다. 마련된 설정을 고르기만 합니다.
까닭은 순서입니다. GPU(Graphics Processing Unit, 그래픽 처리 장치)는 화면 칸 여럿을 한꺼번에 계산합니다. 같은 칸을 노리는 후보 둘이 비슷한 때에 도착하면 색 버퍼를 읽고 쓰는 순서가 엉킬 수 있습니다.
sequenceDiagram
participant A as 후보 A
participant B as 후보 B
participant C as 색 버퍼
A->>C: 있던 색을 읽는다
B->>C: 있던 색을 읽는다
A->>C: 섞은 색을 쓴다
B->>C: 섞은 색을 쓴다
Note over C: 고정된 하드웨어가 도형 순서대로 처리해 이 엉킴을 막는다
둘 다 읽고 나서 둘 다 쓰면 먼저 쓴 쪽의 결과가 사라집니다. 섞기는 있던 값을 읽어 새 값을 쓰는 일이라 순서가 어긋나면 결과가 달라집니다. 그래서 이 읽고 쓰기를 고정된 하드웨어가 맡아, 도형이 들어온 순서대로 처리되도록 보장합니다.
여기를 열어 두면 그 보장이 깨집니다. 쓸 수 있는 규칙이 몇 가지로 제한되는 대신 결과가 어긋나지 않습니다.
이름이 갈리는 대목
이 이름은 Direct3D 계열에서 왔습니다. 파이프라인 마지막 단계에 Output Merger 라는 이름을 달았습니다. 줄여서 OM 이라고 씁니다.
OpenGL 계열은 같은 일을 하는 구간을 프래그먼트별 연산이라고 부릅니다. 이름은 다르지만 담고 있는 것은 같습니다. 후보를 거르는 테스트, 색 섞기, 마스크가 그 안에 들어 있습니다.
바로 앞 단계도 이름이 갈립니다. 이 편에서 프래그먼트 셰이더라고 부른 것을 Direct3D 계열은 픽셀 셰이더라고 부릅니다. 부르는 이름이 달라도 하는 일은 같습니다.
두 계열의 문서를 오가며 읽으면 이름 때문에 서로 다른 것처럼 보입니다. 같은 구간을 두 이름으로 부르는 것이라고 알아 두면 됩니다. 앞에 있는 단계들을 포함해 전체가 어떻게 이어지는지는 그래픽스 파이프라인이 다룹니다.
관련 항목
출력 병합기가 거치는 처리 단계
스텐실 테스트 · 깊이 테스트 · 블렌딩 · 논리 연산 · 컬러 라이트 마스크
출력 병합기가 읽고 쓰는 버퍼
프레임버퍼 · 렌더 타깃 · 색 버퍼 · 깊이 버퍼 · 스텐실 버퍼 · 알파 채널
출력 병합기 앞에 서는 파이프라인 단계
그래픽스 파이프라인 · 래스터화 · 프래그먼트 셰이더 · 픽셀 셰이더 · 정점 셰이더 · 프래그먼트
출력 병합기를 설정하는 그래픽스 API
Direct3D 11 · OpenGL · Vulkan · WebGPU
출력 병합기가 맡는 은면 제거와 그 대안
은면 제거 · 화가 알고리즘 · 뒷면 제거 · Z 파이팅
출력 병합기가 도는 하드웨어와 그 둘레
다른 이름: Output Merger · OM · 출력 병합 단계 · 프래그먼트별 연산