본문으로 건너뛰기

© 2026 Molayo

Unity헤드라인2026. 05. 20. 01:40

Survival Kids의 그래픽 및 렌더링 팁

요약

Unity와 KONAMI가 협력하여 개발한 'Survival Kids'의 그래픽 및 렌더링 최적화 과정을 다룹니다. 제한된 리소스를 가진 소규모 팀이 Universal Render Pipeline(URP)을 기반으로 커스텀 Shader Graph 타겟과 Adaptive Probe Volumes(APV)를 활용해 성능과 시각적 품질 사이의 균형을 맞춘 사례를 소개합니다.

핵심 포인트

  • Universal Render Pipeline(URP)을 기반으로 커스텀 렌더 기능 및 셰이더를 구현하여 성능 효율성 확보
  • AssemblyDefinitionReferences를 활용해 URP 코어를 수정하지 않고도 프로젝트 전용 Shader Graph 타겟 추가 가능
  • 동적 조명 구현을 위해 초기에는 라이트 프로브(LightProbes)와 앰비언트 오클루전(AO)을 사용
  • Unity 6의 Adaptive Probe Volumes(APV)로 전환하여 시각적 품질과 성능 최적화 도모

이번 여름, Unity는 KONAMI와의 파트너십을 통해 자체적으로 처음부터 끝까지 개발한 게임인 협동 가족 게임 Survival Kids의 업데이트 버전을 출시했습니다. 이 게임은 최대 약 20명 규모의 작은 내부 팀에 의해 제작되었기 때문에, 팀은 여느 인디 스튜디오와 마찬가지로 제한된 리소스를 활용하여 프로젝트 범위와 출시 일정을 준수하기 위한 혁신적인 방법들을 찾아야 했습니다. 이 포스트에서는 우리가 게임의 비주얼 프레임과 렌더링(Rendering)을 어떻게 구축했는지 자세히 살펴보겠습니다.

우리는 시각적으로 흥미로운 결과물을 얻고 싶었습니다. 우리의 목표는 매우 예술적이었지만, 처음에는 어떤 종류의 기기 성능을 다루게 될지 알 수 없었기 때문에 성능 측면에서 매우 저렴하게(cheap) 만들고자 했습니다.

프로젝트의 첫 번째 단계는 단순히 시각적인 탐색이었습니다. 우리는 우리가 상상하는 아트 스타일을 보여주기 위해 아트 디오라마(Art diorama)를 사용했습니다. 그중 일부는 커스텀 그림자(Customized shadows)를 포함한 매우 스타일라이즈된(Stylized) 조명 설정입니다.

우리는 Universal Render Pipeline (URP)를 선택했는데, 이는 광범위한 기기에서 성능에 대한 훌륭한 실적을 보유하고 있으며, 게임의 비주얼 목표를 달성하기 위해 필요한 새로운 기능들을 만들기가 비교적 쉽기 때문입니다. 게임에는 주로 태양이라는 단 하나의 광원만 존재하기 때문에, 렌더링된 프레임은 Forward 모드의 기본(Vanilla) URP와 매우 유사합니다. 커스텀 그림자, 앰비언트 오클루전(Ambient occlusion), 그리고 몇 가지 다른 커스텀 렌더 기능(Custom render features)과 같이 여기저기에 몇 가지 수정 사항이 있지만, 전반적으로 화면에는 기본 URP가 구현되어 있습니다.

가장 큰 추가 사항은 조명이 계산되는 방식을 수정해야 했기 때문에 아트 디렉션의 매우 특정한 룩(Look)을 지원하기 위한 셰이더(Shaders)였습니다. 커스텀 셰이더를 만드는 것이 특별히 새로운 일은 아니지만, 우리는 누구나 기여할 수 있도록 우리만의 커스텀 Shader Graph 타겟을 작성했습니다. AssemblyDefinitionReferences를 사용함으로써 완전히 커스텀된 URP 버전을 가질 필요 없이 프로젝트 전용 Shader Graph 타겟을 추가할 수 있었습니다.

이를 통해 우리는 로컬 Shader Graph 타겟만을 사용하여 바닐라(vanilla) URP를 그대로 유지할 수 있었으며, 이는 우리 프로젝트에 매우 효과적이었습니다. 우리의 목표 중 하나는 동적 조명 (dynamic lighting)을 구현하는 것이었습니다. 즉, 조명의 색상, 강도 등을 변경할 수 있는 옵션을 원했습니다. 이는 라이트맵 (lightmaps)을 사용하여 조명 정보를 쉽게 베이크 (bake)할 수 없음을 의미했고, 결과적으로 베이크를 통해 얻을 수 있는 바운스 라이팅 (bounce lighting) / 전역 조명 (global illumination)의 세부적인 조명 효과를 놓치게 된다는 뜻이었습니다. 동적 조명 방식은 일반적으로 비용이 더 많이 들기 때문에, 우리는 높은 시각적 품질과 좋은 성능 사이의 균형을 맞추기 위한 다양한 방법을 고민해야 했습니다. 이로 인해 초기에는 라이트 프로브 (LightProbes)를 사용하고, 물체를 지면에 고정시키는 느낌을 주기 위해 앰비언트 오클루전 (Ambient Occlusion, AO)에 더 많이 의존했습니다. 이 프로젝트에서 전역 조명 (global illumination)이 매우 중요하다는 것을 알고 있었기에, 처음에는 런타임에 라이트 프로브 (LightProbes)를 업데이트하는 커스텀 솔루션을 구현했습니다. 하지만 Unity 6로 전환하면서, 팀은 어댑티브 프로브 볼륨 (Adaptive Probe Volumes, APVs)으로 전환하기를 강력히 원했습니다. APV는 우리가 직접 만들어낸 시스템과 성능 영향은 비슷하면서도 시각적 품질은 훨씬 더 뛰어났기 때문입니다. 성능이 좋으면서도 품질이 높은 '매우 좋은' 것으로 업그레이드할 수 있는 선택지가 있다면, 당연히 전환하게 됩니다.

바다는 Unity URP 데모 프로젝트인 Boat Attack을 기반으로 하되, 더 스타일라이즈드 (stylized)된 외형을 적용했습니다. 우리가 정말 하고 싶었던 것 중 하나는 섬과 물속의 다른 요소들로부터 항적 (wake)이 발생하는 것을 구현하는 것이었습니다. 이는 보통 깊이 버퍼 (depth buffer)를 사용하여 거리에 따른 해안선을 계산함으로써 구현되지만, 우리에게는 일반적인 해안선이 아니라 'Whurtle-island'가 있습니다. Whurtle-island의 경우 갑작스러운 경사가 나타나며, 특히 물 아래 잠긴 지형을 고려할 때 효과를 내기에 충분한 깊이 감쇠 (depth falloff)가 발생하지 않습니다. 우리가 생각해낸 최선의 아이디어는 부호 거리 함수 (signed distance field, SDF)를 사용하는 것이었습니다. 이는 기본적으로 객체, 즉 우리 사례에서는 해안선의 부호 거리를 인코딩하는 텍스처입니다.

이 방식을 통해 우리는 해안선으로부터 일정 거리에서 항적 (wake)을 시작할 수 있으며, 이후 사인파 (sine wave)와 몇 가지 왜곡 텍스처 (distortion textures)를 사용하여 흥미로운 외형을 부여할 수 있습니다. 최종적으로 우리는 설정된 네 가지 수위 (water heights)를 기반으로 해안선의 부호 거리 (signed distance)를 베이크 (bake)하는 에디터 (Editor) 도구를 만들었습니다. 대부분의 레벨에서 수위가 플레이어의 진행 상황에 따라 변하기 때문에, 해안선이 실제로 어디에 위치하는지 대략적으로 근사화하기 위해 이들 사이의 블렌딩 (blending)과 선형 보간 (lerping)을 수행했습니다. 우리는 해양 파도 높이 조절부터 거품 (foam), 항적 (wake), 그리고 가스틱 (caustics) 추가에 이르기까지 여러 가지 다양한 효과를 위해 이 사전 베이크된 SDF 정보를 활용했습니다.

시각적 상호작용을 위해, 플레이어, 운반 가능한 객체 (carriable objects), 도구 등 위치를 추적해야 하는 모든 대상 주변에 탑다운 (top-down) 뷰로 캡슐 (capsule)을 렌더링하여 렌더 텍스처 (RenderTexture)에 담았습니다. 이 텍스처는 플레이어의 카메라가 움직임에 따라 슬라이딩 윈도우 (sliding window) 방식을 사용하는 월드 공간 (world space) 기반입니다. 우리는 캡슐의 중심으로부터의 오프셋 (offset, 빨간색, 파란색)과 월드 공간 높이 정보 (worldspace height information, 초록색)를 생성합니다. 알파 채널 (alpha channel)에는 강도에 대한 감쇠 (falloff) 값을 저장합니다. 이는 식생의 굽힘 (vegetation bending), 수면 위의 애니메이션 리플 (animated ripples), 또는 매우 부드러운 그림자 효과를 만들기 위해 지형을 약간 어둡게 만드는 것과 같은 효과를 생성하는 다양한 셰이더 (shader)에 의해 사용됩니다.

성능 최적화를 위해 우리는 데스 프리패스 (depth prepass)를 사용했습니다. 이는 객체를 정상적으로 렌더링하기 전에 깊이 버퍼 (depth buffer)를 채워, 얼리 데스 테스트 거부 (early depth test rejection)를 통해 해당 객체들의 렌더링 비용을 줄여줍니다. 디더링된 객체 (dithered objects)는 상태와 이를 바라보는 플레이어가 누구인지에 따라 다르게 렌더링해야 하므로 커스텀 패스 (custom pass)에서 별도로 처리했습니다. 이들은 렌더러 (renderer)의 불투명 레이어 마스크 (Opaque Layer Mask)에서 제외된 다른 게임 오브젝트 (GameObject) 레이어에 속해 있어 자동으로 렌더링되지 않으며, 이는 우리가 커스텀 패스에서 이들을 렌더링해야 함을 의미합니다. 우리는 개별 객체에 값을 설정하기 위해 머티리얼 프로퍼티 블록 (MaterialPropertyBlocks)을 사용하였고, 나중에 해당 섹션들을 블러 (blur) 처리할 수 있도록 디더링된 객체들을 표시하기 위해 스텐실 (stencil)을 적용했습니다.

하지만 이는 SRP 배칭 (SRP batching)을 깨뜨리기 때문에, 사용을 제한해야 했습니다. 우리는 필요한 경우에만 MaterialPropertyBlock을 적용하고, 작업이 끝나면 이를 제거하여 객체들을 다시 배칭 가능한 상태로 복구하기로 결정했습니다. 결국, 우리는 해당 특정 레이어를 깊이 버퍼 (depth buffer)에 어떻게 렌더링할지만을 다루는 별도의 패스 (pass)를 갖게 되었습니다. 그다음, 깊이 버퍼에 스텐실 (stencil)을 적용하여 사라져가는 객체들의 일부인 픽셀들을 표시하며, 이는 나중에 안티앨리어싱 (anti-aliasing)을 수행할 때 사용됩니다. 우리 아트 스타일의 일부는 그림자의 방향을 따라 그라데이션이 있는 유색 그림자를 갖는 것이었습니다. 이를 달성하기 위해, 우리는 RenderFeature로부터 생성된 커스텀 스크린 공간 텍스처 (screenspace texture)를 사용하여 월드 공간 (world space)에서 섀도우 맵 (shadow map)을 샘플링하되, 그림자 블렌드 (shadow blend) 값을 결정하기 위해 XZ 평면을 미리 내다보도록(look ahead) 했습니다. 이는 소프트 섀도우 (soft shadows)에 사용되는 PCF 필터와 유사하지만, 한 방향으로만 작동합니다. 이것은 화면 크기의 약 4분의 1 정도인 축소된 텍스처로 렌더링되었으며, 그 후 세 가지 색상 사이에서 그림자 색상을 블렌딩했습니다. 불행히도, URP에서 제공하는 SSAO는 우리의 요구 사항에 완전히 부합하지 않았습니다. 이는 모바일 친화적인 구현이지만, 우리가 목표로 하는 비주얼을 위해서는 반경 (radius) 값을 상당히 높게 설정해야 했고, 이는 프레임 예산 (frame budget)의 상당 부분(~4ms)을 차지했습니다. 대신, 우리는 이전의 PostProcessing Stack v2 패키지에 있던 MSVAO 구현을 재사용하였으며, 더 효율적으로 만들고 우리의 그림자 색상을 통합하기 위해 약간의 변경을 가했습니다. Survival Kids는 URP에서 기대할 수 있는 표준 렌더링 패스 (Opaque, Skybox, Transparency)를 가지고 있지만, 불투명 (opaque) 패스 바로 다음에 디더링된 객체들을 처리하기 위한 추가 패스를 가지고 있습니다. 이 레이어의 지오메트리 (geometry)는 불투명 패스에서 렌더링되지 않기 때문에, 바로 이 단계에서 디더링된 지오메트리를 실제로 렌더링하게 됩니다.

또한 이 패스(pass)에서 depth equals 테스트를 수행하여, 우리가 깊이 버퍼(depth buffer)를 미리 채워둔(prefilled) 영역에서만 렌더링되도록 보장합니다. 디더링(dithered)된 오브젝트의 경우, MSVAO가 깊이 버퍼의 "구멍"들을 차폐(occlusion)로 처리하여 발생하는 아티팩트(artefacts) 때문에 해당 오브젝트들에 대한 앰비언트 오클루전 (Ambient Occlusion)을 비활성화해야 합니다. 장면이 렌더링된 후에는 안티앨리어싱 (anti-aliasing)을 적용합니다. 불행히도 디더링된 영역은 알고리즘 (SMAA)을 방해하여 시각적 아티팩트 (visual artefacts)를 유발합니다. 이를 방지하기 위해 우리는 이 영역들을 별도로 처리해야 합니다. 디더링된 영역(스텐실(stencil)로 결정됨)은 블러(blur) 처리를 하여 해당 영역에 알파 블렌드 (alpha blend) 효과를 생성하고, 디더링되지 않은 영역에 대해서만 SMAA를 처리합니다. 특정 상황에서는 이 과정이 생략되기도 하지만, 결과적으로 포스트 프로세싱 (post processing)을 위한 준비가 된 깔끔한 최종 이미지를 얻게 됩니다.

우리는 포스트 프로세싱 효과를 최대한 저렴하게 유지하기 위해 톤매핑 (Tonemapping), 블룸 (Bloom), 그리고 컬러 커렉션 (Color Correction)만을 사용했습니다. 한때는 UI 뒤의 게임 화면을 부드럽게 만들기 위해 포스트 프로세싱에서 URP의 블러 (Blur)를 사용하기도 했으나, 나중에 더 저렴한 Kawase blur RenderFeature로 교체했습니다. 우리의 UI 시스템은 UGUI를 기반으로 구축되었으며, 페이딩 (fading)을 위해 약간의 커스텀 렌더링을 사용합니다. 초기에 UI를 설정했을 때는 메뉴를 페이드 인/아웃 (fade in/out) 시켰으나, UI의 알파 (alpha) 처리 방식 때문에 몇 가지 문제가 발생했습니다. 처음에는 카메라를 통해 별도의 텍스처로 UI를 렌더링한 다음, 이를 메인 이미지로 페이드 인 시킬 수 있도록 올바르게 블릿 (blit) 했으나, 이후 별도의 카메라 전체를 사용하는 대신 RenderFeature를 사용하여 이를 달성할 수 있도록 변경했습니다.

Survival Kids 제작 과정을 심층 분석하는 저희 블로그 시리즈의 다른 회차들도 확인해 보세요: - "Survival Kids의 그래픽 및 렌더링 팁" - "Survival Kids의 레벨 레이아웃 및 지형 워크플로우" - "Survival Kids 멀티플레이어 네트워크 인프라 내부"

AI 자동 생성 콘텐츠

본 콘텐츠는 Unity Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0