정점 버퍼
고친 사람 github-actions[bot]
정점 버퍼는 도형의 꼭짓점 값을 한 덩이로 모아 그래픽 카드 쪽 메모리에 올려 둡니다. 그림을 그릴 때마다 값을 새로 보내지 않고 올려 둔 덩이를 읽습니다. 삼각형 하나라면 꼭짓점 셋의 위치와 색이 그 덩이에 차례로 들어갑니다.
쉽고 빠른 이해
정점 버퍼는 꼭짓점 값을 늘어놓은 덩이를 그래픽 카드가 읽을 수 있는 메모리에 올려 둔 것입니다. 삼각형 하나를 그린다면 꼭짓점 셋의 위치와 색이 이 덩이 안에 차례로 들어갑니다.
이게 없으면 한 장면을 그릴 때마다 꼭짓점 값을 하나씩 건네야 합니다. 사람 모형 하나가 꼭짓점 수만 개라면 그 수만 개를 매번 다시 보내는 셈입니다. 한 번 올려 두고 여러 번 읽는 쪽이 훨씬 적게 움직입니다.
어떻게 도나:
- 그리는 쪽이 꼭짓점 값을 순서대로 배열에 늘어놓습니다
- 그 배열을 그래픽 카드 쪽 메모리로 한 번 복사해 올립니다
- 그릴 때마다 그 덩이에서 꼭짓점이 하나씩 읽혀 나갑니다
대가는 올린 뒤에 바꾸기가 번거롭다는 것입니다. 값을 고치려면 다시 올려야 합니다. 장면마다 바뀌는 값이면 그 복사가 매번 붙습니다. 쓰지 않는 동안에도 메모리를 차지합니다.
상세
이 절은 꼭짓점 값이 어떤 모양으로 한 덩이에 담기는지부터 봅니다. 그다음 그 덩이가 어디에 놓이는지, 읽는 쪽이 숫자를 어떻게 끊어 읽는지, 같은 꼭짓점을 다시 쓰는 방법, 그리고 값이 바뀔 때 무엇이 비싸지는지를 차례로 따라갑니다.
부품을 상자에 담아 건넨다
공장에 부품을 넣는다고 해 봅시다. 하나씩 창구로 건네면 건넬 때마다 사람이 오가야 합니다. 상자에 담아 한 번에 밀어 넣으면 오가는 일이 한 번으로 끝납니다.
정점 버퍼가 그 상자입니다. 정점은 꼭짓점을 가리키는 그래픽스 쪽 말입니다. 이 글에서는 꼭짓점이라 쓰고, 정점 버퍼·정점 셰이더처럼 굳은 이름에만 정점을 씁니다.
꼭짓점은 도형을 이루는 점 하나입니다. 그 점에는 위치뿐 아니라 색이나 표면이 향한 방향 같은 값도 붙습니다. 꼭짓점 여럿의 값을 순서대로 한 덩이에 담아 그래픽 카드가 읽을 수 있는 메모리에 올려 둔 것이 정점 버퍼입니다.
담기는 것은 숫자뿐입니다. 이 덩이는 어느 숫자가 위치이고 어느 숫자가 색인지 스스로 말하지 않습니다. 그 구분은 뒤에서 볼 규칙이 맡습니다.
한 덩이에 늘어놓는 모양
값은 꼭짓점 단위로 묶여 차례로 놓입니다. 꼭짓점 하나 몫의 값이 한 벌 끝나면 바로 다음 꼭짓점 몫이 이어집니다.
값이 어떻게 늘어서는지는 배열 하나로 보면 빠릅니다. 아래는 삼각형 하나를 담은 배열입니다. 꼭짓점마다 위치 둘과 색 셋을 붙였습니다.
// 꼭짓점 셋 · 하나에 값 다섯 개
float v[] = {
-0.5, -0.5, 1.0, 0.0, 0.0, // 0번 꼭짓점
0.5, -0.5, 0.0, 1.0, 0.0, // 1번
0.0, 0.5, 0.0, 0.0, 1.0, // 2번
};
한 줄이 꼭짓점 하나입니다. 앞 두 값이 위치이고 뒤 세 값이 색입니다. 덩이에는 줄바꿈이 없고 숫자 열다섯 개가 이어져 있을 뿐입니다.
값을 이렇게 섞어 담는 대신 위치만 모은 덩이와 색만 모은 덩이로 나눠 담을 수도 있습니다. 같은 값 여섯 조각을 두 방식으로 늘어놓으면 이렇게 갈립니다.
block-beta columns 6 h1["섞어 담기"]:6 m1["위치 0"] m2["색 0"] m3["위치 1"] m4["색 1"] m5["위치 2"] m6["색 2"] h2["나눠 담기"]:6 s1["위치 0"] s2["위치 1"] s3["위치 2"] s4["색 0"] s5["색 1"] s6["색 2"]
한 꼭짓점 몫이 붙어 있으면 읽는 쪽이 가까운 메모리를 연달아 훑습니다. 나눠 담으면 위치만 쓰는 그리기에서 색을 건드리지 않습니다. 어느 쪽이 나은지는 그 값들을 같이 쓰느냐가 정합니다.
그 덩이가 놓이는 곳
GPU(Graphics Processing Unit, 그래픽 처리 장치)는 자기 옆에 붙은 전용 메모리를 가장 빠르게 읽습니다. 이 메모리를 비디오 메모리라고 부릅니다. 정점 버퍼를 올린다는 말은 프로그램이 만든 배열을 그 메모리로 한 번 복사해 둔다는 뜻입니다.
flowchart TD
A["프로그램이 만든 배열 · 보통 메모리"] --> B["올리기 · 한 번 복사한다"]
B --> C["정점 버퍼 · 그래픽 카드 쪽 메모리"]
C --> D["그리기 명령마다 꼭짓점이 하나씩 읽힌다"]
D --> E["정점 셰이더로 들어간다"]
그림 끝의 정점 셰이더는 꼭짓점 하나마다 한 번씩 도는 짧은 프로그램입니다. 그 점을 화면 어디에 놓을지 정합니다. 정점 버퍼는 그 프로그램에 값을 대주는 쪽입니다.
복사는 한 번이고 읽기는 여러 번입니다. 같은 나무 모형을 백 장면에 걸쳐 그려도 덩이는 올려 둔 채 그대로 두고 그리기 명령만 백 번 내립니다.
이 이득이 큰 까닭은 두 메모리 사이를 오가는 길이 좁기 때문입니다. 꼭짓점 값을 장면마다 보내면 그 좁은 길이 곧 병목이 됩니다. 그래픽 카드는 값이 도착하기를 기다립니다.
숫자를 끊어 읽는 규칙
버퍼는 숫자가 이어진 덩이일 뿐이라 읽는 쪽에 따로 알려 줘야 할 것이 넷 있습니다. 어디부터 시작해서, 값 몇 개씩 묶어, 어떤 타입으로 보고, 다음 꼭짓점까지 몇 칸을 건너뛸지입니다.
| 알려 주는 것 | 무엇을 정하나 | 위 배열에서 색의 값 |
|---|---|---|
| 시작 | 덩이의 몇 번째 값부터 읽나 | 세 번째 값부터 |
| 개수 | 한 꼭짓점 몫으로 몇 개를 묶나 | 세 개 |
| 타입 | 그 숫자를 무엇으로 읽나 | 소수 |
| 간격 | 다음 꼭짓점까지 몇 칸인가 | 다섯 칸 |
이 넷을 알려 주면 같은 덩이에서 위치와 색을 따로 뽑아낼 수 있습니다. 위치는 첫 값부터 둘씩, 색은 세 번째 값부터 셋씩 읽되 둘 다 다섯 칸마다 다음 꼭짓점으로 건너뜁니다.
이렇게 뽑혀 나온 값 한 벌을 정점 속성이라고 부릅니다. 꼭짓점 하나에 딸린 값이라는 뜻입니다. 규칙 자체는 버퍼 안에 없어서 따로 적어 둡니다.
같은 꼭짓점을 다시 쓰기
사각형 하나는 삼각형 둘로 그립니다. 두 삼각형은 꼭짓점 둘을 나눠 씁니다. 꼭짓점을 읽는 순서대로만 늘어놓으면 같은 값을 두 벌 담게 됩니다.
그래서 꼭짓점 목록과 그리는 순서를 갈라 둡니다. 정점 버퍼에는 서로 다른 꼭짓점 넷만 담습니다. 몇 번째 꼭짓점을 어느 차례로 쓸지는 번호 여섯 개로 따로 적습니다. 이 번호 목록을 담는 덩이가 인덱스 버퍼입니다.
flowchart TD
subgraph IB["인덱스 버퍼 · 번호 여섯"]
subgraph T1["첫 삼각형"]
i0["0"]
i1["1"]
i2["2"]
end
subgraph T2["둘째 삼각형"]
i3["0"]
i4["2"]
i5["3"]
end
end
subgraph VB["정점 버퍼 · 꼭짓점 넷"]
v0["꼭짓점 0"]
v1["꼭짓점 1"]
v2["꼭짓점 2"]
v3["꼭짓점 3"]
end
i0 --> v0
i1 --> v1
i2 --> v2
i3 --> v0
i4 --> v2
i5 --> v3
번호 0·1·2 가 첫 삼각형이고 0·2·3 이 둘째 삼각형입니다. 0번과 2번 꼭짓점으로 화살표가 둘씩 들어오는 것이 두 삼각형이 나눠 쓰는 대목입니다. 값은 버퍼에 한 번씩만 있고 번호만 두 번 나옵니다.
꼭짓점이 많고 서로 붙어 있는 모형일수록 이 이득이 커집니다. 삼각형 수천 개로 이뤄진 메시에서는 꼭짓점 하나를 예닐곱 삼각형이 같이 쓰는 일이 흔합니다.
값이 바뀔 때
올린 값을 고치려면 바뀐 값을 다시 올려야 합니다. 물결치는 수면처럼 장면마다 꼭짓점 위치가 달라지는 그림이라면 그 복사가 매 장면 붙습니다.
복사가 겹치는 것도 걸림돌입니다. 그래픽 카드가 아직 그 버퍼를 읽는 중에 같은 곳을 덮어쓰려 하면 둘 중 하나가 기다려야 합니다. 그래서 자주 바뀌는 값은 버퍼를 두 벌 두고 번갈아 씁니다.
sequenceDiagram
participant P as 프로그램
participant A as 버퍼 A
participant B as 버퍼 B
participant G as 그래픽 카드
Note over P,G: 이번 장면
P->>B: 새 값을 쓴다
G->>A: 읽어서 그린다
Note over P,G: 다음 장면 · 역할이 바뀐다
P->>A: 새 값을 쓴다
G->>B: 읽어서 그린다
이번 장면에는 프로그램이 버퍼 B 에 쓰고 그래픽 카드가 버퍼 A 를 읽습니다. 다음 장면에는 둘의 역할이 바뀝니다. 같은 곳을 동시에 건드리는 일이 없으니 어느 쪽도 기다리지 않습니다.
움직임을 꼭짓점 값 자체로 만들지 않는 길도 있습니다. 꼭짓점 목록은 손대지 않고 올려 둔 채, 옮기고 돌리는 계산만 정점 셰이더에 맡기면 덩이를 다시 올릴 일이 없습니다.
비용이 붙는 곳
메모리는 꼭짓점 수에 한 벌 크기를 곱한 만큼 듭니다. 모형을 정교하게 만들수록 꼭짓점이 늡니다. 꼭짓점마다 붙이는 값이 많을수록 한 벌이 두꺼워집니다. 쓰지 않는 값을 습관처럼 붙여 두면 그만큼이 계속 차지합니다.
그리는 동안 드는 비용은 덩이를 바꿔 끼우는 횟수를 따라갑니다. 그리기 명령 한 번을 드로 콜이라고 부릅니다. 물체마다 다른 버퍼를 물려 주면 드로 콜이 물체 수만큼 늘어납니다.
그래서 작은 물체 여럿을 한 덩이에 모아 담아 한 번에 그립니다. 같은 모형을 여러 벌 그릴 때는 목록을 한 벌만 올려 두고 위치 값만 바꿔 넘깁니다. 뒤엣것을 인스턴싱이라고 합니다.
관련 항목
정점 버퍼 안에 담기는 값
정점 · 정점 속성 · 법선 · UV 좌표 · 정점 색 · 접선 · 뼈대 가중치
정점 버퍼와 같은 그리기에 물리는 다른 버퍼
인덱스 버퍼 · 유니폼 버퍼 · 상수 버퍼 · 스토리지 버퍼 · 스테이징 버퍼 · 프레임버퍼 · 깊이 버퍼
정점 버퍼를 읽어 가는 처리 단계
그래픽스 파이프라인 · 입력 어셈블러 · 정점 셰이더 · 프리미티브 조립 · 래스터화 · 프래그먼트 셰이더
덩이를 끊어 읽는 방법을 정하는 설정
정점 배열 객체 · 정점 레이아웃 · 스트라이드 · 오프셋 · 버퍼 뷰 · 접근자
정점 버퍼가 놓이는 메모리와 그 관리
버퍼 · 버퍼 객체 · 비디오 메모리 · GPU · 메모리 매핑 · 버퍼 용도 힌트
정점 버퍼로 그려지는 도형
프리미티브 · 삼각형 · 메시 · 프리미티브 토폴로지 · 감김 순서 · 삼각형 스트립
꼭짓점을 버퍼 대신 만들어 내는 단계
지오메트리 셰이더 · 테셀레이션 · 스트림 출력 · 트랜스폼 피드백 · 컴퓨트 셰이더
정점 버퍼 비용을 키우거나 줄이는 요인
드로 콜 · 인스턴싱 · 정점 캐시 · 레벨 오브 디테일 · 정점 수 · 배칭
모형의 꼭짓점을 담아 두는 파일 포맷
glTF · OBJ · FBX · COLLADA
정점 버퍼를 다루는 그래픽 API
OpenGL · WebGL · Vulkan · Direct3D · Metal · WebGPU
정점 버퍼가 속하는 상위 분류
다른 이름: vertex buffer · 버텍스 버퍼