100 FPS에서 500명의 NPC를 구동하는 절차적 제로 텍스처(zero-texture) 셰이더 기반 NPC 생성 시스템을 만들었습니다
요약
텍스처를 전혀 사용하지 않고 버텍스 컬러와 수학적 연산만으로 500명의 NPC를 100 FPS로 구동하는 절차적 셰이더 시스템을 소개합니다. Unity와 Blender를 활용하여 메쉬 변형과 UV 언랩 최적화를 통해 효율적인 캐릭터 생성을 구현했습니다.
핵심 포인트
- 텍스처 없이 버텍스 컬러와 셰이더만으로 NPC 외형 구현
- 블렌드 셰이프를 활용한 다양한 체형 변화 지원
- UV 언랩 최적화를 통한 셰이더 연산 효율 극대화
- 스크립트를 통한 무작위 재질 설정을 통한 다양성 확보
100 FPS로 구동되는 500명의 NPC. 참고: NavMesh surface가 제대로 베이크(bake)되지 않았습니다 ㅎㅎ
안녕하세요 여러분. 제가 만든 멋진 절차적(procedural) NPC 시스템을 자랑하고, 이것이 어떻게 작동하는지 기록해두고 싶었습니다. (참고로 이것은 단계별 가이드가 아닙니다. 이를 재현하려면 Unity, Blender, 그리고 Shader Graph에 대한 어느 정도의 경험이 필요합니다.)
-
이 장면의 모든 NPC는 두 개의 모델을 사용합니다. 남성 모델과 여성 모델입니다. 서로 다른 체형은 메쉬(mesh)의 마른 버전과 뚱뚱한 버전 사이를 블렌드(blend)하는 블렌드 셰이프(blend shapes)를 통해 구현됩니다.
-
채색은 다음과 같이 작동하는 커스텀 셰이더(custom shader)를 통해 이루어집니다. (셰이더와 UV 언랩(unwrap) 사진은 아래에 첨부됩니다.)
- 두 모델 모두에 버텍스 컬러(vertex colors)가 칠해져 있습니다. 피부 영역은 검은색, 셔츠 영역은 빨간색, 바지 영역은 초록색, 신발 영역은 파란색으로 칠해져 있습니다.
- 모델의 UV는 매우 특정한 방식으로 언랩(unwrap)됩니다. 메쉬의 다리 부분은 Blender의 Project from View 언랩 방식을 통해 언랩되며, 팔은 팔목에 심(seam)을 표시한 다음, 겨드랑이에 도달할 때까지 팔을 따라 이어지는 중간 심을 표시하고, 캐릭터의 몸통 중앙을 따라 허리에 도달할 때까지 심을 표시하는 방식으로 언랩됩니다. 한쪽 팔이 언랩되면, Blender의 UV squares 애드온을 사용하여 해당 언랩을 사각형으로 변환하는데, 이는 셰이더가 읽기 더 쉽게 만들어줍니다. 그런 다음 다른 쪽 팔에도 동일한 과정을 수행합니다. (양쪽 팔을 한꺼번에 언랩하려고 하지 마세요. 하나를 하고 나서 다른 하나를 하세요.)
- 셰이더는 먼저 버텍스 컬러를 사용하여 피부가 나타나야 할 위치를 결정함으로써 메쉬에 피부색을 적용한 다음, 버텍스 컬러를 사용하여 셔츠 색상, 바지 색상, 신발 색상을 겹쳐서 표시합니다. 이 과정에서 텍스처(texture)는 전혀 사용되지 않는다는 점을 명심하세요.
-
그 다음 반바지나 티셔츠 같은 것들을 구현하기 위해, 모델의 UV를 참조점으로 사용하여 모델 내에서 정점(vertices)이 어디에 위치하는지 결정합니다 (모델이 애니메이션을 재생하기 위해 변형될 때 깨지는 것을 방지하기 위해 모델의 오브젝트 위치(object position)는 사용하지 않습니다). 그런 다음 기본적인 수학 연산을 적용하여 특정 차단 지점(cutoff point)보다 낮은 픽셀이 있다면 피부색을 적용합니다.
-
그 후 동일한 재질(material)을 사용하면서도 다양한 재질 설정(material settings)을 가진 NPC를 생성하기 위해, 많은 재질 설정을 무작위로 변경하는 커스텀 스크립트를 활용합니다. 해당 스크립트는 이 포스트 하단에서 확인하실 수 있습니다.
전체 NPC 셰이더 (참고: 이 셰이더는 약간 버그가 있지만 매우 잘 작동합니다. 개선 방법에 대한 제안이 있다면 정말 감사하겠습니다.)
UV 맵이 어떻게 보여야 하는지와 표시해야 할 위치의 사진
버텍스 페인팅(vertex painting)이 어떻게 보여야 하는지
어쨌든 여기까지 읽어주셔서 감사합니다. 이것은 제가 기록하고 싶었던 정말 멋진 작업이기 때문입니다. 여러분은 왜 이것이 필요한지 물으실 수도 있습니다. NPC를 위해 다양한 의상을 가진 여러 개의 서로 다른 모델을 만들어 그것을 사용하면 안 될까요? 아니면 런타임(runtime)에 텍스처(textures)를 교체하면 어떨까요? 혹은 그냥 수많은 서로 다른 재질(materials)을 사용하면 어떨까요?
결국 모든 것은 성능 (performance) 문제로 귀결됩니다. 만약 수많은 서로 다른 변형의 NPC(동일한 시각적 다양성을 확보하기 위해 잠재적으로 수백 개가 필요할 수 있음)를 보유하려 한다면, 이 모든 모델은 더 많은 건물이나 집과 같은 다른 요소에 사용될 수 있는 RAM의 상당 부분을 차지하게 될 것입니다. 이 방식을 사용하면 NPC를 위해 한 번에 단 두 개의 모델만 로드되므로, 그 과정에서 엄청난 양의 리소스를 절약할 수 있습니다. 런타임 (runtime)에 텍스처 (textures)를 교체하는 것 또한 이상적이지 않은데, 왜냐하면 동일한 시각적 다양성을 달성하기 위해 NPC의 다양한 의상에 대해 잠재적으로 수백 개의 서로 다른 텍스처가 필요할 수 있으며, 이는 게임의 파일 크기를 비대하게 만들기 때문입니다. 수많은 서로 다른 재질 (materials)을 사용하는 경우에는 배칭 (batching)이 문제가 될 수 있는데, 서로 다른 재질을 가진 씬 (scene) 내의 오브젝트들은 배칭할 수 없기 때문입니다. 이 방식을 사용하면 이러한 모든 문제들이 해결됩니다. 색상이 절차적 (procedurally)으로 생성되므로 텍스처가 게임 파일 크기를 비대하게 만드는 것을 걱정할 필요가 없고, NPC를 위해 항상 두 개의 모델만 로드되므로 모델이 RAM을 차지하는 것을 걱정할 필요가 없으며, NPC가 한 번에 하나의 재질만 사용하므로 배칭 (batching)과 호환되어 드로우 콜 (draw calls)을 걱정할 필요가 없습니다. 또한 Material Property Blocks의 대안인 새로운 SRP 워크플로우 (workflow) 덕분에 여전히 엄청난 시각적 다양성을 얻을 수 있습니다. 제 생각에 이 방식은 그저 더 효율적일 뿐입니다. 대안적인 워크플로우에 대한 더 자세한 정보는 요약 (TL-DR) 아래에 있습니다.
물론 이 접근 방식에도 몇 가지 단점이 있습니다. 우선, 이 방식은 로우 폴리 (low-poly) 스타일라이즈드 (stylized) 게임을 만들려고 할 때만 작동합니다. 또한 구현하기가 약간 어렵지만 아주 어렵지는 않습니다. 무엇보다 지루한 작업에 가깝지만, 일단 작동하게 만들면 더 다양한 NPC를 만드는 데 정말 좋을 수 있습니다. 또한 메쉬 (mesh)를 버텍스 컬러 (vertex colors)로 칠하기 때문에 사용할 수 있는 채널이 4개뿐이어서, 의상에 대해 정말 창의적인 표현을 하기에는 한계가 있습니다.
요약 (TL-DR):
셰이더를 통해 NPC를 절차적 (procedurally)으로 생성하는 시스템을 만들었습니다. 이 셰이더는 메쉬 (mesh)에 칠해진 정점 색상 (vertex colors)을 사용하여 셔츠, 바지, 신발, 피부와 같은 요소들을 어디에 그릴지 결정합니다. 그런 다음 셰이더는 메쉬의 UV를 읽어 메쉬 내 정점의 위치를 파악하고, 특정 픽셀이 특정 임계값 (threshold) 아래에 있는지 확인하는 기본적인 검사를 수행하여 피부를 그려냄으로써 티셔츠나 반바지 같은 표현을 가능하게 합니다. 이 방식은 NPC가 단 두 개의 모델과 하나의 머티리얼 (material)만을 사용하기 때문에 메모리, 드로우 콜 (draw calls), 그리고 저장 공간을 절약하기 위해 채택되었습니다.
NPC 스폰을 위한 스크립트 (스크립트는 씬의 게임 오브젝트에 부착됩니다. 또한 속성 이름은 제 셰이더 그래프 (shader graph)의 속성 이름과 일치하도록 되어 있습니다. 만약 사용자의 셰이더에서도 속성 이름을 저와 동일하게 지정했다면, 이 스크립트를 그대로 복사하여 붙여넣을 수 있습니다):
또한 많은 분들이 제가 이 스크립트에서 여러 게임 오브젝트에 걸쳐 동일한 머티리얼의 다양한 속성을 허용하기 위해 머티리얼 프로퍼티 블록 (Material Property Blocks, MPB)을 사용하지 않았다는 점을 지적할 수도 있습니다. 저는 URP와 함께 SRP Batcher를 사용하고 있습니다. Unity의 문서에 따르면 SRP Batcher는 MPB와 호환되지 않으므로, 저는 더 최신 워크플로우 (workflow)를 사용하고 있습니다. MPB를 사용하는 대신, 이 스크립트에서 머티리얼의 속성을 직접 설정하고 있으며, 셰이더 그래프 내에서 이 스크립트가 변경하는 모든 속성으로 가서 스코프 (scope)를 Hybrid Per Instance로 설정했습니다. 이를 통해 SRP Batcher와 호환성을 유지하면서도 여러 게임 오브젝트에 걸쳐 다양한 머티리얼 속성을 가질 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 r/Unity3D (top/week)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기