닐로툰(nilotoon)은 왜 퍼 쉐이더를 지원하지 않을까
퍼 쉐이더를 만들자!
→ 테셀레이션삥뽕빵뽕
→ 어? 릴툰은 이거 안쓰네?
→ ...왜?
→ 그럼... 얘는 왜?
이런 맥락에서 시작된 닐로툰과 퍼 쉐이더에 대한 고찰
1. URP 구조에서 퍼 셰이더 구현이 어려운 근본적 이유
URP(Unity Universal Render Pipeline)는 경량화와 범용성에 초점을 맞춘 렌더링 파이프라인임.
때문에 다양한 커스텀 셰이더 기능을 만들 수 있도록 SRP(Scriptable Render Pipeline) 구조를 열어두었지만,
동시에 기존 Built-in 렌더 파이프라인과는 전혀 다른 렌더링 구조를 가짐.
이게 퍼 셰이더 구현에서 큰 걸림돌이 됨.
릴툰(liltoon)의 퍼 셰이더의 핵심은 “다중 셸 구조(Shell Offset)”를 쉐이더 내에서 반복해서 처리하는 것임.
기본적으로 5~16겹의 가상 메시를 GPU 내에서 복제하듯 처리하면서 각 셸마다 텍스처, 라이팅, 위치 오프셋 등을 다시 계산함.
이 작업은 전통적인 Vertex Shader 단계에서의 단순 루프 처리가 핵심임.
그런데 URP는 ShaderLab + HLSL 구조만으로 쉘 반복을 처리하기 어렵게 설계되어 있음.
대부분의 URP 셰이더는 SRP Batcher와 호환되기 위해 한 번의 버텍스 패스만 허용하며, 다중 셸 오프셋을 생성할 만큼 유연한 구조가 아님.
게다가 셸 반복이 필요한 퍼 셰이더는 다중 패스(MultiPass) 기반으로 작성해야 하는데, URP의 공식 패스 구조는 다중 패스를 매우 제한적으로 지원함.
예를 들어:
Pass
{
Name "ForwardLit"
Tags { "LightMode" = "UniversalForward" }
...
}
이렇게 하나의 LightMode만 허용되는데, 퍼 셰이더는 보통 쉘 반복 + 라이트 반복이 필요하므로, 다중 Pass가 필요한 경우가 많음.
URP에서는 이런 구조를 기본적으로 막아버림. 다중 Pass 셰이더를 쓰면 SRP Batcher도 비활성화됨.
한마디로 퍼 셰이더는 URP 구조에서 잘 안 맞는 기형적인 요구사항임.
닐로툰 개발자 입장에서는 URP의 퍼포먼스 최적화를 유지하면서도 다중 셸 기반의 퍼 렌더링 구조를 얹기가 좀 그렇지 않았을까...
닐로툰은 그림자를 Foward add 형식으로 받는게 아니라, 뎁스 텍스쳐를 통해서 그리고 있음.
퍼 셰이더의 핵심은 쉘이 바깥으로 튀어나온 것처럼 보이게 만드는 것임.
하지만 실제 메시나 버텍스가 확장된 게 아니고, Vertex Shader에서만 잠깐 이동시켰다가 바로 Rasterize하는 구조임.
즉, 물리적으로 퍼가 존재하지 않기 때문에 깊이(Depth) 버퍼에는 등록되지 않음.
예를 들어 SSAO나 Contact Shadows 같은 기능은 Depth와 Normals 텍스처를 기준으로 픽셀 간의 occlusion(가림 처리)을 계산함. 퍼 셰이더는 다중 쉘이지만 쉘의 깊이나 노멀 정보가 Depth Prepass에서 전혀 반영되지 않음.
또한 퍼가 쉘 오프셋으로 밖으로 튀어나오게 렌더링되었을 때, 이 겹겹이의 실루엣이 그림자에 반영되지 않음.
그림자에는 ‘맨 바깥 메시’만 반영되고, 나머지 퍼 셸은 무시됨. 이로 인해 퍼는 그림자에서 잘리거나 아예 안 나오는 듯한 현상이 발생함.
닐로툰은 URP 환경에서 그림자 일관성(특히 directional light나 additional light) 구현에 굉장히 공들인 구조인데, 여기에 퍼 쉘까지 반영하려면 DepthPrepass 커스터마이징, Custom Render Feature 추가, ShadowCaster Pass 재정의 등을 모두 건드려야 함. 이건 단순 셰이더 제작이 아니라 렌더링 파이프라인 자체를 통째로 수정해야 하는 레벨임.
요약하면 닐로툰의 깊이 기반 그림자 구조는 쉘 기반 퍼 렌더링과 충돌함.
이게 퍼 쉐이더를 미지원한 이유 중 가장 큰 기술적 원인으로 판단됨...
3. 테셀레이션을 사용하지 않은 이유는 뭘까?
털 쉐이더를 만드는데에는 쉘 오프셋 기반 방식 외에도,
메쉬를 삼각형 단위로 쪼개서 추가 메쉬를 뽑아내는 테셀레이션 방식을 사용하는 방법도 있음.
테셀레이션은 고품질 렌더링을 위해 매우 유용한 기술임.
실제로 AAA급 게임이나 MV, 실사 트레일러에서도 적극적으로 사용되고 있음.
따라서 닐로툰이 퍼 셰이더를 넣을 거였다면, 오히려 테셀레이션 방식이 더 자연스러웠을 수도 있음.
그런데도 끝내 테셀레이션 기반 퍼 셰이더를 넣지 않았음... 왜일까?를 GPT와 함께 고찰해보았음.
1. URP에서의 테셀레이션 지원 미비
URP는 기본적으로 테셀레이션을 공식 지원하지 않음.
Unity Shader Graph 기준으로도 2024년 현재까지 테셀레이션 노드는 존재하지 않음.
Hull, Domain 쉐이더 자체가 SRP에서 강제로 빠져 있는 상태임.
즉, URP에서 테셀레이션을 사용하려면 커스텀 HLSL + RenderFeature 확장 + 플랫폼 조건 분기 처리까지 다 해야 함.
닐로툰툰의 코드 구조는 Shader Graph 기반이 아닌 커스텀 HLSL 코드 기반이지만, 여전히 URP 내부 렌더링 레지스터와 SRP Batcher 호환을 유지하려 노력하고있음. 이 상태에서 테셀레이션을 넣으려면 구조 자체를 완전히 비켜나가야 함.
테셀레이션 기능을 넣는 순간 URP 표준 파이프라인과는 완전히 분리되며, ShadowCaster, DepthOnly, Meta Pass 등의 조정까지도 동반됨.
2. Shader Model 4.6 이상 + DX11/Metal 등 특정 환경 필요
테셀레이션은 Shader Model 4.6 이상, 그리고 DX11/12, Metal, Vulkan 이상이 필요함.
GLES2, WebGL1, 모바일 저사양 디바이스에서는 아예 컴파일조차 되지 않음.
닐로툰은 비록 고품질 셰이더이긴 하지만, GitHub에서도 "모바일에서도 돌릴 수 있도록 설계된 URP 기반 셰이더" 라고 명시함.
즉, 고사양 기기를 대상으로 한 콘텐츠도 있지만, 그렇다고 VR, WebGL, Quest, Android 저사양을 포기한 건 아님.
닐로툰의 아웃라인 구조도 UBO, VFACE 없이 SRP Batcher에 붙는 구조로 되어 있고,
다이나믹 쉐도우까지 고려한 스탠스를 취함.
테셀레이션을 넣는 순간 이러한 "범용 호환성"이 무너짐.
이는 단순히 퍼 셰이더 하나를 추가하는 비용보다 훨씬 큰 전략적 리스크로 작용했을 가능성이 큼.
3. 기능 일관성과 커스터마이징 범위 유지가 어려움
닐로툰은 단순히 셰이더 기능을 많이 넣은 것이 아니라,
"모든 기능이 서로 간섭 없이 동작하고, 커스터마이징이 용이하게 하는 것"에 큰 비중을 둔 구조임.
예를 들어 Rim Light는 Lighting Model과 분리되면서도 Shadow와 잘 섞임.
Cutout, Clip, Outline, Rim, Additional Light Override가 각각 독립적이면서도 조합 가능하게 되어 있음.
여기에 테셀레이션 기반 퍼 셰이더를 넣으면 다음 문제가 생김:
- Rim Light와 테셀레이션된 표면 간의 림 범위 충돌
- 테셀레이션 메시의 노멀 vs 원래 노멀 → 쉐도우/SSAO 연산 불일치
- 기존의 MatCap, Custom BRDF 조명 구조와 충돌
즉, 테셀레이션 기반 퍼 셰이더를 넣는 순간 전체 셰이더 구조가 이중 루트로 갈라지게 됨.
퍼 관련 파이프라인과 나머지 조명 파이프라인이 동기화되지 않으면, 디버깅 지옥 + 사용자 혼란이 발생함.
뭐 대충 이런 이유가 아닐까... 라는 고찰글.
개인적인 고찰이기 때문에 진실은 닐로캣만 안다고 하네요 삥뽕빵뽕