그래픽스 파이프라인
고친 사람 github-actions[bot]
그래픽스 파이프라인은 삼각형 같은 도형 데이터를 받아 화면에 칠할 픽셀 색으로 바꿔 내놓습니다. 그 일을 한 덩어리로 하지 않고 정해진 단계 여럿으로 쪼개 차례로 넘깁니다. 앞 단계가 내놓은 것을 뒤 단계가 받아 조금씩 다른 모양으로 바꿔 갑니다. 화면에 뜬 그림 한 장은 이 길을 끝까지 지난 결과입니다.
쉽고 빠른 이해
그래픽스 파이프라인은 점 세 개짜리 삼각형 하나를 받아 그 삼각형이 덮는 픽셀의 색을 내놓는 길입니다. 게임 화면 한 장은 이 길을 삼각형 수만 개가 지나간 결과입니다.
그리기를 한 덩어리 계산으로 짜면 삼각형을 하나씩 끝내고 다음으로 넘어가야 합니다. 일을 단계로 쪼개 두면 뒤 단계가 앞 삼각형을 붙들고 있는 동안 앞 단계가 다음 삼각형을 집어 들 수 있습니다.
어떻게 도나:
- 점 하나하나가 화면 어디에 놓이는지 계산합니다
- 점을 묶어 삼각형을 만들고, 그 삼각형이 덮는 픽셀 칸을 찾습니다
- 칸마다 색을 계산해 화면 이미지에 써넣습니다
대가가 있습니다. 단계 순서와 단계끼리 주고받는 데이터 모양이 미리 정해져 있습니다. 그 틀을 벗어나는 그리기 방법은 이 길로 올 수 없습니다.
상세
이 절은 그래픽스 파이프라인이 무엇을 받아 무엇을 내놓는지, 그 사이에 어떤 단계가 서 있는지를 봅니다. 삼각형 하나를 화면에 칠하는 예를 줄곧 씁니다.
자동차 공장의 조립 라인을 떠올리면 가깝습니다. 라인을 따라 자리마다 하는 일이 정해져 있고, 한 자리가 일을 마치면 다음 자리로 넘깁니다.
이 길을 걷는 것은 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 화면에 칠할 값을 한꺼번에 많이 계산하는 칩입니다. GPU 는 「자동차를 그려라」 같은 말을 알아듣지 못합니다. 알아듣는 것은 「이 점들을 삼각형으로 묶어 칠해라」뿐이고, 그 말을 처리하는 순서가 그래픽스 파이프라인입니다.
무엇을 받아 무엇을 내놓나
들어가는 것은 정점 배열입니다. 정점은 도형의 꼭짓점 하나를 나타내는 데이터 묶음입니다. 공간 좌표가 기본이고, 색이나 텍스처 좌표처럼 그 꼭짓점에 딸린 값이 함께 붙습니다.
정점 배열만으로는 어디서 도형이 끊기는지 알 수 없습니다. 그래서 「셋씩 묶어 삼각형으로 봐라」처럼 묶는 규칙을 함께 넘깁니다. 이렇게 묶여 나온 도형 하나를 프리미티브라고 부릅니다.
나오는 것은 프레임버퍼에 적힌 색 값입니다. 프레임버퍼는 화면 한 장 분량의 픽셀 값을 담아 두는 메모리입니다. 파이프라인이 끝나면 화면 장치가 이 메모리를 읽어 화면에 뿌립니다.
| 무엇 | |
|---|---|
| 들어가는 것 | 정점 배열 · 정점을 어떤 도형으로 묶을지 정한 규칙 |
| 나오는 것 | 프레임버퍼 칸마다 적힌 색 값 |
한 덩어리로 짜지 않고 단계로 쪼갠 까닭
그리기는 같은 계산을 엄청나게 되풀이하는 일입니다. 삼각형 수만 개에 픽셀 수백만 개를 매 장면마다 다시 계산합니다. 단계로 쪼개 두면 단계마다 전용 회로를 두고, 서로 다른 삼각형을 단계마다 하나씩 올려 동시에 굴릴 수 있습니다.
쪼갠 덕을 보는 쪽이 하나 더 있습니다. 순서와 주고받는 데이터 모양이 정해져 있으면 프로그래머가 손댈 대목이 좁아집니다. 점의 위치를 정하는 계산과 픽셀의 색을 정하는 계산만 짜 넣으면 나머지는 칩이 알아서 처리합니다.
대신 순서를 바꾸거나 단계를 건너뛸 수 없습니다. 정해진 틀이 주는 속도와 맞바꾼 것입니다.
단계가 이어지는 순서
아래 그림은 삼각형 하나가 지나는 단계를 순서대로 놓은 것입니다. 세 묶음으로 갈라 두었는데, 묶음이 바뀌는 대목이 곧 다루는 데이터가 바뀌는 대목입니다.
그림에 셰이더라는 낱말이 두 번 나옵니다. 셰이더는 GPU 에서 도는 작은 프로그램이고, 그리는 사람이 직접 짜 넣는 계산이 여기 들어갑니다. 나머지 단계 이름은 그림 뒤 소절에서 하나씩 풉니다.
flowchart TD
subgraph A["정점을 다룬다"]
A1["정점 셰이더 · 점마다 화면 위치 계산"]
A2["프리미티브 조립 · 점을 도형으로 묶음"]
A3["클리핑과 컬링 · 안 보일 것 버림"]
A1 --> A2 --> A3
end
subgraph B["도형을 픽셀 칸으로 바꾼다"]
B1["래스터화 · 도형이 덮는 칸 찾기"]
end
subgraph C["픽셀 후보를 다룬다"]
C1["프래그먼트 셰이더 · 칸마다 색 계산"]
C2["깊이 테스트 · 가려진 것 버림"]
C3["블렌딩 · 이미 칠해진 색과 섞음"]
C1 --> C2 --> C3
end
A3 --> B1 --> C1
앞쪽 — 정점을 다루는 단계
첫 단계인 정점 셰이더는 정점마다 한 번씩 도는 셰이더입니다. 정점을 하나씩 따로 받아, 그 점이 화면의 어느 지점에 놓이는지 계산해 내놓습니다.
이 단계에서는 어느 점이 어느 삼각형에 속하는지 아직 모릅니다. 점을 묶어 도형을 만드는 일은 다음 단계인 프리미티브 조립이 맡습니다. 넘겨받은 묶는 규칙대로 점을 끊어 삼각형을 세웁니다.
도형이 생기고 나서야 물을 수 있는 것이 둘 있습니다. 화면 밖으로 삐져나갔나, 그리고 카메라를 등지고 있나입니다. 화면 밖으로 나간 부분을 잘라 내는 일이 클리핑이고, 등진 면을 버리는 일이 컬링입니다.
둘 다 칠할 필요가 없는 것을 미리 덜어 냅니다. 뒷단계로 갈수록 다뤄야 할 개수가 불어나므로, 버릴 것은 앞에서 버릴수록 전체 계산이 줄어듭니다.
가운데 — 도형이 픽셀 칸으로 바뀌는 단계
래스터화는 도형 하나가 화면의 어느 픽셀 칸을 덮는지 찾는 절차입니다. 화면은 픽셀 칸이 바둑판처럼 깔린 것이라, 좌표로 적힌 삼각형을 칸 단위로 바꿔야 칠할 수 있습니다.
래스터화가 찾은 칸마다 프래그먼트가 하나씩 생깁니다. 프래그먼트는 「이 픽셀에 칠할 후보」 하나입니다. 후보라고 부르는 까닭은 뒤 단계에서 다른 도형에 가려 버려질 수 있어서입니다.
프래그먼트는 색 값을 아직 안 가졌습니다. 대신 꼭짓점에 붙어 있던 값을 세 꼭짓점으로부터의 거리에 맞춰 섞은 값을 들고 나옵니다. 삼각형 한 꼭짓점이 빨강이고 다른 꼭짓점이 파랑이면 그 사이 칸들은 두 색을 섞은 값을 받습니다.
뒤쪽 — 픽셀 후보를 다루는 단계
프래그먼트 셰이더는 프래그먼트마다 한 번씩 도는 셰이더입니다. 넘겨받은 값과 텍스처를 읽어 이 칸에 칠할 색 하나를 내놓습니다. 그림자나 반사처럼 눈에 띄는 표현은 대개 이 단계에서 만들어집니다.
색이 정해져도 바로 써넣지는 않습니다. 앞뒤로 겹친 도형이 여럿이면 카메라에 가까운 것만 보여야 합니다. 이것을 가리는 단계가 깊이 테스트입니다. 칸마다 지금까지 칠한 것 중 가장 가까운 거리를 적어 두고, 새 프래그먼트가 그보다 멀면 버립니다.
마지막으로 블렌딩이 남은 색을 이미 칠해져 있던 색과 섞습니다. 유리창이나 연기처럼 뒤가 비쳐야 하는 것을 이렇게 그립니다. 섞지 않기로 하면 새 색이 옛 색을 덮어씁니다. 여기까지 마친 값이 프레임버퍼에 적히고 한 프래그먼트의 여정이 끝납니다.
지나가면서 개수가 바뀐다
이 길의 특징 하나는 단계마다 다루는 덩어리의 개수가 크게 달라진다는 것입니다. 앞쪽은 정점 수만큼 돌고, 래스터화를 지나면 픽셀 칸 수만큼 돕니다. 화면을 크게 덮는 삼각형 하나가 프래그먼트 수만 개를 낳습니다.
flowchart TD
V["정점 3개"] --> P["삼각형 1개"]
P --> F["프래그먼트 수만 개 · 덮은 칸마다 하나"]
F --> D["살아남은 것만 프레임버퍼로"]
그래서 계산을 어느 단계에 두는지가 일의 양을 가릅니다. 꼭짓점 셋에서 한 번 계산해도 되는 것을 프래그먼트 셰이더에 두면 같은 계산을 수만 번 하게 됩니다.
거꾸로 프래그먼트 셰이더에서만 할 수 있는 것도 있습니다. 픽셀 칸마다 텍스처를 읽어 무늬를 입히는 일은 칸 단위로만 됩니다. 어느 쪽에 둘지 고르는 것이 그리기 코드를 짤 때 되풀이되는 결정입니다.
프로그램을 끼워 넣는 단계와 그럴 수 없는 단계
단계가 전부 같은 성격은 아닙니다. 코드를 짜 넣을 수 있는 단계가 있고, 설정만 고를 수 있는 단계가 있습니다. 정점의 위치와 프래그먼트의 색은 만드는 사람마다 다르게 정하고 싶은 것이라 코드를 받고, 도형을 칸으로 바꾸는 계산은 누가 해도 같은 규칙이라 회로로 굳혀 둡니다.
| 단계 | 프로그램을 끼워 넣나 | 무엇을 정하나 |
|---|---|---|
| 정점 셰이더 | 넣는다 | 점의 화면 위치와 점에 딸린 값 |
| 프리미티브 조립 | 못 넣는다 | 묶는 규칙만 고른다 |
| 클리핑 · 컬링 | 못 넣는다 | 어느 면을 버릴지 설정으로 고른다 |
| 래스터화 | 못 넣는다 | 칸을 채울지 테두리만 그릴지 고른다 |
| 프래그먼트 셰이더 | 넣는다 | 칸에 칠할 색 |
| 깊이 테스트 · 블렌딩 | 못 넣는다 | 비교 방법과 섞는 방법을 고른다 |
셰이더를 짜 넣는 단계는 이 표의 둘 말고도 더 있습니다. 삼각형을 잘게 나누거나 도형을 더 만들어 내는 단계가 앞쪽에 끼어들 수 있는데, 쓰는 그림이 정해져 있어 안 쓰는 프로그램이 많습니다.
그리기를 시키는 API(Application Programming Interface, 응용 프로그램 인터페이스)는 이 단계 구성을 저마다 조금씩 다른 이름으로 부릅니다. OpenGL과 Vulkan이 그런 API 입니다. 이름과 단계를 묶는 방법은 갈리지만, 정점을 받아 프래그먼트를 거쳐 프레임버퍼로 간다는 줄기는 같습니다.
이 길을 지나지 않는 그리기
화면에 그림을 내는 방법이 이것 하나는 아닙니다. 픽셀마다 빛줄기를 쏘아 부딪히는 물체를 찾는 레이 트레이싱은 도형을 픽셀 칸으로 바꾸는 단계가 없습니다. 반사와 그림자가 자연스럽게 나오는 대신 계산이 많이 듭니다.
그림과 상관없는 계산을 GPU 에 시키는 길도 따로 있습니다. 컴퓨트 파이프라인에는 정점도 프래그먼트도 없고, 프로그램 하나를 수많은 데이터 조각에 한꺼번에 돌릴 뿐입니다. 물리 계산이나 머신러닝 학습이 이 길을 씁니다.
그래픽스 파이프라인을 고르는 때는 삼각형으로 이뤄진 모양을 화면에 빠르게 띄워야 할 때입니다. 게임 화면, 지도 렌더링, 브라우저의 3차원 그래픽이 여기에 듭니다. 한 장을 오래 걸려 그려도 되는 영화 장면이나, 그릴 것이 없는 순수 계산은 다른 길을 고릅니다.
관련 항목
파이프라인 앞쪽에 서는 단계
입력 어셈블러 · 정점 셰이더 · 테셀레이션 · 지오메트리 셰이더 · 프리미티브 조립 · 클리핑 · 컬링 · 원근 나누기 · 뷰포트 변환
파이프라인 뒤쪽에 서는 단계
래스터화 · 프래그먼트 셰이더 · 프래그먼트별 테스트 · 깊이 테스트 · 스텐실 테스트 · 블렌딩 · 출력 병합기
파이프라인을 오가는 데이터
정점 · 정점 버퍼 · 인덱스 버퍼 · 프리미티브 · 프래그먼트 · 텍스처 · 프레임버퍼 · 깊이 버퍼
이 파이프라인을 굴리게 하는 API
OpenGL · Vulkan · Direct3D · Metal · WebGL · WebGPU
셰이더 단계를 짜는 데 쓰는 언어
셰이더 · GLSL · HLSL · WGSL · SPIR-V
같은 하드웨어를 쓰는 다른 파이프라인
컴퓨트 파이프라인 · 레이 트레이싱 · GPGPU
이 파이프라인이 속하는 상위 분류
파이프라인 단계를 조절해 얻는 표현
다른 이름: graphics pipeline · 렌더링 파이프라인 · rendering pipeline · 그래픽 파이프라인