멈추지 않고 끈질기게
[Graphics] 렌더링 파이프라인(Rendering Pipeline) 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 Unity 2021.3.15f1 버전을 기준으로 작성되었습니다.
※ 해당 포스팅은 하기 출처들을 참조하였습니다.
- 오지현, 유니티 그래픽스 최적화 스타트업, 비엘북스, 2019
이번 포스팅에서는 렌더링 파이프라인에 대해 알아보겠습니다.
1. 렌더링 파이프라인(Rendering Pipeline)
렌더링 파이프라인(Rendering Pipeline)이란 간단히 말해서 3D 오브젝트를 2D 픽셀 이미지로 그려내는 과정 전체를 말합니다. 이 과정은 플랫폼에 따라 달라지는 부분도 많아서 명확하게 나누긴 어렵지만, 크게 어플리케이션, 지오메트리, 래스터라이저 스테이지 정도로 구분할 수 있습니다. 렌더링 파이프라인은 그 이름처럼 수도관 시스템과 비슷하여, 한 구간에 이상이 생기면 나머지 구간이 멀쩡해도 전부 영향을 받게 됩니다. 즉, 전체 시스템이 가장 문제가 있는 구간의 성능을 따라가게 되기 때문에 렌더링 과정을 최적화하려면 렌더링 파이프라인을 이해하고, 현재 게임이 어느 구간에서 성능 저하를 일으키고 있는지 알아내야 합니다.
2. 어플리케이션 스테이지(Application Stage)
어플리케이션 스테이지는 게임에서 오브젝트를 렌더링 하기 전에 각종 연산(물리 연산, 입출력 처리, AI, 네트워크, 오디오 등)을 처리하는 단계를 말합니다. 이는 엄밀히 말해서 GPU가 하는 일이 아니므로 렌더링 파이프라인에 속하지는 않다고 볼 수도 있지만, 이 단계의 결과가 렌더링 파이프라인에 영향을 줄 수 있기 때문에 넓은 의미에서 속한다고 볼 수 있습니다. 매 프레임마다 플레이어나 적과 같은 오브젝트들의 위치 변경, 회전 및 애니메이션 정보를 계산하고, 이러한 변경들은 버텍스 정보에 반영되기 때문입니다.
해당 단계에서는 특히 컬링(culling) 연산이 중요합니다. 컬링 연산은 현재 프레임에서 화면에 담길 오브젝트를 미리 선별하는 과정으로, 렌더링할 오브젝트 수 자체를 줄이기 때문에 GPU 파이프라인의 전체적인 부담이 줄어들게 됩니다. 컬링에 대해서는 추후 다른 포스팅에서 더 자세히 다루도록 하겠습니다.
3. 지오메트리 스페이지(Geometry Stage)
렌더링 파이프라인에서 오브젝트의 지오메트리 정보, 즉 버텍스와 폴리곤의 처리를 진행하는 단계를 지오메트리 스테이지라 합니다.
버텍스(vertex)는 삼각형을 만들기 위한 점들을 의미하며, 버텍스들을 이어서 만든 삼각형을 폴리곤(polygon)이라 합니다(사각 폴리곤도 존재합니다). 그리고 폴리곤들을 모아 특정 오브젝트의 모양을 나타낸 것을 메시(mesh)라 합니다. 버텍스 정보들은 GPU의 메모리에 저장되어 있으며, 이 버텍스 데이터는 메시의 형태만을 반영하는 로컬 스페이스(local space) 데이터입니다. 렌더링 시 해당 메시를 사용하는 오브젝트의 트랜스폼 정보를 반영하여 월드 스페이스(world space) 데이터로 변환해야 하고 이 과정을 월드 트랜스폼(world transform)이라 합니다. 또한 오브젝트를 화면에 출력할 때는 카메라를 기준으로 하며 카메라 또한 별도의 트랜스폼 정보를 가지기 때문에, 해당 데이터를 카메라에 상대적인 뷰 스페이스(view space) 데이터로 변환해야 합니다. 이 과정을 뷰 트랜스폼(view transform)이라 합니다. 해당 과정을 통해 최종적으로 오브젝트가 화면의 어느 위치에 보이게 될지 정해지며, 이러한 트랜스폼 변환은 버텍스 쉐이더(vertex shader)를 통해 처리됩니다.
4. 래스터라이저 스테이지(Rasterizer Stage)
3D 오브젝트를 2차원 픽셀 화면에 매칭시키는 과정을 래스터라이징(Rasterizing)이라고 하며, 렌더링 파이프라인에서 래스터라이징을 진행하는 단계를 래스터라이저 스테이지라 합니다. 오브젝트들이 화면의 어느픽셀에 그려질 것인지, 그 픽셀의 색상이 어떻게 되는지 결정하게 되며, 이 과정은 프래그먼트 쉐이더(fragment shader)에서 수행합니다(픽셀 쉐이더(pixel shader)라고도 합니다). 여기서 결정된 최종 픽셀 정보는 컬러 버퍼에 저장되며, 여기에는 RGB 및 알파의 4가지 채널 정보가 저장됩니다.
추가로 각 픽셀의 오브젝트와 카메라와의 거리값에 대한 정보가 필요하며, 이는 Z 버퍼(Z-Buffer)에 저장됩니다. 여기에 저장된 깊이값을 이용하여 오브젝트의 픽셀 렌더링 여부를 판단합니다. 한마디로 특정 픽셀에 오브젝트가 겹쳐있을 때, 카메와의 거리가 더 멀어 가려지는 픽셀은 렌더링하지 않는다는 것입니다.
상기 그림은 유니티에서 큐브와 구를 하나씩 생성하고, 큐브를 더 가까이에 배치한 후 캡쳐한 사진에 픽셀처럼 구역을 나눈 것입니다. 그림과 같은 상황에서 렌더링을 하면 우선 카메라에 가까운 큐브를 먼저 렌더링하고, Z 버퍼에 깊이값을 저장해둡니다. 다음에 구를 렌더링하면서 Z 버퍼를 체크하고, 이 때 사진의 체크된 픽셀(실제로는 더 첨예하겠지만) 부분에서는 큐브를 렌더링할 때 저장해둔 깊이값과 비교하며 어느 쪽이 뒤에 있는지 판단합니다. 비교 결과 현재 그리려는 픽셀이 이미 그려진 오브젝트에 가려지는 부분으로 판단되면, 해당 픽셀은 무시함으로서 비용을 절감하는 것입니다.
다만 투명한 오브젝트의 경우 얘기가 조금 달라집니다. 상기 사진처럼 카메라에 가까운 오브젝트가 투명 오브젝트일 경우, 뒤에 있는 오브젝트도 비쳐 보이기 때문에 해당 픽셀도 렌더링해야 합니다. 뿐만 아니라, 픽셀 출력 알파값을 이용하여 겹치는 부분의 픽셀에 이미 저장된 색상과 렌더링하는 오브젝트의 색상을 적절히 혼합하여 최종 색상을 결정하는 알파 블렌딩(Alpha Blending) 과정도 거쳐야 합니다. 따라서 투명 오브젝트를 많이 사용할 경우, Z 버퍼를 통한 비용 절감 효율이 떨어지고 알파 블렌딩으로 인한 추가 비용이 들어가기 때문에 주의해야 합니다.
5. 더블 버퍼링(Double Buffering)
사용자에게 완성된 프레임을 연속적으로 보여주기 위해 버퍼를 두 개 사용하는 시스템을 더블 버퍼링(Double Buffering)이라 합니다(3개 사용하는 트리플 버퍼링도 존재).
더블 버퍼링 시스템에서는 버퍼 a에 완성된 프레임을 출력하는 동안 버퍼 b에서 다음 프레임의 렌더링을 진행하고, 완료되면 버퍼b를 화면 출력에 사용하고 버퍼 a에서 다음 프레임을 렌더링 하는 과정을 반복합니다. 이 때 화면 출력에 사용하는 버퍼를 프론트 버퍼(Front-Buffer), 다음 프레임의 렌더링에 사용하는 버퍼를 백 버퍼(Back-Buffer)라고 합니다. 항상 렌더링이 완료된 버퍼를 출력함으로서, 사용자에게 순차적으로 렌더링되는 모습이 아닌 완성된 프레임을 연속적으로 보여줄 수 있습니다.
상기 내용을 정리하자면, 게임에서의 매 프레임은
1) 어플리케이션 스테이지에서 오브젝트의 이동, 회전, 애니메이션 등의 연산 및 컬링 연산 수행
2) 버텍스 쉐이더에서 각 오브젝트들의 로컬 스페이스 정보를 뷰 스페이스 정보까지 변환(트랜스폼)
3) 프래그먼트 쉐이더에서 최종적으로 화면에 그려질 내용을 컬러 버퍼에 저장
4) 완성된 컬러 버퍼를 프론트 버퍼로 화면에 출력, 백 버퍼에서 다음 프레임의 렌더링 준비
의 과정을 통해 출력되며, 이 과정이 반복되며 연속적인 화면으로 유저에게 보여지게 됩니다.
'Graphics' 카테고리의 다른 글
[Graphic][Unity] 라이트 프로브(Light Probe), 리플렉션 프로브(Reflection Probe) (0) | 2023.03.21 |
---|---|
[Graphics][Unity] 라이트 맵(Light Map) (1) | 2023.03.10 |
[Graphics][Unity] 쉐도우 매핑(Shadow Mapping) (0) | 2023.03.10 |
[Graphics] 포워드 렌더링 vs 디퍼드 렌더링 (0) | 2023.03.02 |
[Graphics] 드로우 콜(DrawCall) (0) | 2023.02.20 |