SKYLIGHT STUDIO

[Unity] Shader 본문

Game Development/Unity C#

[Unity] Shader

SKY_L 2024. 7. 22. 17:25

 

높은 자리에 욕심을 부리고, 무력으로만 전횡하며, 한 사람의 사리사욕을 위해 백성과 나라를 재앙에 빠뜨린다면, 책임은 더욱 무거워질 것이다. 퇴위를 서두르는 것이 오히려 자기를 보존하는 길이다. - 탕화룡, 원세개에게 경고하며.

 

쉐이더는 GPU(그래픽 처리 장치)에서 실행되는 작은 프로그램으로, 게임이나 애플리케이션에서 그래픽 렌더링을 제어하는 데 사용된다. 쉐이더는 픽셀과 버텍스의 외관을 조작할 수 있기 때문에 조명(light), 그림자(darkness), 텍스처(texture) 및 재질(materia)e등 과 같은 시각적 효과를 생성하는 데 사용된다.

 

In computer graphics, a shader is a computer program that calculates the appropriate levels of light, darkness, and color during the rendering of a 3D scene—a process known as shading. Shaders have evolved to perform a variety of specialized functions in computer graphics special effects and video post-processing, as well as general-purpose computing on graphics processing units.
컴퓨터 그래픽에서 쉐이더는 3D 장면을 렌더링하는 동안 적절한 수준의 빛, 어둠 및 색상을 계산하는 컴퓨터 프로그램으로, 이를 쉐이딩(shading)이라고 합니다. 쉐이더는 컴퓨터 그래픽 특수 효과와 비디오 후처리뿐만 아니라 그래픽 처리 장치(GPU)를 이용한 범용(general-purpose) 컴퓨팅에서도 다양한 특수 기능을 수행하도록 발전해 왔습니다.

 

위키피디아에서는 쉐이더에 대해 다음과 같이 정의하고 있다.

 

결국 쉐이더는 하나의 추상적인 개념이 아니라, 하나의 프로그램인 것임을 생각하고 이해하면 쉽다.

 

 

쉐이더가 통합된 하나의 프로그램은 아니며, 타입별로 수행하는 역할이 어느정도 상이하다. 해당 강의에서 유니티 렌더링 파이프라인에서 버텍스 쉐이더, 헐 쉐이더, 지오메트리 쉐이더 등 여러 타입들을 확인할 수 있다.

 

사용하는 쉐이더의 종류에 따라 정리해본다.

 


 

버텍스 셰이더(Vertex Shader)

버텍스 쉐이더(Vertex Shader)는 장면의 기하학을 수정하는 쉐이더 프로그램입니다. 장면의 각 버텍스마다 실행되며, 출력은 투영 좌표, 색상, 텍스처 및 프래그먼트 쉐이더로 전달되는 기타 데이터를 포함합니다. #pragma vertex [함수 이름] 지시어는 버텍스 함수의 이름을 정의하는 데 사용됩니다. - Unity Documentation

 

그래픽스 렌더링 파이프라인에서 초기 단계에서 위치하는 쉐이더. 프로그래밍 가능(Programmable) 쉐이더로 분류된다. 주로 3d 모델의 각 버텍스(번역어 : 정점)을 처리하며, 버텍스의 위치, 색상, 텍스쳐 좌표 같은 데이터들을 변환하는 역할을 한다. 또한 기본적인 조명(Lightning) 계산도 가능하다.

 

크게 변환(Transformation), 텍스쳐 좌표 설정, 컬러 변환 등을 수행한다.

 


 

여기서 변환모델 공간(Model Space, 로컬 좌표)에서 정의된 버텍스 좌표를 월드 공간(World Space, 월드 좌표)과 뷰 공간(View Space, 뷰포트 좌표), 클립 공간(Clip Space, 클립 좌표계. 투영 변환 행렬을 통해 3D 좌표를 2D 화면 좌표로 변환해 화면에 투영)으로 각각 변환하는 것. 가장 기본적인 기능이다.

 

다만 뷰포트 변환은 렌더링 파이프라인 단계에서 래스터화 단계에서 수행된다. 클립 공간의 좌표를 화면 공간(Screen Space)으로 변환해 화면에 출력시키는 것이므로 어찌 보면 당연하다.


텍스쳐 좌표 변환 또한 중요한 기능으로, 각 버텍스에 텍스처 좌표를 할당하여 프래그먼트 쉐이더가 올바른 텍스처 샘플을 참조할 수 있게 해준다. 

 

최종적으로 클립 공간으로 넘어간다.


 

헐 쉐이더(Hull Shader)

 

한국어로 번안하면 껍질 쉐이더, 내지는 덮개 쉐이더쯤 될까.

 

GPU에서 수행하는 테셀레이션 파이프라인의 첫 번째 단계라고 할 수 있는 셰이더로, 테셀레이션 제어 쉐이더(Tesselation Control Shader, TCS)라고도 한다. 이 셰이더는 입력된 패치(버텍스들의 집합)을 처리하여 테셀레이터와 도메인 쉐이더에 필요한 제어 점과 테셀레이션 팩터를 생성한다.

 

사실 이게 무슨 말인지는 나도 잘 몰라서 테셀레이션 쉐이더는 따로 파봐야 한다...

 

헐 쉐이더(HS) 단계는 테셀레이션 단계 중 하나로, 모델의 단일 표면을 효율적으로 여러 삼각형으로 분할합니다. 헐 쉐이더(HS) 단계는 입력된 패치(사각형, 삼각형, 선)에 해당하는 기하학적 패치와 패치 상수(patch constants)를 생성합니다. 헐 쉐이더는 각 패치마다 한 번씩 호출되며, 저차원 표면을 정의하는 입력 제어점을 패치를 구성하는 제어점으로 변환합니다. 또한 각 패치마다 계산을 수행하여 테셀레이터(TS) 단계와 도메인 쉐이더(DS) 단계에 필요한 데이터를 제공합니다. - Microsoft Learn

 

마이크로소프트는 헐 쉐이더는 상기와 같이 정의하고 있다.

 

여기서 제어점(Control Points)은 테셀레이션 파이프라인에서 매우 중요한 요소로, 곡선이나 표면을 정의하고 조작하는 데 사용된다.(여기서 곡선은 단순한 곡선이나 평면 같은 것이 아니라, 베지어 곡선이나 B-스플라인 같은 수학적인 개념이 가미된다) 설명하자면, 헐 쉐이더 단계에서 인풋되는 제어점은 저차원 표면을 정의하고, 이를 기반으로 다음 단계인 테셀레이션에서 고차원 표면으로 변환되는 것. 그러니까 입력된 제어점을 기반으로 패치를 정의한다는 것이다.

 

이후 설명할 도메인 쉐이더에서도 사용된다.

 


 

헐 쉐이더의 역할은 크게 2개로 나누어서 설명할 수 있다.

Hull-Main Shader : 폴리곤을 어떻게 분할할까

헐 메인 쉐이더는 입력된 패치(삼각형, 사각형)의 제어점을 처리하여, 테셀레이션을 통해 생성될 새로운 제어점을 결정하는데, 이 과정은 입력 패치를 테셀레이터가 이해할 수 있는 형태로 변환하는 것이다.

 

이 과정은 처리하는 각 기본 도형(그러니까, 패치)마다 전부 한번씩 호출되며, 입력된 제어점을 처리하여 새로운 제어점으로 변환시킨다.


 

Hull-Constant Shader: 폴리곤을 얼마나 분할할까

헐 컨스턴트 쉐이더는 각 패치의 테셀레이션 팩터를 계산한다. 테셀레이션 팩터는 패치의 각 엣지와 내부를 얼마나 세분화할지를 결정한다

 

설명하려면 테셀레이션 팩터에 대해서도 설명해야 한다.

 

테셀레이션 팩터(Tessellation Factors)는 테셀레이션 쉐이더 단계에서 입력된 패치를 얼마나 세분화할지를 결정하는 값입니다. 이 팩터는 헐 쉐이더(Hull Shader)에서 계산되어 테셀레이터(Tessellator)로 전달됩니다. 테셀레이터는 이 팩터를 기반으로 패치를 작은 삼각형이나 사각형으로 세분화합니다. - Chatgpt 4o

 

 

일종의 값(value)이며, 이 값이 헐 쉐이더에서 계산되어 테셀레이터로 전달되고 패치를 작은 삼각형으로 산산조각내는 것이다. 엣지 테셀레이션 팩터(Edge Tessellation Factor)와 내부 테셀레이션 팩터(Inside Tessellation Factor)가 따로 존재하는데, 결과론적으로 두 팩터의 값이 전부 높을 수록 각각 엣지와 패치의 내부가 더 많은 작은 프리미티브(삼각형)으로 세분화된다. 

 

여기서 엣지는 선분, 내부는 엣지들로 둘러싸인 영역이다.

 

보면 직관적으로 이해가 된다.


도메인 쉐이더

사실 전 단계인 테셀레이터도 설명하는 것이 좋긴 하지만, 그건 따로 분량을 할애하는 것이 옳을 듯 하다.

 

도메인 쉐이더는 테셀레이터(Tessellator) 단계에서 세분화된 패치의 각 점을 처리하고, 주어진 패치의 제어점과 테셀레이션 팩터를 사용하여 최종적으로 각 점의 위치를 계산하게 된다. 고해상도의 매끄러운 표면을 생성하는 데 필수적.

 


역할은 크게 세 가지로 나뉘는데,

아까도 언급했던 테셀레이터가 생성한 패치의 각 점(그러니까, 버텍스)을 처리한다.

 

테셀레이터가 생성한 세분화된 패치의 각 버텍스마다 도메인 쉐이더가 호출되는데, 각 버텍스들은 무게 중심 좌표 (Barycentric Coordinates) 또는 UV 좌표를 포함한다. 그 후 헐 쉐이더가 생성한 제어점과 테셀레이터의 출력 무게중심 좌표를 사용하여 버텍스들의 최종적인 위치를 계산하게 된다.

 

이 과정에서 무게중심 좌표 및 UV 좌표를 이용하여 각 버텍스의 위치를 보간(interpolate)해준다.

 

이떄 최종 위치를 계산할 때 써먹을 수 있도록 투영 변환(모델 공간(로컬 좌표) -> 클립 공간)을 수행. 체크해야 할 사항으로, 버텍스트는 실제적인 위치가 아니라 실제 위치가 아니라, 패치 영역 내에서 매개변수 좌표(UV 좌표)를 입력받아 처리된다.


 

그 외에도 텍스처 기반 높이 조정을 통해 표면의 세부 사항을 추가할 수 있다. 이건 디스플레이스먼트 매핑(Displacement Mapping)이라고 한다. 깔끔한 표면을 구현할 때 도메인 쉐이더가 중요한 이유이다. 이건 차후에 설명한다.

 

 


 

지오메트리 쉐이더

기하 쉐이더라고도 한다.

 

프래그먼트 쉐이더 전에서 수행되며, 입력된 프리미티브(삼각형, 선, 점 등)를 처리하고 새로운 프리미티브를 생성할 수 있게 해주는 쉐이더. 그러니까 프리미티브를 세분화하거나 생성, 혹은 새로운 속성을 부여해주는 쉐이더라고 보면 된다. 입력은 한번에 하나의 프리미티브만 받는다.

 

예를 들어서 점을 선으로 변경하거나 선을 삼각형을 변경하는 등의 기하학적인 변형을 수행하고, 입력된 프리미티브에 새로운 버텍스를 기반으로 해서 더 복잡한(Complex) 지오메트리를 생성하거나, 버텍스의 법선 벡터, 텍스쳐 좌표, 색상 등을 변경하거나... 프리미티브를 쪼물딱거리는 쉐이더.

 

생각보다 그리 중요하지는 않아 보이지만, 입자(Particle) 효과를 구현하는 데 사용되기도 하며며, 디테일 레벨 제어(LOD)에서 필수적으로 이용하는 쉐이더다 보니 없으면 안된다고도 볼 수도.


 

픽셀 쉐이더/프래그먼트 쉐이더

 

다이렉트X쪽만 하시는 분들에게 프래그먼트 쉐이더라는 이야기를 하면 가끔 갸우뚱하시는 분들도 있다. 결론적으로 말해 같은 존재이며, 픽셀 쉐이더 (Pixel Shader)는 주로 DirectX, 프래그먼트 쉐이더(Fragment Shader)는 주로 OpenGL이나 벌칸 진영에서 사용되는 용어이다.

 

앞서 래스터라이즈 단계에서 래스터라이즈된 기하학적인 데이터를 기반으로 최종적인 이미지의 각 픽셀 생각 및 속성을 결정하게 된다. 단순히 말하면 각 픽셀의 데이터를 생성하는 단계. 깊이 테스트(Z-Test), 알파 블렌딩, 텍스쳐 샘플링, 조명 처리 등의 세세한 요소들과 관련된 과업들은 전부 여기서 수행된다고 보면 된다.

 

모델이 화면에서 차지하는 픽셀의 개수만큼 실행되므로, 4K 환경과 FHD 환경의 퍼포먼스 차이가 발생하는 주요 요인 중 하나.