2023. 9. 5. 23:26ㆍ배움엔 끝이없다/그래픽스
쉐이더 스터디 내용 공유를 위해 작성했습니다.
참고 강의 영상: 알쓸유잡 - 야 너두 쉐이더 개발할 수 있어!
주 참고 자료: Render Hell - Book 2
1. GPU의 내부 시스템들 (Nvidia GPU 기준)
GPU 코어는 per vertex / pixel 연산을 할 수 있지만,
tessellation이나 culling 등 복잡한 Fixed pipeline의 작업들은 전문 하드웨어 블록이 따로 존재한다.
Streaming Multiprocessor(SM)는 하나의 쉐이더에 속한 stream of verticies/pixels를 담당하고, 이에 대한 작업을 여러 GPU 코어에 분배한다.
하나의 GPU에 여러개의 SM이 있기 때문에, 2개 이상의 쉐이더를 동시에 실행할 수 있다.
2. GPU의 pipeline
아래 과정 중 4~8의 과정은 Shader를 실행할 때 일어나는 일이다.
1. Application(게임)에서 Graphics Driver에게 API를 통해 원하는 명령을 전달한다.
2. Driver는 받은 명령을 토대로 Command Buffer를 채운다.
- 이 과정에서 오버헤드가 발생하는데, 각 API Call마다 오버헤드가 있어 API 호출을 최소화하는게 좋다. 최신 API에서는 이 오버헤드가 줄긴 했지만, 그래도 여러 API call을 큰 하나의 call로 만드는 것이 정석이다.
3. GPU의 Host Interface가 Command Buffer를 읽는다.
4. GPU가 RAM에서 VRAM으로 읽어와야할 데이터(Vertex buffer, Shader parameter 등)가 있을 수 있고, 이는 GPU 내부 담당 엔진이 읽어온다.
5. Gigathread Engine이 vertex/pixel마다 쓰레드를 만들어 Thread Block으로 묶는다. 그리고 이 것들은 SM들에게 분배된다.
6. SM 내부의 Polymorph Engine은 VRAM에 있는 정점데이터 등을 캐쉬로 복사해온다.
7. SM이 Thread Block을 32(up to 64)개의 Warp로 쪼갠다.
8. 각 Wrap을 GPU 코어가 실행한다.
- GPU 코어는 Branching을 할 수 없어서, 만약 if문이 쉐이더 내에 있으면 모든 브랜치를 다 실행하게 된다. (코어의 절반은 브랜치 A, 절반은 브랜치 B를 실행하는 방식인데, 브랜치 A를 실행하는 동안 나머지 절반의 코어는 쉰다.)
- 따라서 이는 코어를 n배로 소모해 비효율적이라, 분기가 있는 쉐이더는 컴파일 타임에 다른 쉐이더로 나눠주는게 좋다.
- Warp를 하나의 SM이 여러개 들고 있는 이유는, 어떤 Warp는 특정 데이터가 계산되기까지 기다려야하는데, 이 Memory Stall 동안 다른 Warp를 실행하기 위해서다.
3. Rendering Pipeline
1. Vertex Shader에서 per vertex operation을 해준다. 주로 Transformation이나 Lighting 등을 담당한다.
2. Tessellation Stage는 더 세밀한 표현을 위해 폴리곤을 늘리는 작업으로, optional하다.
2-1. Patch Assembly 단계에선 표면을 쪼개기 위해 정점 단위로 관리되던 데이터를 표면으로 변환한다.
2-2. Hull Shader는 포면을 쪼개기 위한 parameter인 tesselation factor (inner, outer)를 계산해 반환한다.
2-3. Tesselation 단계에선 tesselation factor에 맞게 표면에 여러 vertex를 추가해 면을 여러개로 만든다.
2-4. Domain Shader는 Hull Shader의 output(Patch Assembly의 output)과 Tesselator의 output을 받아서 쪼개진 표면을 이루는 정점들의 위치를 결정한다.
- Displacement Map이 이 단계에서 사용된다.
3. Primitive Assembly 단계에선 정점 단위로 관리되던 데이터를 해석해 primitive(점, 선, 면)를 만든다.
4. Geometry Shader는 Rasterizing 직전에 마지막으로 primitive를 변환하는 쉐이더로, 정점을 추가하거나 변환할 수 있다. 추가적으로 점을 삼가형으로 변환하는 등 primitive를 다른 primitive로 바꿀 수도 있다.
5. Rasterizing 전에 지금까지 변환된 점들 (Projection Space좌표)을 Viewport에 맞게 변환하고(=Viewport transformation), Viewport의 경계에서 잘린 primitive를 자른다(=Clipping).
6. 하나의 GPU엔 여러 Rasterizer가 있고 각 Rasterizer가 담당하는 화면 내 구역이 있는데, primitive가 걸친 Rasterizer에게 해당 primitive 정보를 전달한다.
7. Rasterizer가 primitive를 픽셀에 대응시킨다. 각 픽셀에는 primitive의 attributes가 interpolate돼 전달된다. 이 후 depth testing을 통해 그려야하는 픽셀을 정한다. (다른 primitive에 가려진 부분은 pixel shader로 전달되지 않는다.)
8. Pixel Shader 혹은 Fragment Shader는 interpolate된 attributes를 이용해 각 pixel의 색을 정한다.
9. Raster Output 단계에선 L2 캐쉬의 데이터를 VRAM의 Framebuffer로 옮긴다.
'배움엔 끝이없다 > 그래픽스' 카테고리의 다른 글
GPU를 위한 텍스쳐 압축 기법 (DXT, PVRTC, ETC, ASTC) (1) | 2023.11.04 |
---|---|
[Unity Shader] 간단한 Cloak 쉐이더 (1) | 2023.11.02 |
Render Hell - Book 1 (+알쓸유잡 강의 참고) 정리 (0) | 2023.09.05 |