이전 글에서는 렌더링 파이프라인의 첫 단계인 Input Assembly 단계가 왜 필요한지를 정리했습니다.
GPU는 처음부터 “캐릭터”나 “건물” 같은 개념을 이해하는 것이 아니라,
Vertex 데이터를 읽고 Primitive 형태로 조립한다는 점도 함께 살펴봤습니다.
이번 글에서는 렌더링 파이프라인의 다음 단계인 Vertex Shader 단계에 대해 정리해보려고 합니다.
처음 Vertex Shader를 배우면 “Vertex를 처리하는 Shader” 정도로만 이해하는 경우가 많습니다.
물론 틀린 설명은 아니지만, 왜 GPU가 Vertex를 “변환”하기 시작했는지를 이해하는 것이 훨씬 중요하다고 생각합니다.
왜냐하면 렌더링 파이프라인의 전체 과정은 결국,
3D 공간 정보를 화면 이미지로 변환하는 과정이기 때문입니다.
그리고 Vertex Shader는 바로 이 공간 변환의 시작이라고 할 수 있습니다.
GPU는 처음부터 화면 좌표를 알고 있는 것이 아니다
렌더링 파이프라인의 첫 단계인 Input Assembly 단계에서 GPU는 기본 도형(Primitive)을 구성한다는 점을 지난 포스팅에서 살펴봤습니다.
Input Assembly 단계에서 아래와 같은 Triangle 구조가 만들어질 수 있습니다.
Triangle = (0, 1, 2)
여기에서 중요한 점은 이 시점의 Vertex 데이터는 아직 “화면 위치”가 아니라는 점입니다.
예를 들어 정점 위치가 아래처럼 존재할 수 있습니다.
Position = { 10, 5, -20 }
이 값은 3D 공간 안의 위치 정보입니다. 하지만 우리가 실제로 보는 모니터는 결국 2D 화면입니다.
따라서 GPU는 이렇게 3D 공간에 표현된 정보를 화면에 그릴 수 있는 형태로 변환해야 합니다.
그리고 바로 이 작업이 Vertex Shader 단계에서 시작됩니다.
왜 Vertex를 변환해야 할까?
최신 GPU는 아직 예전의 3D 가속 장치의 기본적인 매커니즘대로 동작합니다.
3D 가속 장치가 하는 일 중의 핵심은 삼각형으로 이루어진 기본 도형에 들어갈 픽셀을 판별하는 작업입니다.
렌더링 파이프라인을 공부하면서 “그냥 Vertex를 바로 그리면 되는 것 아닌가?”라는 생각을 할 수도 있는데,
실제 게임 화면은 생각보다 훨씬 많은 공간 계산 과정을 거쳐야합니다.
예를 들어서 게임 안에 배치된 객체들은 각자의 위치를 알고 있어야 하고, 회전도 가능해야 하며, 크기가 달라질 수도 있습니다.
카메라 역시 움직이고, 회전하며, Scene을 다른 방향에서 바라볼 수 있습니다.
즉, GPU는 단순히 Vertex를 그리는 것이 아니라,
현재 Camera 기준에서 객체가 어떻게 보여야 하는지를 계속 계산해야 합니다.
그리고 이 과정에서 다양한 변환 개념이 등장합니다.
- World 변환
- View 변환
- Projection 변환
Vertex Shader는 Vertex마다 실행된다
Vertex Shader(정점 셰이더)는 기본 적으로 모든 정점 마다 실행됩니다.
이 점을 기억하는 것이 굉장히 중요합니다. Vertex Shader는 Vertex 마다 한 번씩 실행됩니다.
예를 들어 Triangle 하나가 아래처럼 구성되어 있다면, 정점 셰이더는 Vertex 0, Vertex 1, Vertex 2에 대해 각각 실행됩니다.
Triangle = (0, 1, 2)
즉, Vertex Shader는 결국 각 Vertex의 위치를 변환하는 일을 담당합니다.
그리고 최근 GPU는 이런 계산을 병렬적으로 하기 때문에 수많은 Vertex 변환 작업을 동시에 수행할 수 있습니다.
World Transform은 왜 필요할까?
게임 엔진 안에서 사용되는 모델링 데이터는 외부의 3D 모델 저작 도구를 활용해 제작됩니다.
이렇게 제작되는 각 모델은 자신을 기준으로 하는 Local 공간 기준으로 제작됩니다.
예를 들어, 6면체인 큐브의 경우 모델의 중심 위치를 원점(0, 0, 0)으로 제작하고,
애니메이션이 가능한 캐릭터 모델의 경우 발을 딛는 위치를 원점(0, 0, 0)으로 제작하는 경우가 많습니다.
하지만, 이렇게 제작된 각 모델이 게임 안에 배치되려면 각 모델이 자신을 기준으로 삼았던 로컬 좌표에서 벗어나
“게임의 공간”을 기준으로 재배치되어야 합니다.
예를 들어서 캐릭터 모델을 사용하는 Player가 게임 안의 공간에 배치되면 아래와 같은 위치를 가질 수 있습니다.
Player Position = { 100, 0, 50 }
즉, GPU는 먼저 Local 공간 기준 Vertex를 World 공간 기준으로 변환해야 합니다.
로컬 공간의 위치를 월드 공간의 위치로 변환하는 과정을 월드 변환(World Transformation)이라고 하고, 이 때 World Matrix가 사용됩니다.
따라서 Vertex Shader 단계는 단순 Vertex 처리 단계가 아니라, 자신을 기준으로 삼아 로컬 공간에 각각 배치되어 있던 각 물체를 실제 게임 세계 안으로 다시 배치하는 과정이기도 합니다.
왜 Camera 기준으로 다시 계산해야 할까?
게임 안에 배치하기 위해 World 공간으로의 변환이 끝났다고 해서 바로 화면에 그릴 수 있는 것은 아닙니다.
(물론, 카메라 없이 공간을 설계하는 것도 가능하지만, 공간을 제한적으로 사용할 수 밖에 없고, 일반적인 경우에는 대부분 카메라를 사용합니다.)
화면은 결국 Camera 기준으로 결정되기 때문입니다.
예를 들어서 같은 객체의 경우에도 카메라의 위치와 방향에 따라 완전히 다른 화면 결과가 만들어집니다.
즉, GPU는 다시 World 공간 정보를 Camera 기준 공간으로 변환해야 합니다.
이 과정을 뷰 변환(View Transform)이라고 부르고, 이 과정에서 View Matrix가 사용됩니다.
즉, Vertex Shader는 로컬 공간의 위치를 월드 공간으로 변환한 후에 다시
“현재 카메라 입장에서 객체가 어디에 보이는가”를 계산합니다.
Projection 변환은 왜 필요할까?
Vertex Shader에서 변환을 처리할 때 보통 월드 변환, 뷰 변환, 투영 변환이 한 묶음으로 다루어 지는 경우가 많습니다.
월드 변환과 뷰 변환은 앞서 살펴봤으니 이번에는 투영 변환(Projection Transformation)에 대해 살펴보겠습니다.
자신을 기준으로 하던 객체가 게임 안의 같은 공간에 배치가 되고(월드 변환), 다시 카메라를 기준으로 배치(뷰 변환)된 후에 또 다른 중요한 문제가 등장합니다.
게임 세계는 3D 공간이지만, 모니터는 2D 화면이라는 점입니다.
즉, GPU는 결국 3D 공간 정보를 2D 화면 형태로 변환해야 합니다.
지금까지 다뤘던 변환은 모두 3D 공간에서 처리가 이루어집니다.
3D 공간에서는 원근감이 존재합니다. 따라서 3D에서 2D로 변환을 할 때 원근감을 적용해야 합니다.
이렇게 원근감을 적용하는 변환이 투영 변환(Projection Transformation)입니다.
투영 변환을 거치면 멀리 있는 객체는 작게 보이고, 가까운 객체는 크게 보이며, 시야 밖 객체는 화면에 보이지 않아야 합니다.
Projection Transform은 결국, 3D 공간을 화면 이미지 형태로 변환하기 위한 과정이라고 할 수 있습니다.
즉, 3차원 공간의 깊이 정보를 2차원 화면 위에 투영하는 과정입니다.
그리고 이 과정에서 Projection Matrix가 사용됩니다.
Vertex Shader는 공간 변환의 핵심 단계
로컬 공간
↓ 월드 변환
월드 공간
↓ 뷰 변환
뷰 공간
↓ 투영 변환
투영 공간
Vertex Shader는 Input Assembly 단계에서 넘어온 각 Vertex 마다 실행된다고 살펴봤습니다.
그래서 Vertex Shader가 단순히 Vertex를 처리하는 단계처럼 느껴질 수도 있습니다.
하지만 실제로는 3D에서 2D로의 공간 변환을 해야하는 렌더링 파이프라인의 전체 공간 변환의 핵심 단계라고 할 수 있습니다.
왜냐하면 이 단계에서 아래와 같이 핵심 공간 변환이 이루어지기 때문입니다.
- Local → World 변환
- World → View 변환
- View → Projection 변환
즉, Vertex Shader는 결국 3D 공간 정보를 화면 계산 가능한 형태로 변환하는 시작점이라고 할 수 있습니다.
왜 이름이 “Vertex Shader”일까?
잠시 용어를 살펴보려고 합니다.
렌더링 파이프라인에서 우리가 결국 집중하는 단계는 각 Shader 단계입니다.
그렇다면 Shader라는 단어는 무엇을 의미할까요? Shader는 Shade라는 단어에서 왔습니다.
Shade는 그늘, 어두움, 음영이라는 뜻을 가지고 있습니다. 밝음이 있어야 어두움도 있는 존재한다는 것은 잘 알고 계실겁니다.
따라서 Shader는 밝고 어두움을 표현하는 프로그램이라는 뜻을 갖습니다. 여기에는 색상을 표현하는 것도 포함됩니다.
초기의 Shader는 실제로 조명과 음영 계산을 담당하는 역할에서 시작되었습니다.
하지만 현대 GPU에서 Shader는 단순 색상 계산만 처리하지는 않습니다.
실제로 Vertex Shader는 공간 변환 계산을 주로 담당합니다.
여기에서 중요한 점은 각 Shader 단계는 개발자가 직접 프로그래밍할 수 있다는 점입니다.
즉, Vertex Shader는 GPU의 Vertex 처리 과정을 개발자가 직접 제어할 수 있는 프로그램입니다.
CPU는 Vertex Shader를 위해 무엇을 준비할까?
여기서 CPU 역할 역시 굉장히 중요합니다.
GPU가 스스로 World Matrix나 View Matrix를 생성하는 것은 아닙니다.
일반적으로 정점 변환을 위해서 CPU는 World Matrix, View Matrix, Projection Matrix를 계산해서 GPU의 Shader 단계로 전달합니다.
예를 들어 DirectX에서는 Constant Buffer 형태로 Shader에게 전달합니다.
즉, CPU는 공간 계산 데이터를 준비하고, GPU는 Vertex Shader 단계에서 실제 변환 계산을 수행하게 됩니다.
실제 변환은 CPU가 전달한 행렬을 곱하는 형태로 처리됩니다.
행렬 곱 처리는 CPU에서 진행해도 되지만, GPU에서 더 좋은 성능을 내기 때문에 GPU에서 처리하는 것이 일반적입니다.
Vertex Shader 단계는 이후 단계들과도 연결된다
Vertex Shader가 끝났다고 해서 렌더링이 끝나는 것은 아닙니다.
이후 GPU는 변환된 Vertex들을 기반으로 기본 도형(Primitive)을 화면 공간으로 변환하고, 픽셀 후보를 생성하며, 최종 색상을 계산합니다.
즉, Vertex Shader는 이후의 래스터라이저(Rasterizer), 픽셀 셰이더(Pixel Shader)와 같은 단계들과도 연결됩니다.
그리고 이 단계가 제대로 동작하지 않으면 이후 모든 렌더링 결과 역시 잘못될 수 있습니다.
공장의 컨베이어 벨트를 생각해보면, 앞의 공정에서 문제가 생기면 이후의 공정에서 제대로된 처리가 어렵다는 점을 상상하시면 이해하시는데 도움이 됩니다.
Vertex Shader는 “공간 변환 단계”다
Vertex Shader 단계는 3D 공간 정보를 화면 계산이 가능한 형태로 변환하는 핵심 단계라는 것을 알게됩니다.
그리고 이 과정을 이해하기 시작하면 왜 Matrix, Camera, Projection 같은 개념들이 렌더링 파이프라인 안에서 계속 등장하는지도 이해할 수 있습니다.
마무리
Vertex Shader 단계는 단순 Vertex 처리 단계처럼 느껴질 수도 있습니다.
하지만 실제로는 렌더링 파이프라인 전체 공간 변환 과정의 핵심 단계라고 할 수 있습니다.
Vertex Shader 단계에서 아래와 같은 핵심 공간 변환 처리가 이루어집니다.
- World Transform
- View Transform
- Projection Transform
다음 글에서는 Rasterizer 단계가 왜 필요한지,
그리고 GPU는 왜 삼각형을 “픽셀 후보” 형태로 변환하기 시작했는지를 이어서 정리해보려고 합니다.