Real-Time Rendering 4e 판을 읽고 특히 의미 있었던 내용을 정리해서 블로그에 남긴 글입니다.
또, 필요한 배경 지식은 경북대학교 백남훈 교수님의 컴퓨터 그래픽스 강의를 기반으로 하고 있습니다.
책을 읽기 시작한 것은 지난 6월 23일부터인데, 기록을 남겨야 할 것 같아 이제부터라도 블로그에 글을 쓰려고 합니다.
제가 개인적으로 공부한 과정을 남긴 것이니, 오류가 있다면 지적해 주시면 감사하겠습니다. 혹시나 궁금한 점이 있으시다면 제가 아는 범위 내에서 답변을 작성해 보겠습니다.
1. 그래픽스 파이프라인의 전체적인 단계
그래픽스를 공부하는 사람들이라면, 그래픽스 파이프라인의 단계를 익히 들어봤을 것이다.
그래픽스 파이프라인(Graphics pipeline), 혹은 렌더링 파이프라인(Rendering pipeline)은 3차원에서 정의된 모델을 2차원 공간의 화면으로 내보내는 과정을 말한다. 일반적으로 2D 이미지를 생성(보통 렌더(render)한다고 한다)하는 과정 자체를 말한다.
예전에는 이 모든 단계가 VLSI 칩 같은 것을 사용해서 하드웨어에 기능이 고정되어 있었다.
그런데 전체 파이프라인을 가속화하려다 보니, 특정 단계가 느리다는 것이 발견되었다. 그 두 단계가 바로 정점 처리 단계(Vertex processing), 프래그먼트 처리 단계(Fragment processing)가 바로 그것이었다. 기술적 발달로 인해 트랜지스터가 많아지면서, 이들은 병렬로 처리하기 시작했다.
그 후로 그래픽스를 전문적으로 처리하는 하드웨어가 만들어지고, 그래픽스 전용 CPU, 즉 GPU도 만들어지게 되었다. 이때, GPU에서 돌릴 수 있는 작은 프로그램을 만들자는 흐름이 생겼다. 이 GPU를 위한 작은 프로그램을 사람들이 익히 듣게 되는 프로그램, 쉐이더(Shader)라고 부른다.
여기서 쉐이더가 나타나면서 GPU에 프로그래머가 컴파일한 코드를 돌릴 수 있게 되었다. 그래서 이때부터 나타난 파이프라인은 프로그래머가 프로그래밍 가능하다고 해서 프로그래머블 파이프라인(Programmable pipeline)이라 부른다.
프로그래머블 파이프라인에서는 특히나 시간이 많이 걸리는 두 단계, 정점 처리 단계와 프래그먼트 처리 단계를 유저 프로그램, 즉 쉐이더로 대체한다. 그래서 정점 처리하는 프로그램을 Vertex Shader, 프래그먼트 처리를 하는 프로그램을 Fragment Shader라고 부른다. 실제 위 그림 같은 그래픽스 파이프라인을 보면 Vertex Shader, Fragment Shader라는 용어를 찾아볼 수 있다.
실제로 그래픽스 프로그래머는 이 두 단계만을 사용해서 프로그래밍한다. 다른 단계는 GPU 같은 하드웨어에서 이미 알아서 처리해 주기 때문에 크게 신경 쓸 것이 없기 때문이다. 그래서 간단한 설정 값만을 조절하는 식이다.
1-1. Vertex Shader와 Fragment Shader?
그래픽스를 많이 접해보지 못한 분들께 이 두 개가 무엇을 처리하는지를 간단하게만 설명하자면 이렇다.
1. 정점 쉐이더(Vertex Shader)는 정점 각각에 대해 정보를 처리한다.
컴퓨터는 일반적으로 삼각형 단위로 그래픽을 처리한다. 삼각형은 가장 간단한 면이고 이를 합쳐서 충분히 복잡한 형태들을 만들 수 있기 때문이다.
이때 삼각형은 세 개의 점, 즉 정점(Vertex)으로 이루어지므로 그 세 개 정점에 대해 정보를 받아주어야 한다.
# 왜 정점에 대해 정보를 받을까? (접기)
그래픽스에서 거치는 변환(Transform, 행렬곱을 말함)은 거의 대부분 Affine Transform으로, 이 변환은 변환하고 나서도 평행성을 유지한다. 직선을 변환해도 여전히 직선의 형태를 가지고, 선분의 양 끝점을 변환해도 그 점은 여전히 선분의 양 끝점을 이룬다.
삼각형은 선분 세 개로 구성되고, 선분은 정점 두 개만 있으면 그릴 수 있다.
따라서 삼각형은 정점 세 개만 있으면 그릴 수 있게 된다.
당연한 말을 어렵게 써놓은 것 같다면 사실 맞다...
# 왜 정점에 대해 정보를 받을까? (닫기)
2. 프래그먼트 쉐이더(Fragment Shader)는 픽셀마다의 정보, 프래그먼트(Fragment)를 처리한다.
Fragment의 정의는 여기저기 둘러보면 모두 정의가 조금씩 다르다. 다만 일반적으로 Fragement란 모니터를 이루는 픽셀, 그 각각이 갖는 정보를 말한다.
정점 쉐이더가 끝나면 보통 래스터라이즈(Rasterization) 단계를 거쳐 픽셀마다 정보가 보간 되어 저장된다. 이들을 특별히 Fragment라고 부른다. 이들을 이용해서 최종적으로 모니터에 보이게 될 색상을 결정하는 것이 프래그먼트 쉐이더이다.
그래픽스 파이프라인에서 처리하는 모든 단계를 설명하는 것은 다른 강의를 찾아보면 좋을 것 같다.
일반적으로 그래픽스에 처음 입문하는 사람에겐 위 두 단계, 정점 쉐이더와 프래그먼트 쉐이더에 대해서만 설명을 듣게 된다. 실제로 이 두 가지만 알아도 그래픽스를 다루는 데에는 큰 지장이 없다.
2. 파이프라인에서 잘 안 다뤄지는 세 단계
그런데 실제 그래픽스 파이프라인에서 보면 이상한 단계가 몇 개 더 끼어 있는 걸 볼 수 있다. Tessellation 단계, Geometry Shader 단계, 위 파이프라인 그림에는 안 나와 있는 Stream Output이다.
이들은 OpenGL, DirectX 같은 그래픽스 API에서도 비교적 새로 나온 기능이다.
이 세 가지는 하드웨어 지원이 필요하며 모든 gpu가 구현하고 있는 것도 아니어서 실제 개발할 땐 그리 많이 쓰지 않는 단계이다. 즉, 사용해도 되고 안 해도 되는 단계이기도 하다.
하지만 또 알아두면 유용한 기능이다.
이 세 단계는 무슨 역할을 하는 것일까?
2-1. 테셀레이션(Tessellation)
테셀레이션은 원하는 만큼 삼각형을 분할하는 역할을 맡는다.
예를 들어 구를 만든다고 하면, 컴퓨터에서 완벽하게 구를 구현할 수 없기 때문에 삼각형을 여러 개 사용해서 근사를 해야 한다.
당연히 삼각형을 많이 사용할수록 더 높은 품질을 얻을 수 있지만 그만큼 연산량이 많아질 수 있다. 따라서 적절한 수의 삼각형을 사용해야 한다.
그렇기 때문에 모델에는 삼각형의 개수를 어느 정도 제한해 놓을 수밖에 없는데, 그 경우 거리가 가까울 땐 디테일이 아쉽고 멀 땐 지나치게 세밀하게 된다.
이를 해결할 수 있는 것이 테셀레이션 단계이다. 테셀레이션을 사용하면 때에 따라 필요한 만큼 삼각형을 사용하여 곡면을 생성할 수 있다.
테셀레이션 단계는 주어진 메시의 삼각형을 분할하여 새로운 삼각형을 만들어낸다. 중요한 점은 프로그래밍적으로 제어할 수 있다 보니, 가까울 땐 많이 분할하고 멀 땐 적게 분할하는 것이 가능해진다는 것이다.
이를 이용하면, 카메라와의 거리에 따라 디테일을 조절하는 세부 수준(Level of detail; LOD)을 구현할 수 있다.
또, 아래 그림처럼 평상시에는 낮은 폴리곤 개수만을 저장해 놨다가 필요할 때 소프트웨어에서 삼각형을 분할할 수도 있다.
정점들을 통해 곡면을 형성하는데, 이 곡면은 더 작은 곡면인 패치의 집합으로 나타낼 수 있다. 패치는 또 여러 개의 정점으로 구성된다.
이 단계는 Hull Shader, Tessellator, Domain Shader로 나눌 수 있다. 패치를 이루는 정점들을 통해 더 많은 정점을 생성할 수 있다.
2-2. 지오메트리 쉐이더(Geometry shader)
지오메트리 쉐이더는 테셀레이션보다 더 예전에 나온 쉐이더로 더 일반적으로 많이 사용된다.
여러 종류의 프리미티브를 받는다는 점, 새 정점을 만들어낼 수 있다는 점에서는 테셀레이션 쉐이더와 비슷하다.
하지만 생성이 제한적이고, 출력되는 프리미티브도 제한적이기 때문에 더 단순한 단계이다.
말만 들으면 어려운데, 실제로 사용되는 곳을 보면 이해하기 쉽다.
지오메트리 쉐이더는 파티클 생성에 많이 사용된다.
예를 들어 폭죽을 정점 하나로 표현한 뒤에 Geometry shader를 통해 이 정점을 두 개 삼각형으로 이루어진 사각형으로 바꾸어줄 수 있겠다.
이를 보면, 테셀레이션에 비해 만드는 도형이 더 단순한 편이다.
2-3. 스트림 출력(Stream output)
일반적으로 정점 쉐이더에 테셀레이션, 지오메트리 쉐이더까지 거치고 나면 래스터화(Rasterization) 단계로 넘어간다.
반면, 스트림 출력은 결과를 화면으로 바로 보내지 않고 정점 버퍼에 보관하도록 한다. 추가적인 처리를 위해 배열에 보내고 이를 CPU나 GPU에서 사용할 수 있게 된다.
보통 파티클 시뮬레이션에서 많이 사용하는데, 앞에서 말한 불꽃놀이가 그 예시이다. 지오메트리 쉐이더에서 정점을 두 개 삼각형으로 나눈 후, 추가적인 처리를 하기 위해 CPU나 GPU에 넘긴다. 그러고 나서 다시 Vertex shader에 넣어서 Fragment Shader 단계까지 거치는 식으로 할 수 있다.
쉽게 말해 그래픽스를 2-Pass로 나누어서 처리할 수 있게 해 준다.
단계가 복잡해 보이는데, 찾아보니 해당 글이 예시로 들기 좋을 것 같아 링크를 단다.
다이렉트X Stream Output을 이용한 파티클 (tistory.com)
세 가지 단계는 테셀레이션, 지오메트리 쉐이더, 스트림 출력 순으로 진행되며 각각은 선택사항이라서 있을 수도 없을 수도 있다.
따라서 필요할 때 적재적소에 사용하는 것이 포인트라고 할 수 있겠다.
참고
Real-Time Rendering 4e p.18~p.19
Vulkan Tutorial Introduction(그림 1 출처)
Four Ways to Create a Mesh for a Sphere | by Oscar Sebio Cajaraville | Medium(그림 2 출처)
렌더링 파이프 라인 - Tessellation Stage(테셀레이션 단계) (tistory.com)
Introduction to Particle Systems - Unity Learn
댓글