이 페이지에서 얻을 수 있는 내용: MetalPop Games의 소프트웨어 엔지니어인 Michelle Martin이 제공하는 다양한 모바일 장치용 게임 최적화 방법에 대한 팁을 통해 최대한 많은 잠재 플레이어에게 다가갈 수 있습니다.
MetalPop Games는 모바일 전략 게임인 Galactic Colonies를 통해 플레이어가 프레임 속도 저하나 장치 과열 없이 저사양 장치에서 거대한 도시를 건설할 수 있도록 해야 하는 과제에 직면했습니다. 멋진 비주얼과 탄탄한 성능 사이의 균형을 어떻게 찾았는지 알아보세요.
오늘날 모바일 장치가 강력하기는 하지만, 크고 멋진 게임 환경을 견고한 프레임 속도로 실행하는 것은 여전히 어렵습니다. 구형 모바일 장치의 대규모 3D 환경에서 견고한 60fps를 달성하는 것은 어려울 수 있습니다.
개발자로서 우리는 고급 휴대폰을 대상으로 하고 대부분의 플레이어가 게임을 원활하게 실행하는 데 충분한 하드웨어를 보유하고 있다고 가정할 수 있습니다. 그러나 여전히 사용 중인 오래된 장치가 많기 때문에 이로 인해 엄청난 양의 잠재적인 플레이어가 차단될 것입니다. 이들은 모두 제외하고 싶지 않은 잠재 고객입니다.
우리 게임인 Galactic Colonies에서 플레이어는 외계 행성을 식민지화하고 수많은 개별 건물로 구성된 거대한 식민지를 건설합니다. 작은 식민지에는 12개의 건물만 있을 수 있지만 큰 식민지에는 쉽게 수백 개의 건물이 있을 수 있습니다.
다음은 파이프라인을 구축하기 시작했을 때의 목표 목록입니다.
- 수많은 건물이 있는 거대한 맵 제작
- 우리는 더 저렴하거나 오래된 모바일 장치에서 빠르게 실행하고 싶습니다.
- 멋진 광원 및 그림자
- 간편하고 관리하기 쉬운 프로덕션 파이프라인
게임의 좋은 조명은 3D 모델을 멋지게 보이게 만드는 핵심입니다. Unity에서는 쉽습니다. 레벨을 설정하고 동적 조명을 배치하면 준비가 완료됩니다. 성능을 계속 관찰해야 하는 경우 모든 조명을 굽고 후처리 스택을 통해 SSAO 및 기타 눈요기를 추가하면 됩니다. 자, 배송하세요!
모바일 게임의 경우 조명을 설정하려면 좋은 트릭과 해결 방법이 필요합니다. 예를 들어, 고급 장치를 대상으로 하지 않는 한 후처리 효과를 사용하고 싶지 않을 것입니다. 마찬가지로 동적 조명으로 가득 찬 대규모 장면에서도 프레임 속도가 크게 낮아집니다.
데스크탑 PC에서는 실시간 조명 비용이 많이 들 수 있습니다. 모바일 장치의 리소스 제한은 훨씬 더 엄격하며 원하는 모든 멋진 기능을 항상 감당할 수는 없습니다.
따라서 장면에 화려한 조명을 너무 많이 사용하여 사용자의 휴대폰 배터리를 필요 이상으로 소모하고 싶지는 않을 것입니다.
계속해서 하드웨어의 한계를 넘어서면 전화기가 뜨거워지고 결과적으로 자체 보호를 위해 속도가 느려집니다. 이를 방지하려면 실시간 그림자를 투사하지 않는 모든 조명을 구울 수 있습니다.
라이트 베이킹 프로세스는 (정적) 장면에 대한 하이라이트와 그림자를 미리 계산한 다음 해당 정보를 라이트맵에 저장하는 것입니다. 그런 다음 렌더러는 모델을 밝게 또는 어둡게 만드는 위치를 파악하여 빛의 환상을 만듭니다.
이 방식으로 렌더링하면 비용이 높고 느린 조명 계산이 이미 오프라인으로 수행되어 런타임에서는 렌더러(셰이더)가 텍스처에서 결과물만을 검색하기 때문에 속도가 매우 빠릅니다.
여기서 단점은 추가 라이트맵 텍스처를 배포해야 하기 때문에 빌드의 크기가 증가하며 런타임에 추가적인 텍스처 메모리가 필요하다는 점입니다. 또한 메시가 라이트맵 UV를 필요로 하며 크기가 약간 커지기 때문에 일정 공간이 소모됩니다. 하지만 전체적으로 속도가 상당히 향상됩니다.
하지만 우리 게임에서는 게임 세계가 플레이어에 의해 실시간으로 구축되기 때문에 이것은 선택 사항이 아니었습니다. 새로운 지역이 지속적으로 발견되고, 새로운 건물이 추가되거나 기존 건물이 업그레이드된다는 사실은 어떤 종류의 효율적인 라이트 베이킹을 방해합니다. 하지만 사용자에 의해 계속해서 변화하는 동적인 세계에서는 단순히 Bake 버튼을 눌러 베이크를 진행할 수 없습니다.
따라서 고도로 모듈화된 장면을 위해 조명을 베이킹할 때 발생하는 여러 가지 문제에 직면했습니다.
Unity의 라이트 베이킹 데이터는 저장되며 장면 데이터와 직접 연결됩니다. 개별 레벨과 사전 구축된 장면, 소수의 동적 개체만 있는 경우에는 문제가 되지 않습니다. 조명을 미리 베이킹하고 완료할 수 있습니다.
분명히 이것은 레벨을 동적으로 생성할 때 작동하지 않습니다. 도시 건설 게임에서 세계는 미리 만들어지지 않습니다. 대신 무엇을 빌드할지, 어디에 빌드할지에 대한 플레이어의 결정에 따라 동적으로 즉석에서 대부분 조립됩니다. 이는 일반적으로 플레이어가 무언가를 구축하기로 결정할 때마다 프리팹을 인스턴스화하여 수행됩니다.
이 문제에 대한 유일한 해결 방법은 관련된 모든 광원 베이킹 데이터를 씬이 아닌 프리팹 안에 보관하는 것입니다. 안타깝게도 사용하고자 하는 라이트맵과 해당 좌표 및 스케일의 데이터는 프리팹으로 쉽게 복사할 수 없습니다.
광원 베이킹된 프리팹을 다루는 안정적인 파이프라인을 구축하기 위한 최선의 방법은 프리팹을 별도의 씬, 즉 여러 개의 씬에 생성하여 필요할 때 메인 게임에 불러오는 것입니다. 각 모듈식 조각은 광원 베이킹되며 필요할 때 게임에 로드됩니다.
Unity에서 라이트 베이킹이 어떻게 작동하는지 자세히 살펴보면 라이트 베이크된 메시를 렌더링하는 것이 실제로는 다른 텍스처를 적용하고 메시를 약간 밝게 하거나 어둡게 하는(때로는 색상을 지정하는) 것임을 알 수 있습니다. 필요한 것은 라이트맵 텍스처와 UV 좌표뿐입니다. 둘 다 라이트 베이킹 프로세스 중에 Unity에서 생성됩니다.
라이트 베이킹 중에 Unity는 새로운 UV 좌표 세트(라이트맵 텍스처를 가리키는)와 개별 메시에 대한 오프셋 및 스케일을 생성합니다. 조명을 다시 베이킹하면 이러한 좌표가 매번 변경됩니다.
이 문제에 대한 솔루션을 개발하려면 UV 채널의 작동 방식과 이를 가장 잘 활용하는 방법을 이해하는 것이 도움이 됩니다.
각 메시에는 여러 UV 좌표 세트(Unity에서는 UV 채널이라고 함)가 있을 수 있습니다. 대부분의 경우 서로 다른 텍스처(확산, 사양, 범프 등)가 모두 이미지의 동일한 위치에 정보를 저장하므로 하나의 UV 세트로 충분합니다.
하지만 오브젝트가 라이트맵과 같은 텍스처를 공유하여 하나의 큰 텍스처 내에서 매우 특정한 위치의 정보를 찾아야 할 경우 대체로 UV 세트를 추가하여 공유된 텍스처와 사용해야 합니다.
다중 UV 좌표의 단점은 추가 메모리를 소비한다는 것입니다. 하나가 아닌 두 개의 UV 세트를 사용하는 경우 메시의 모든 정점에 대한 UV 좌표의 양이 두 배로 늘어납니다. 이제 모든 정점은 렌더링 시 GPU에 업로드되는 두 개의 부동 소수점 숫자를 저장합니다.
Unity는 일반 라이트 베이킹 기능을 사용하여 좌표와 라이트맵을 생성합니다. 엔진은 라이트맵의 UV 좌표를 모델의 두 번째 UV 채널에 기록합니다. 모델을 풀어야 하기 때문에 기본 UV 좌표 세트를 사용할 수 없다는 점에 유의하는 것이 중요합니다.
각 측면에 동일한 텍스처를 사용하는 상자를 상상해 보세요. 상자의 개별 측면은 동일한 텍스처를 재사용하기 때문에 모두 동일한 UV 좌표를 갖습니다. 그러나 상자의 각 면이 빛과 그림자에 개별적으로 닿기 때문에 라이트맵 개체에는 작동하지 않습니다. 각 측면에는 개별 조명 데이터가 포함된 라이트맵에 자체 공간이 필요합니다. 따라서 새로운 UV 세트가 필요합니다.
그렇기 때문에 새로운 광원 베이킹된 프리팹을 설정하기 위해서는 텍스처와 좌표를 잃어버리지 않도록 저장한 다음 프리팹으로 복사하면 됩니다.
광원 베이킹이 끝나면 씬의 모든 메시를 거쳐 실행되는 스크립트로 오프셋 및 스케일 값을 적용하여 UV 좌표를 메시의 실제 UV2 채널에 작성합니다.
메시를 수정하는 코드는 비교적 간단합니다(아래 예 참조).
좀 더 구체적으로 말하자면: 이 작업은 원본이 아닌 메시의 복사본에 수행됩니다. 왜냐하면 베이킹 프로세스 중에 메시에 대한 추가 최적화를 수행할 것이기 때문입니다.
복사본은 자동으로 생성되어 프리팹에 저장되고 사용자 정의 셰이더와 새로 생성된 라이트맵이 포함된 새 재질이 할당됩니다. 그러면 원래 메시는 그대로 유지되며, 라이트 베이크된 프리팹은 즉시 사용할 수 있습니다.
이로 인해 작업 흐름이 매우 단순해졌습니다. 그래픽의 스타일과 모양을 업데이트하려면 적절한 장면을 열고 만족스러울 때까지 모든 수정을 가한 다음 자동화된 베이킹 및 복사 프로세스를 시작하면 됩니다. 이 프로세스가 완료되면 게임은 업데이트된 프리팹과 업데이트된 조명이 포함된 메시를 사용하기 시작합니다.
실제 라이트맵 텍스처는 커스텀 셰이더에 의해 추가되며, 렌더링시 라이트맵을 모델에 두 번째 조명 텍스처로 적용합니다. 셰이더는 매우 단순하며 색상 및 라이트맵을 적용하는 것 이외에는 가짜 스페큘러/광택 효과를 계산합니다.
다음은 셰이더 코드입니다. 위 이미지는 이 셰이더를 사용한 머티리얼 설정입니다.
우리의 경우에는 모든 프리팹이 설정된 4개의 서로 다른 장면이 있습니다. 우리 게임은 열대, 얼음, 사막 등과 같은 다양한 생물 군계를 특징으로 하며 이에 따라 장면을 분할했습니다.
특정 장면에 사용되는 모든 프리팹은 단일 라이트맵을 공유합니다. 이는 하나의 재료만 공유하는 프리팹에 추가로 하나의 추가 텍스처를 의미합니다. 이를 통해 한 번의 드로우 콜로 모든 모델을 정적 상태로 렌더링하고 세계의 대부분을 배치 렌더링할 수 있었습니다.
모든 타일/건물이 설정된 라이트 베이킹 장면에는 국부적인 하이라이트를 생성하기 위한 추가 광원이 있습니다. 어쨌든 모두 구워질 것이기 때문에 설정 장면에 필요한 만큼 많은 조명을 배치할 수 있습니다.
베이킹 프로세스는 필요한 모든 단계를 처리하는 사용자 정의 UI 대화상자에서 처리됩니다. 이는 다음을 보장합니다.
- 모든 메시에 올바른 머티리얼 할당
- 과정 중에 구울 필요가 없는 모든 것은 숨겨져 있습니다.
- 메시가 결합/구워집니다.
- UV가 복사되고 프리팹이 생성됩니다.
- 모든 이름이 올바르게 지정되었으며 버전 제어 시스템에서 필요한 파일이 체크아웃되었습니다.
적절한 이름의 Prefab은 메시에서 생성되므로 게임 코드가 이를 직접 로드하고 사용할 수 있습니다. 이 프로세스 중에 메타 파일도 변경되므로 프리팹의 메시에 대한 참조가 손실되지 않습니다.
이 워크플로를 통해 원하는 데로 건물을 수정하고 조명을 설정한 후 스크립트를 이용하여 나머지 과정을 처리할 수 있습니다.
다시 메인 씬으로 돌아와 게임을 실행하면 원활하게 작동됩니다. 별도의 수동 작업 및 업데이트가 필요 없습니다.
조명이 100% 사전 베이킹된 장면의 명백한 단점 중 하나는 동적 개체나 모션을 포함하기 어렵다는 것입니다. 그림자를 던지는 모든 것에는 실시간 빛과 그림자 계산이 필요하며, 물론 우리는 이를 완전히 피하고 싶습니다.
그러나 움직이는 물체가 없다면 3D 환경은 정적이고 죽은 것처럼 보일 것입니다.
물론 우리의 최우선 과제는 좋은 시각적 요소와 빠른 렌더링을 달성하는 것이기 때문에 몇 가지 제한 사항을 감수할 의향이 있었습니다. 살아있고 움직이는 우주 식민지나 도시의 느낌을 만들기 위해 실제로 움직이는 데 필요한 많은 물체는 없습니다. 그리고 이들 중 대부분은 반드시 그림자가 필요한 것은 아니거나 적어도 그림자의 부재는 언급되지 않을 것입니다.
우리는 모든 도시 빌딩 블록을 두 개의 개별 프리팹으로 분할하는 것부터 시작했습니다. 대부분의 정점, 메시의 모든 복잡한 비트를 포함하는 정적 부분과 가능한 한 적은 수의 정점을 포함하는 동적 부분입니다.
프리팹의 동적 부분은 정적 부분 위에 배치된 애니메이션 비트입니다. 그것들은 라이트 베이크되지 않았으며 우리는 매우 빠르고 저렴한 가짜 조명 셰이더를 사용하여 개체가 동적으로 조명을 받는 듯한 환상을 만들었습니다.
또한 객체에는 그림자가 없거나 동적 비트의 일부로 가짜 그림자를 만들었습니다. 우리 표면의 대부분은 평평하기 때문에 우리의 경우에는 큰 장애물이 아니었습니다.
동적 비트에는 그림자가 없지만 찾아보지 않는 한 거의 눈에 띄지 않습니다. 동적 프리팹의 조명도 가짜입니다. 실시간 조명이 전혀 없습니다.
우리가 취한 첫 번째 저렴한 지름길은 광원(태양)의 위치를 가짜 조명 셰이더에 하드코딩하는 것이었습니다. 셰이더가 월드에서 동적으로 조회하고 채워야 하는 변수가 하나 적습니다.
동적 값보다 상수로 작업하는 것이 항상 더 빠릅니다. 이를 통해 우리는 기본적인 조명, 메시의 밝은 면과 어두운 면을 얻었습니다.
좀 더 빛나게 하기 위해 동적 객체와 정적 객체 모두에 대한 셰이더에 가짜 반사광/광택 계산을 추가했습니다. 반사광 반사는 금속성 느낌을 만드는 데 도움이 되지만 표면의 곡률을 전달합니다.
반사 하이라이트는 반사의 한 형태이므로 이를 정확하게 계산하려면 카메라와 광원의 상대적인 각도가 필요합니다. 카메라가 움직이거나 회전하면 반사광이 변경됩니다. 모든 셰이더 계산에는 카메라 위치와 장면의 모든 광원에 대한 액세스가 필요합니다.
하지만 우리 게임에는 반사광에 사용하는 광원이 태양 하나만 있습니다. 우리의 경우 태양은 절대 움직이지 않으며 방향성 조명으로 간주될 수 있습니다. 하나의 조명만 사용하고 고정된 위치와 들어오는 각도를 가정하면 셰이더를 많이 단순화할 수 있습니다.
더 좋은 점은 Galactic Colonies의 카메라가 대부분의 도시 건설 게임처럼 하향식 뷰에서 장면을 보여주고 있다는 것입니다. 카메라는 약간 기울일 수 있고, 확대/축소할 수 있지만 위쪽 축을 중심으로 회전할 수는 없습니다.
전반적으로 항상 위에서 환경을 바라보고 있습니다. 값싼 반사 효과를 흉내내기 위해 우리는 카메라가 완전히 고정되어 있고 카메라와 조명 사이의 각도가 항상 같은 것처럼 가장했습니다.
이 방법으로 또 다시 셰이더에 고정된 값을 하드코딩하여 값싼 스페큘러/광택 효과를 구현할 수 있었습니다.
물론 반사광에 고정 각도를 사용하는 것은 기술적으로 올바르지 않지만 카메라 각도가 많이 변하지 않는 한 실제로 차이를 구분하는 것은 사실상 불가능합니다.
플레이어에게는 장면이 여전히 올바르게 보일 것입니다. 이것이 바로 실시간 조명의 핵심입니다.
실시간 비디오 게임에서 환경을 조명하는 것은 물리적으로 올바르게 시뮬레이션되는 것보다 시각적으로 올바르게 나타나는 것이 항상 중요합니다.
거의 모든 메시가 하나의 재질을 공유하고 라이트맵과 정점에서 많은 세부 정보가 나오기 때문에 반사 텍스처 맵을 추가하여 셰이더에 사양 값을 적용할 시기와 위치, 강도를 알려줍니다. 텍스처는 기본 UV 채널을 사용하여 액세스되므로 추가 좌표 세트가 필요하지 않습니다. 그리고 세부 사항이 많지 않기 때문에 해상도가 매우 낮아 공간을 거의 사용하지 않습니다.
버텍스 수가 적은 일부 소규모 동적 조각에 대해서는 Unity의 자동화된 동적 배칭을 활용하여 렌더링을 가속화할 수 있었습니다.
이러한 구운 그림자는 때때로 새로운 문제를 일으킬 수 있으며, 특히 상대적으로 모듈식 건물을 작업할 때 더욱 그렇습니다. 한 경우에는 플레이어가 실제 건물에 저장된 상품의 유형을 표시하는 창고를 지을 수 있었습니다.
빛으로 구운 개체 위에 빛으로 구운 개체가 있기 때문에 문제가 발생합니다. 라이트베이크-셉션!
이 문제는 또 다른 트릭을 통해 해결했습니다.
- 오브젝트가 추가되는 표면은 플랫해야 하며 기본 건물과 일치하는 특정 회색 컬러를 사용해야 함
- 이러한 단점을 보완하기 위해 더 작은 플랫한 표면에서 오브젝트를 베이크하고 약간의 오프셋을 적용하여 이를 해당 영역 위에 배치할 수 있었음
- 광원, 하이라이트, 컬러가 적용된 글로우, 그림자 모두 해당 타일에 베이크됨
이런 방식으로 프리팹을 구축하고 베이킹하면 매우 낮은 드로우 콜 수를 유지하면서 수백 개의 건물이 포함된 거대한 지도를 가질 수 있습니다. 우리의 전체 게임 세계는 어느 정도 단 하나의 머티리얼로 렌더링되었으며 UI가 게임 세계보다 더 많은 그리기 호출을 사용하는 지점에 이르렀습니다. Unity가 렌더링해야 하는 다양한 재질이 적을수록 게임 성능이 향상됩니다.
이는 세계에 파티클, 날씨 효과 및 다른 보기 좋은 요소를 추가할 수 있는 공간을 확보해 줍니다.
이 방법으로 오래된 기기를 사용하는 플레이어도 안정적으로 60fps를 유지하면서 수백 개의 건물로 이루어진 도시를 구축할 수 있습니다.