블로그 이미지
Every unexpected event is a path to learning for you. blueasa

카테고리

분류 전체보기 (2797)
Unity3D (853)
Programming (479)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (185)
협업 (61)
3DS Max (3)
Game (12)
Utility (68)
Etc (98)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (55)
Android (14)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (18)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday

[링크] https://rito15.github.io/posts/unity-mobile-optimization/

 

유니티 - 모바일 성능 최적화

프로젝트 설정

rito15.github.io

 

반응형
Posted by blueasa
, |

게임 출시를 앞두고, Android플랫폼의 심각한 발열과 낮은 프레임에 대한 최적화를 수행했다.

여러개의 프로젝트를 런칭하면서 쌓인 노하우 및 새로 공부한 지식으로 유의미한 최적화 성과를 거두었기에 최적화 방법을 공유한다.

 

먼저, 최적화 방법 공유에 앞서 최적화 대상 프로젝트의 스펙은 다음과 같다

-Unity 2021.3.8f로 개발

-URP 프로젝트

-NGUI사용

 

최적화 성과 (갤럭시 s8기준)

FPS :  20~30 -> 50~60

용량 : 400mb -> 300mb

발열 : S22에서 심각한 발열로 인해 30~40fps가 나옴 -> S22와 같은 심각한 발열 기기에서 거의 발열 없이 수십분 이상 구동 가능. 안정적인 60fps유지.

 

이와같은 성능을 유지하기 위해선 여러가지 복합적인 이유들이 있겠지만,

untiy editor의 stats기준으로 batch는 많아도 100~200수준을 잡아주는 것이 좋다

(스크립트 병목과 높은 vertex로 인한 성능 하락은 고려하지 않는다)

 

먼저 최적화에 앞서 본인 프로젝트에 대한 프로파일링을 우선 진행하길 바란다.

필자는 최적화에 Unity Profiler와 Memory Profiler(실험적) 을 사용했다

 

profiler를 통해 스크립트 병목의 원인을 추적하고, 메모리 스냅샷을 사용해 메모리 사용량을 최적화하라

 

그리고 최적화의 대상이 CPU처리인지 GPU처리인지도 파악해야 한다.

 

 

 

NGUI관련 최적화

1. UI Atlas의 depth최적화, panel을 최적화한다. 

ngui에서 가장 기본적인 것으로 동일한 panel을 사용하는 widget들의 depth를 정리하여 최적화 해주어야 한다.

무분별한 panel의 사용으로 동일한 atlas를 사용하는 widget이 쪼개져 중복적인 draw call을 야기하지 않도록 해야한다.

 

ngui에서 A와 B아틀라스 2개를 통해 UI를 구성한다면 흔이 2의 draw call을 예상할 것 이다.

하지만 만약 depth가 A A B A BB이런 식으로 ui를 배치한다면 draw call은 4가 된다

2의 draw call을 얻기 위해선 A A A B B B B와 같이 atlas에 따른 ui depth를 정리해주어야 한다.

 

하나의 atlas로 draw되다가 다른 atals를 만나면 중간에 batching이 끊기게 된다.

 

2. 동일한 atlas를 사용하는 widget은 단일 panel에서 최대한 순서를 정렬하여 처리해준다.

필자의 경우 ui atlas를 back / icon / overay / font 이렇게 4가지로 나누어, 정리했으며 

ui가 아무리 많아도 batch는 최대 4만 유지하도록 해주었다.

draw call의 병합 규칙은 두가지가 있는데, 하나는 material, texture, shader가 모두 같다는 것이고 다른 하나는 widget의 레이어가 같거나 인접해야 한다는 것이다.

 

3. NGUI에서 Transform의 변화, 생성, 삭제가 일어나는 객체를 조심해라

NGUI에서는 Panel에 속한 widget의 Transform이 변화(생성/삭제 포함) 하면 해당 panel에 있는 모든 widget을 다시 rendering한다. 

즉, Panel에 연출을 위한 widget이 tween position, scale등을 한다면 매 lateUpdate에 Panel은 drawcallfill을 다시 호출하는 것이다.

NGUI Widget의 Transform이 정적이지 않거나, 생성/삭제가 자주 일어나는 객체라면 완전히 다른 panel의 하위 객체로 두어 격리해두어야 한다.

즉, NGUI에서는 한 panel에 속한 widget의 상태에 변화가 생기면 해당 panel의 모든 atlas를 다시 렌더링해주게끔 되어있다.

 

3-1. 특히 NGUI의 UI Label을 조심해라

UI Label로 데미지 등을 처리할 경우 데미지 연출을 위해 UI Label이 매프레임 tweening하거나 animation을 재생할 수 있다. 이 경우 UI Label은 엄청난 성능 병목을 일으킨다.

매 프레임 움직이는 UILabel이 속한 panel의 draw fill/fill shadow/draw call fill을 야기시킨다.

특히 UI Label에 쉐도우나, 아웃라인 등이 들어가있다면 매프레임 Transform의 변화에 따라 Label을 다시 렌더링 해주게 된다.

이를 방지하기 위해 데미지 폰트 등은 이미지 폰트를 사용하는 것을 추천한다.

 

4. panel의 static상태 활성화

panel로 구성한 ui에 있는 모든 객체가 정적인 것이 보장된다면, panel의 static옵션을 활성화해주면 좋다.

ngui객체도 기본적으로 mono객체이며, static객체가 아니면 모든 ui객체에서 update/lateUpdate가 호출되기에 성능 병목이 발생한다.

정적인 객체의 경우 3번 방법을 참고하여 동적인 객체만 관리하는 panel등으로 격리해주자

 

 

그 외 Unity공통 최적화

1. GC를 줄이자

BinaryFormatter의 Serialize는 엄청난 양의 GC를 야기하며, 디바이스를 freeze시킨다. 이러한 동작은 매프레임 해주지 말고, 스케쥴러를 만들어 처리해주자

 

BigInteger.Parse(string)과 같은 value copy를 야기하는 처리를 피해주자

 

특정 타이밍에 GC.Collect를 수동으로 호출해주자

 

2. URP Setting의 Post Processing의 fast sRGB/Linear Conversions로 gamma공간을 사용하고 있다면 gpu의 부담을 덜어주자

 

3.  SustainedPerformanceMode를 활성화해 지속가능한 성능 옵션을 켜주어, 발열을 방지하자 (이로 인해 기본적인 fps의 하락이 있을 수 있음)

 

4. GPU의 병목/부하가 생기는 Project Setting의 옵션은 비활성화 하자 (Compute Skinning, Graphics Jobs...등)

 

5. Static Batching과 Dynamic batcing을 적극 활용하자

 

6. Texture compression format : ASTC, normal map encoding : DXT5nm-style

 

7. Screen.SetResoultion을 통해 화면 해상도를 지정해주자 (HD~FHD). 이를 해주지 않을 경우 기기의 해상도에 따라 렌더링되기 때문에 발열과 배터리소모의 원인이된다.

 

8. Target Framerate를 설정해주자 (60fps). 만약 프레임상한이 없다면 상위 기기에선 비정상적으로 높은 fps가 발생할 수 있으며 이는 발열/배터리소모의 원인이 될 수 있다.

 

9. realtime shadow는 최대한 피하고, 눈속임을 통해 그림자를 표시할 수 있는 방법을 찾자

quad를 통해 그림자를 표현하고, 이 quad를 dynamic batcing하는 등이 방법이다.

 

10. 가능한 많은 object pooling을 해주자. 

또한 NGUI객체의 경우 객체를 pool에서 회수/반환할 때 gameObject.SetActive보단 화면에 rendering되지 않는 먼 영역으로 좌표를 이동시켜 주는것이 좋은 선택지일 수 있다.

NGUI객체는 오브젝트가 enable/disable될 때 많은 병목을 일으키는 처리를 수행한다

 

11. 거리에 따른 최적화 방법을 숙지하라

Generate mipmap은 기본적을 texture의 용량을 1.3배 정도 부풀린다.

단 한 화면에 render되는 객체가 많을 경우 이는 한 화면의 메모리 부하를 줄여주므로 적극 활용해야한다.

 

또한 오브젝트에 LOD level을 세팅하면 거리에 따라 3D오브젝트의 렌더링 부하를 줄일 수 있지만 용량이 추가적으로 들어가는 것을 유의해야 한다.

 

Aniso Level을 주의해라. Aniso Level은 오브젝트를 그릴 때 단일 텍스쳐라도 멀어지면 흐리게 해주는 효과이다.

Aniso Level이 높을수록 선명해지겠지만 ,처리비용이 많이 든다.

프로젝트 세팅에서 확인하라 (기본값은 Forced On이다)

 

12. Asset의 Read/Write옵션을 주의하라

이 옵션은 기본적으로 비활성화지만 활성화 시 GPU메모리 뿐만 아니라 메인 메모리에도 복사되므로 소비량이 2배로 증가한다.

꼭 필요한 오브젝트의 경우만 식별하여 사용할 것

 

13. 파티클의 shader/material에 따라 render order를 조정해주어라.

NGUI와 같은 개념으로, 파티클에서 사용되는 shader/material도 render order를 조정해주어 batching되도록 해주자

 

14. Resources.Load를 매니징하자

디바이스로 부터 불러온 자원의 경우 사용 빈도에 따라 GC가 발생하지 않도록 상시 메모리에서 들고있는 등의 방법으로 최적화 해줄 수 있다.

 

15. Debug.Log를 주의해라

런타임 빌드 시 프로젝트 세팅에서 로깅 옵션을 완전히 비활성화 하거나, 스크립트에서 define으로 처리를 격리해주어라.

 

Unity Crash관련 팁

이 항목은 필자가 서비스중인 게임의 크래시 문제를 해결한 경험으로 작성되었기에, 지극히 주관적입니다.

 

1. Android환경에서 Vulkan API는 아직 불안정하다. Vulkan내부에서 native crash를 야기할 수 있다. (필자는 opengles 3을 권장)

 

2. Multithread rendering과 compute skinning도 native crash를 야기할 수 있다. 

 

3. 빈번한 Scene Load는 잦은 크래시를 야기할 수 있다.

맵의 전환 등으로 Scene이 load될 때 전 scene의 unload와 새로운 scene의 load중 스크립트가 unload된 scene의 객체 혹은 아직 preload상태의 scene에 접근할 경우 크래시가 발생한다.

 

추가적으로, unity의 scene 로드 구조는 순간적으로 폭발적인 메모리 사용량을 불러올 수 있다.

A장면에서 B장면으로 넘어갈 때 A장면과 B장면이 동시에 메모리에 올라가있는 순간이 존재하며 이 때 메모리 부족으로 크래시가 발생할 수 있다.

 

A장면에서 B장면으로 넘길 때 빈 장면(C)을 넣어 A장면의 메모리가 모두 해제되고, GC가 Collect되게 한 후 B장면을 로드해주는 방식은 아주 유용하다

 

4. Strip Engine Code옵션은 무조건 끄자

압축된 스크립트 dll을 참조하는 순간 native crash가 발생한다

 

 

[출처] https://gall.dcinside.com/mgallery/board/view/?id=game_dev&no=124306

 

최적화 노하우 공유 - 인디 게임 개발 마이너 갤러리

개인적으로 개발관련해서 메모용으로 사용하는 티스토리 블로그에 작성했다가,꽤 많은 분들에게 도움이 될 것 같아 내용만 똑같이 복붙하여 왔습니다.다들 고생하세요.-----게임 출시를 앞두고,

gall.dcinside.com

 

반응형
Posted by blueasa
, |

Unity 2021.3.33f1

----

 

앱 최초 실행 시, 최적화를 위해 그래픽 품질 관련 자동 설정하는 기능을 찾아보다 적용하고 올려 둠.

 

참조한 글을 보니 기본적으로 유니티 SystemInfo에서 지원하는 하드웨어 스펙을 보고 판단해서 Quality Level을 메기고 있다.

해당 레벨은 Unity의 UnityEngine.QualityLevel을 참조하고 있어서 대응 하는 enum을 추가(eQualityLevel)하고,

60프레임을 사용하기 위해 해당 부분 값만 수정했다.

 

필요할 때 AutoChooseQualityLevel()을 호출하고, 반환 받은 eQualityLevel 값에 따라 원하는 설정을 적용해주면 된다.

/// <summary>
/// UnityEngine.QualityLevel에 대응하는 enum
/// </summary>
public enum eQualityLevel : int 
{
    Fastest,
    Fast,
    Simple,
    Good,       // [참고] PC(i7-10700/64GB/RTX3700)에서 Good 나옴
    Beautiful,
    Fantastic
}

private eQualityLevel AutoChooseQualityLevel()
{
    Debug.Assert(Enum.GetNames(typeof(QualityLevel)).Length == Enum.GetNames(typeof(eQualityLevel)).Length, "Please update eQualityLevel to the new quality levels.");

	var shaderLevel = SystemInfo.graphicsShaderLevel;
    var cpus = SystemInfo.processorCount;
    var vram = SystemInfo.graphicsMemorySize;
    var fillrate = 0;

    // ShaderLevel
    if (shaderLevel < 10)
    	fillrate = 1000;
    else if (shaderLevel < 20)
    	fillrate = 1300;
    else if (shaderLevel < 30)
    	fillrate = 2000;
    else
    	fillrate = 3000;
    // CPU Count
    if (6 <= cpus)
    	fillrate *= 3;
    else if (3 <= cpus)
    	fillrate *= 2;
    // VRam
    if (512 <= vram)
    	fillrate *= 2;
    else if (vram <= 128)
    	fillrate /= 2;
        
    var resx = Screen.width;
    var resy = Screen.height;
    var target_fps = 60.0f;		// 현재 게임의 타겟 프레임에 맞게 설정
    var fillneed = (resx * resy + 400f * 300f) * (target_fps / 1000000.0f);
    // Change the values in levelmult to match the relative fill rate
    // requirements for each quality level.
    var levelmult = new float[] { 5.0f, 30.0f, 80.0f, 130.0f, 200.0f, 320.0f };

    const int max_quality = (int)eQualityLevel.Fantastic;
    var level = 0;
    while (level < max_quality && fillneed * levelmult[level + 1] < fillrate)
        ++level;

    var quality = (eQualityLevel)level;
    Debug.Log(string.Format("{0}x{1} need {2} has {3} = {4} level", resx, resy, fillneed, fillrate, quality.ToString()));

    return quality;
}

 

 

P.s. 참조 글 중에 에셋도 적혀있긴한데 사서 써보진 않음.

 

 

[참조] https://stackoverflow.com/questions/20978106/automatic-quality-settings/20978462#20978462

 

Automatic quality settings

a lot of apps that I see nowadays(Android) instead of having an options screen to select the graphical level, it just automatically does it, I know this is kind of a vast question, but how can I do...

stackoverflow.com

 

[참조] https://discussions.unity.com/t/auto-detect-quality-settings/24351

 

auto detect quality settings

is there a way that unity can auto detect client GPU/CPU capability and performance to set best quality settings?

discussions.unity.com

 

[에셋] https://assetstore.unity.com/packages/tools/integration/auto-quality-hardware-assessment-tool-aqhat-74134

 

Auto Quality Hardware Assessment Tool - AQHAT | 기능 통합 | Unity Asset Store

Use the Auto Quality Hardware Assessment Tool - AQHAT from Eager Amoeba® on your next project. Find this integration tool & more on the Unity Asset Store.

assetstore.unity.com

 

반응형
Posted by blueasa
, |

Audio Import 시, 원하는 최적화 관련 설정 자동 셋팅하도록 소스 정리 함.

(AudioCompressionFormat은 심플하게 Vobis / Quality 40으로 통일함.)

 

using System.IO;
using UnityEditor;
using UnityEngine;

public class AssetPostprocessor_Audio : AssetPostprocessor
{
    /*
     * 오디오클립 파일 경로
     * Resources - Sound ┬ BGM - MainBGM
     *                   ├ InGame - SFX
     *                   └ SoundEffect ┬ OutGame
     *                                 ├ UI
     *                                 └ Voice
     * Resources_AssetBundles ┬ sound ┬ sound_effect
     *                        │       ├ sound_main_bgm
     *                        │       ├ sound_sub_bgm
     *                        │       └ sound_voice
     *                        ├ sound_scenario
     *                        └ sound_scenario_StreamingAssets
     */
    void OnPreprocessAudio()
    {
        bool bDefaultSettingChanged = false;
        AudioImporter audioImporter = assetImporter as AudioImporter;
        AudioImporterSampleSettings defaultSampleSettings = audioImporter.defaultSampleSettings;
        AudioImporterSampleSettings iosSampleSettings = audioImporter.GetOverrideSampleSettings("iOS");
        bool biOSSettingChanged = false;

        UnityEditor.SerializedObject serializedObject = new UnityEditor.SerializedObject(audioImporter);
        UnityEditor.SerializedProperty normalize = serializedObject.FindProperty("m_Normalize");

        string strPath = audioImporter.assetPath;
        Debug.LogFormat("[audioImporter.assetPath] {0}", strPath);

        // Folder Name
        if (strPath.StartsWith("Assets/Resources/Sound/BGM"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = true;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.Streaming;
            audioImporter.preloadAudioData = false;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            //iosSampleSettings.loadType = AudioClipLoadType.Streaming;
            //iosSampleSettings.compressionFormat = AudioCompressionFormat.MP3;
            //iosSettingChanged = true;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources/Sound/InGame/SFX"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources/Sound/SoundEffect/OutGame"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources/Sound/SoundEffect/UI"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources/Sound/SoundEffect/Voice"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.CompressedInMemory;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound/sound_effect"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound/sound_main_bgm"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = true;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.Streaming;
            audioImporter.preloadAudioData = false;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound/sound_sub_bgm"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = true;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.Streaming;
            audioImporter.preloadAudioData = false;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound/sound_voice"))
        {
            audioImporter.forceToMono = true;
            normalize.boolValue = false;
            audioImporter.loadInBackground = false;
            //audioImporter.ambisonic = false;

            defaultSampleSettings.loadType = AudioClipLoadType.CompressedInMemory;
            audioImporter.preloadAudioData = true;
            defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
            defaultSampleSettings.quality = 0.4f;   // 40
            defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

            bDefaultSettingChanged = true;
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound_scenario"))
        {
            // File Name
            if (strPath.Contains("/bg_"))  // BGM
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = true;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.Streaming;
                audioImporter.preloadAudioData = false;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
            else if(strPath.Contains("/se_"))  // SFX
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = false;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
                audioImporter.preloadAudioData = true;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
            else    // Etc(SFX?)
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = false;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
                audioImporter.preloadAudioData = true;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
        }
        else if (strPath.StartsWith("Assets/Resources_AssetBundles/sound_scenario_StreamingAssets"))
        {
            // File Name
            if (strPath.Contains("/bg_"))  // BGM(
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = true;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.Streaming;
                audioImporter.preloadAudioData = false;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
            else if (strPath.Contains("/se_"))  // SFX
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = false;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
                audioImporter.preloadAudioData = true;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
            else    // Etc(SFX?)
            {
                audioImporter.forceToMono = true;
                normalize.boolValue = false;
                audioImporter.loadInBackground = false;
                //audioImporter.ambisonic = false;

                defaultSampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
                audioImporter.preloadAudioData = true;
                defaultSampleSettings.compressionFormat = AudioCompressionFormat.Vorbis;
                defaultSampleSettings.quality = 0.4f;   // 40
                defaultSampleSettings.sampleRateSetting = AudioSampleRateSetting.OptimizeSampleRate;

                bDefaultSettingChanged = true;
            }
        }

        try
        {
            // 수정됐으면 Save
            if (true == bDefaultSettingChanged)
            {
                audioImporter.defaultSampleSettings = defaultSampleSettings;
                serializedObject.ApplyModifiedProperties();
                if (true == biOSSettingChanged)
                {
                    audioImporter.SetOverrideSampleSettings("iOS", iosSampleSettings);
                }
                audioImporter.SaveAndReimport();

                EditorUtility.SetDirty(audioImporter);

                Debug.LogFormat("[{0}] has been imported to [{1}]\n[Force To Mono] {2}\n[Normalize] {3}\n[Load In Background] {4}\n[Load Type] {5}\n[Compression Format] Default: {6}, iOS: {7}\n[Quality] {8}\n[Sample Rate Setting] {9}",
                                Path.GetFileName(strPath),
                                Path.GetDirectoryName(strPath),
                                audioImporter.forceToMono.ToString(),
                                normalize.boolValue,
                                audioImporter.loadInBackground,
                                defaultSampleSettings.loadType.ToString(),
                                defaultSampleSettings.compressionFormat.ToString(),
                                iosSampleSettings.compressionFormat.ToString(),
                                (defaultSampleSettings.quality * 100f),
                                defaultSampleSettings.sampleRateSetting.ToString()
                                );
            }

            // OnPreprocessAudio() 에서는 Refresh 안함
            //AssetDatabase.Refresh();
        }
        catch (System.Exception e)
        {
            Debug.LogError(e);
        }
    }

    // 파일이 이동한 경우 다시 임포트.
    static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        try
        {
            foreach (string movedAsset in movedAssets)
            {
                if (movedAsset.StartsWith("Assets/Resources/Sound")
                    || movedAsset.StartsWith("Assets/Resources_AssetBundles/sound")
                    || movedAsset.StartsWith("Assets/Resources_AssetBundles/sound_scenario")
                    || movedAsset.StartsWith("Assets/Resources_AssetBundles/sound_scenario_StreamingAssets")
                    )
                {
                    AssetDatabase.ImportAsset(movedAsset);
                }
            }
        }
        catch (System.Exception e)
        {
            Debug.LogError(e);
        }
    }
}

 

[참조]

https://overworks.github.io/unity/2018/09/22/unity-audio-clip-import-guide.html

https://jwidaegi.blogspot.com/2019/07/unity-sound.html?sc=1680659862098#c4452839809969885205

https://www.jianshu.com/p/b389936ff1ad

https://blog.csdn.net/hururu20120502/article/details/121425411

https://drehzr.tistory.com/1016

 

 

반응형
Posted by blueasa
, |

모바일 게임을 제작하다 보면, PC-에디터에서는 잘 나오던 게임 사운드가 핸드폰에서는 밀려 나오는 경우를 종종 겪을 수 있다.

이런 상황을 해결하기 위한 설정값을 기록해 둔다.
출처 : https://docs.unity3d.com/kr/current/Manual/class-AudioClip.html

프로젝트 세팅

Edit > Project Settings > Audio에서 DSP Buffer Size Best latency로 설정
- Best Performance : 성능이 우선 되어 출력 시 지연이 발생할 수 있음
- Best latency : 지연이 발생하지 않는 것을 우선시하여 출력 품질이 저하될 수 있음


리소스 타입별 세팅

배경음 1
- Force To Mono : 언 체크(퀄리티에 따라 가변)
- Load In Background : 체크
- Load Type : Streaming
- Preload Audio Data : 언 체크
- Compression Format : Vobis (100%)

배경음 2
- Force To Mono : 언 체크(퀄리티에 따라 가변)
- Load In Background : 체크
- Load Type : Compressed In Memory
- Preload Audio Data : 언 체크
- Compression Format : Vobis (70%)

효과음 : 작은 크기의 빈번한 출력 
- Force To Mono : 체크
- Load In Background : 언 체크
- Load Type : Decompress On Load
- Preload Audio Data : 체크
- Compression Format :  PCM

긴 효과음(보이스) : 중간 크기의 빈번한 출력
- Force To Mono : 체크
- Load In Background : 언 체크
- Load Type : Compressed In Memory
- Preload Audio Data : 체크
- Compression Format :  ADPCM

작은 크기의 가끔 발생하는 Sound
- Force To Mono : 체크
- Load In Background : 언 체크
- Load Type : Compressed In Memory
- Preload Audio Data : 언 체크
- Compression Format :  ADPCM

중간 크기의 가끔 발생하는 Sound
- Force To Mono : 체크
- Load In Background : 언체크
- Load Type : Compressed In Memory
- Preload Audio Data : 언 체크
- Compression Format :  Vobis (70%)


개별 리소스 세팅 정보

Force To Mono(모노 강제조정)
- 스테레오를 모노로 강제 조정
- 모바일이고 최적화를 중시할 경우 설정(체크) 함

Load In Background(지연된 로딩)
- 체크 시 출력 타이밍을 엄격히 지키지 않고, 느긋하게 백그라운드에서 로드
- 따라서 배경음악일 경우 사용 FX 사운드의 경우 체크 해제


Load Type
- Decompress On Load
 실행과 동시에 압축을 해제
 작은 사이즈의 FX 사운드에 유용
 많은 메모리 점유 CPU는 적게 사용

- Compressed In Memory
 메모리에 압축 상태로 저장, 실행 시 압축을 해제하여 재생
 약간의 성능상 오버헤드를 발생시킴
 보이스 사운드 등에 사용

- Streaming
 저장소에 위치한 오디오를 실시간으로 읽어냄.
 보통 배경음악에서 사용


Preload Audio Data
- 씬이 로딩될 때 씬에서 사용하는 모든 오디오 클립을 미리 로드
- 언체크시 플레이시 로드 하기에 랙 발생 됨


Compression Format
- PCM
 최고품질 / 용량 큼 / 작은 파일 크기에 적합 / FX 사운드
 Load Type은 Decompress On Load로 하자
 즉시 재생해야 하는 매우 짧은 효과음

- ADPCM
 중간 품질 / 용량 중간
 PCM대비 3.5배의 압축비, 노이즈가 포함됨
 총격 소리와 같이 무압축에 가까운 품질 까지는 필요없지만,
 지연시간 없이 자주 반복 재생 되야 하는 경우 적절

- Vobis
 최저품질 / 용량 적음 / 배경음에 적합
 압축률 설정이 가능(보통 70%로 설정)
 지연 재생 되어도 무방한 일반적인 배경음

 

 

[출처] https://jwidaegi.blogspot.com/2019/07/unity-sound.html

 

Unity Sound 설정

모바일 게임을 제작하다 보면, PC-에디터에서는 잘 나오던 게임 사운드가 핸드폰에서는 밀려 나오는 경우를 종종 겪을 수 있다. 이런 상황을 해결하기 위한 설정값을 기록해 둔다. 출처 :  https://

jwidaegi.blogspot.com

 

반응형
Posted by blueasa
, |

[링크] https://ijemin.com/blog/%EC%9C%A0%EB%8B%88%ED%8B%B0-2d-%EA%B2%8C%EC%9E%84-%EB%B9%8C%EB%93%9C-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%8C%81/

 

유니티 (Unity) 2D 모바일 게임 최적화 팁 & 체크리스트 – I_Jemin

업데이트 2018/02/10 가독성 개선 2017/12/13 가독성 개선 2017/6/26 레퍼런스 추가 로깅/ GC, 필수 작업/ 퀄리티 세팅 추가 2017/5/26 유니티 5.6에 맞추어 갱신 레퍼런스 https://divillysausages.com/2016/01/21/performanc

ijemin.com

 

반응형
Posted by blueasa
, |

Integrated Success 팀은 유니티 고객들이 복잡한 기술적 문제를 해결할 수 있도록 지원합니다. 유니티의 선임 소프트웨어 엔지니어로 구성된 이 팀과 함께 모바일 게임 최적화에 관한 전문적인 지식을 공유하는 자리를 마련했습니다.

유니티의 엔진 소스 코드를 완벽하게 파악하고 있는 Accelerate Solutions 팀은 Unity 엔진을 최대한 활용할 수 있도록 수많은 고객을 지원합니다. 팀은 크리에이터 프로젝트를 심도 있게 분석하여 속도, 안정성, 효율성 등을 향상시키기 위해 최적화할 부분을 파악합니다.

모바일 게임 최적화에 관한 인사이트를 공유하기 시작하면서, 원래 계획한 하나의 블로그 포스팅에 담기에는 너무나 방대한 정보가 있다는 사실을 알게 되었습니다. 따라서 이 방대한 지식을 한 권의 전자책(여기에서 다운로드 가능)과 75가지 이상의 실용적인 팁을 담은 블로그 포스팅 시리즈를 통해 제공하기로 했습니다.

이번 시리즈의 두 번째 포스팅에서는 UI, 물리, 오디오 설정을 통해 성능을 개선하는 방법을 자세히 살펴봅니다. 이전 포스팅에서는 프로파일링, 메모리, 코드 아키텍처에 대해 다뤘으며, 다음 포스팅에서는 에셋, 프로젝트 구성, 그래픽스에 대해 다룰 예정입니다. 

시리즈 전체 내용을 지금 바로 확인하고 싶다면 무료로 전자책을 다운로드하세요.

그럼 시작하겠습니다.

물리

Unity의 빌트인 물리(Nvidia PhysX)를 모바일에서 사용하려면 많은 리소스가 소요될 수 있지만, 다음 팁을 참고하면 초당 프레임 수를 늘릴 수 있습니다.

설정 최적화

가능하다면 항상 PlayerSettings에서 Prebake Collision Meshes를 선택합니다.

 
Prebake Collision Meshes 활성화확장

가능하다면 항상 물리 설정(Project Settings > Physics)을 편집하여 Layer Collision Matrix를 단순화합니다. 

Auto Sync Transforms를 비활성화하고 Reuse Collision Callbacks를 활성화합니다.

 
물리 프로젝트 설정을 수정하여 성능 향상확장
 
프로파일러의 Physics 모듈을 살펴 성능 문제 확인확장
콜라이더 단순화

메시 콜라이더는 많은 리소스를 요합니다. 복잡한 메시 콜라이더를 기본 또는 단순화된 메시 콜라이더로 대체하여 원래 모양을 대략적으로 표현하세요.

 
콜라이더에 기본 또는 단순화된 메시 사용확장
물리 메서드를 사용하여 리지드바디 이동

MovePosition 또는 AddForce와 같은 클래스 메서드를 사용하면 Rididbody 오브젝트를 이동할 수 있습니다. 트랜스폼 컴포넌트를 직접 변환하면 물리 월드에서 다시 계산하게 되어, 복잡한 씬의 경우 리소스를 많이 소모합니다. Update 대신 FixedUpdate에서 물리 바디를 이동시킵니다.

Fixed Timestep 고정

Project Settings에서 Fixed Timestep의 기본값은 0.02(50Hz)입니다. 이를 목표 프레임 속도와 일치하도록 변경합니다(예: 30fps는 0.03). 

만약 런타임에서 프레임 속도가 하락하면 Unity가 프레임당 FixedUpdate를 여러 번 호출하게 되어 물리가 많이 사용된 콘텐츠에서 CPU 성능 문제를 유발할 수 있습니다.

Maximum Allowed Timestep은 프레임 속도가 하락하는 경우 물리 계산 및 FixedUpdate 이벤트가 사용할 수 있는 시간의 양을 제한합니다. 이 값을 낮추면 성능 문제 발생 시 물리 및 애니메이션이 느려지도록 하여 프레임 속도에 미치는 영향을 줄일 수 있습니다.

 
Fixed Timestep을 목표 프레임 속도와 일치하도록 수정하고 Maximum Allowed Timestep를 낮춰 성능 결함 방지확장
Physics Debugger를 통한 시각화

Physics Debug 창(Windows > Analysis > Physics Debugger)을 사용하면 콜라이더나 불일치로 인한 문제를 해결할 수 있습니다. 이 창은 서로 충돌할 수 있는 게임 오브젝트를 색으로 구별하여 표시합니다.

 
물리 오브젝트의 상호 작용을 시각화하는 Physics Debug 툴확장

자세한 내용은 Unity 기술 자료의 Physics Debug Visualization을 참조하세요.

사용자 인터페이스

UGUI(Unity UI)는 성능 문제의 원인이 되는 경우가 많습니다. Canvas 컴포넌트는 UI 요소에 대한 메시를 생성 및 업데이트하고 GPU에 드로우 콜을 보냅니다. 이러한 기능은 리소스를 많이 소모할 수 있으므로 UGUI를 사용할 때 다음 사항을 기억하세요.

Canvas 나누기

수천 개의 요소가 포함된 하나의 대형 Canvas에서는 UI 요소를 하나만 업데이트해도 전체 Canvas가 강제로 업데이트되므로 CPU 스파이크가 발생할 수 있습니다. 

다수의 Canvas를 지원하는 UGUI의 기능을 활용하세요. 새로 고침해야 하는 빈도에 따라 UI 요소를 나눕니다. 정적 UI 요소는 별도의 Canvas에 두고, 동시에 업데이트되는 동적 요소는 보다 작은 하위 Canvas에 둡니다.

각 Canvas 내의 모든 UI 요소가 동일한 Z 값, 머티리얼, 텍스처를 갖도록 해야 합니다.

보이지 않는 UI 요소 숨기기

게임에서 간헐적으로만 나타나는 UI 요소(예: 캐릭터가 대미지를 입었을 때 나타나는 체력 표시줄)가 있을 수 있습니다. 보이지 않는 UI 요소가 활성화된 경우에도 드로우 콜을 사용할 수 있습니다. 보이지 않는 UI 컴포넌트를 명시적으로 비활성화하고 필요할 때 다시 활성화합니다.

Canvas의 가시성만 해제하면 되는 경우 게임 오브젝트 대신 Canvas 컴포넌트를 비활성화합니다. 이렇게 하면 메시와 버텍스를 재구성하지 않아도 됩니다.

GraphicRaycaster를 제한하고 Raycast Target 비활성화하기

GraphicRaycaster 컴포넌트는 화면 터치나 클릭과 같은 입력 이벤트에 필요합니다. 이 컴포넌트는 화면의 각 입력 지점을 순환하며 입력 지점이 UI의 RectTransform 내에 있는지 확인합니다. 

계층 구조의 맨 위쪽 Canvas에서 기본 GraphicRaycaster를 제거하세요. 대신, 상호 작용이 필요한 개별 요소(버튼, Scrollrect 등)에만 GraphicRaycaster를 추가합니다.

 
기본적으로 활성화되어 있는 Ignore Reversed Graphics 비활성화확장

아울러 모든 UI 텍스트 및 이미지에서 불필요하게 활성화된 Raycast Target도 비활성화합니다. UI에 많은 요소가 있어 복잡한 경우 이와 같이 간단히 설정을 변경하여 불필요한 계산을 줄일 수 있습니다.

 
가능한 경우 Raycast Target 비활성화확장
Layout Group

Layout Group은 비효율적으로 업데이트되므로 반드시 필요할 때만 사용하세요. 콘텐츠가 동적이지 않은 경우 Layout Group을 아예 사용하지 말고, 비율 레이아웃에 앵커를 대신 사용하시기 바랍니다. 그 밖의 경우에는 UI 설정을 마친 후 커스텀 코드를 생성하여 Layout Group 컴포넌트를 비활성화합니다.

동적 요소에 Layout Group(Horizontal, Vertical, Grid)을 사용해야 하는 경우, 중첩되지 않도록 하여 성능을 개선합니다.

 
중첩된 경우 특히 성능을 하락시키는 Layout Group확장
대형 리스트 뷰와 그리드 뷰 사용 시 주의

대형 리스트 뷰와 그리드 뷰는 많은 리소스를 소모합니다. 대형 리스트 또는 그리드 뷰(예: 수백 개의 항목이 있는 인벤토리 화면)를 만들어야 하는 경우 항목마다 UI 요소를 만드는 대신 소규모 UI 요소의 풀을 재사용하는 것이 좋습니다. 샘플 GitHub 프로젝트를 통해 실제 작동 모습을 확인하세요.

요소의 과도한 레이어링 주의

다수의 UI 요소를 레이어링하면(예: 카드 시합 게임에서 쌓여 있는 카드) 오버드로우가 발생합니다. 코드를 커스터마이즈하여 런타임에 레이어링된 요소를 병합하여 요소나 배치(batch)의 수를 줄이세요.

다양한 해상도 및 종횡비 사용

현재 모바일 기기에서 사용 중인 해상도와 화면 크기가 매우 다양하므로, 각 기기에서 최상의 경험을 제공하는 대체 버전의 UI를 만드세요.

Device Simulator를 사용하여 지원되는 다양한 기기의 UI를 미리 볼 수 있습니다. XCode Android Studio에서 가상 기기를 만들 수도 있습니다.

 
Device Simulator를 사용하여 다양한 화면 형식 미리 보기확장
전체 화면 UI 사용 시 기타 요소 모두 숨기기

일시 중지 화면이나 시작 화면이 씬의 나머지 부분을 모두 가리는 경우, 3D 씬을 렌더링하는 카메라를 비활성화합니다. 마찬가지로 맨 위쪽 Canvas에 가려진 배경 Canvas 요소도 모두 비활성화합니다.

60fps에서는 업데이트할 필요가 없으므로 전체 화면 UI 사용 시에는 Application.targetFrameRate를 낮게 설정하는 것이 좋습니다.

World Space 및 Camera Space Canvas 카메라 설정

Event 또는 Render Camera 필드를 공백으로 두면 Unity가 자동으로 Camera.main을 채워 넣어 불필요한 리소스가 소모됩니다. 

가능한 경우 Canvas의 RenderMode에 카메라가 필요 없는 Screen Space – Overlay를 사용하는 것이 좋습니다.

 
World Space Render Mode 사용 시 Event Camera 설정 확인확장
오디오

오디오는 일반적인 성능 저하 원인은 아니지만 최적화를 통해 메모리를 절약할 수 있습니다.

 
AudioClip의 Import Settings 최적화확장
가능한 경우 압축되지 않은 원본 WAV 파일을 소스 에셋으로 사용

압축된 형식(예: MP3 또는 Vorbis)을 사용하는 경우 Unity에서 압축을 푼 뒤 빌드 시간 동안 재압축해야 합니다. 그 결과 손실이 두 번 발생하여 최종 품질이 저하됩니다.

클립을 압축하고 압축 비트레이트 낮추기

압축을 통해 클립의 크기와 메모리 사용량을 줄이세요. 

  • 대부분의 사운드에 Vorbis(반복 재생 목적이 아닌 경우에는 MP3)를 사용하세요.
  • 자주 사용하는 짧은 소리(예: 발자국 소리, 총소리)에 ADPCM을 사용하세요. 이렇게 하면 압축되지 않은 PCM에 비해 파일이 줄지만 재생 중 디코딩은 빨라집니다.

모바일 기기에서의 음향 효과는 최대 22,050Hz여야 합니다. 낮은 설정을 사용해도 최종 품질에 미치는 영향은 일반적으로 매우 적으므로 직접 듣고 판단하세요.

적절한 로드 유형 선택

클립 크기에 따라 설정이 달라집니다.

  • 작은 클립(< 200kb) 로드 시 압축 해제되어야 합니다. 이는 사운드를 가공되지 않은 16비트 PCM 오디오 데이터로 압축 해제하여 CPU 비용과 메모리 사용을 발생시키므로 짧은 사운드에만 적합합니다.
  • 중간 클립(>= 200kb) 메모리에서 압축된 상태여야 합니다.
  • 대용량 파일(배경 음악)  스트리밍으로 설정해야 합니다. 그렇지 않으면 전체 에셋이 메모리에 한 번에 로드됩니다.
메모리에서 음소거된 AudioSource 언로드

음소거 버튼을 구현할 때 볼륨을 0으로 설정하지 마세요. 플레이어가 해당 기능을 자주 켜거나 끌 필요가 없다면 AudioSource 컴포넌트를 삭제하여 메모리에서 언로드할 수 있습니다.

전체 모바일 성능 팁 다운로드

다음 블로그 포스팅에서는 그래픽과 에셋에 대해 자세히 알아보겠습니다. 팀에서 제공하는 유용한 팁 목록을 모두 보고 싶다면 여기에서 전자책을 다운로드하시기 바랍니다.

 
확장

전자책 다운로드

통합 지원 서비스에 대해 자세히 알아보고 엔지니어, 전문가 조언 및 프로젝트에 맞는 베스트 프랙티스 가이드를 이용하려면 여기에서 유니티의 성공 플랜을 확인해 보세요.

더 많은 성능 팁 제공 예정

유니티는 Unity 애플리케이션이 최상의 성능을 발휘할 수 있도록 지원하기 위해 최선을 다하고 있습니다. 자세히 알고 싶은 최적화 주제가 있다면 댓글을 통해 알려 주시기 바랍니다.

2021년 7월 13일 테크놀로지 | 13 분 소요

 

 

[출처] https://blog.unity.com/kr/technology/optimize-your-mobile-game-performance-get-expert-tips-on-physics-ui-and-audio-settings

 

모바일 게임 성능 최적화: 물리, UI, 오디오 설정에 대한 전문가 팁 | Unity Blog

MovePosition 또는 AddForce와 같은 클래스 메서드를 사용하면 Rididbody 오브젝트를 이동할 수 있습니다. 트랜스폼 컴포넌트를 직접 변환하면 물리 월드에서 다시 계산하게 되어, 복잡한 씬의 경우 리소

blog.unity.com

 

반응형
Posted by blueasa
, |

[링크] https://blog.naver.com/ckdduq2507/222113891105

 

유니티(Unity) 사운드 최적화 가이드

  1. 3D Sound Settings 사운드 파일이나 Audio Source 컴포넌트를 붙이게 되면 3D Sound Set...

blog.naver.com

 

반응형
Posted by blueasa
, |

Integrated Success 팀은 유니티 고객들이 복잡한 기술적 문제를 해결할 수 있도록 지원합니다. 유니티의 선임 소프트웨어 엔지니어로 구성된 이 팀과 함께 모바일 게임 최적화에 관한 전문적인 지식을 공유하는 자리를 마련했습니다.

유니티의 엔진 소스 코드를 완벽하게 파악하고 있는 Accelerate Solutions 팀은 Unity 엔진을 최대한 활용할 수 있도록 수많은 고객을 지원합니다. 팀은 크리에이터 프로젝트를 심도 있게 분석하여 속도, 안정성, 효율성 등을 향상시키기 위해 최적화할 부분을 파악합니다. 모바일 게임 최적화에 관한 인사이트를 공유하기 시작하면서, 원래 계획한 하나의 블로그 포스팅에 담기에는 너무나 방대한 정보가 있다는 사실을 알게 되었습니다. 따라서 이 방대한 지식을 한 권의 전자책(여기에서 다운로드 가능)과 75가지 이상의 실용적인 팁을 담은 블로그 포스팅 시리즈를 통해 제공하기로 했습니다.

이번 최적화 시리즈 최종 포스팅에서는 에셋, 프로젝트 구성 및 그래픽의 성능을 향상하는 방법을 자세히 살펴봅니다. 이전 포스팅에서는 프로파일링, 메모리, 코드 아키텍처뿐 아니라 물리, UI, 오디오에 대한 팁을 다뤘습니다. 게임 최적화 방법에 관한 시리즈 전체 내용을 확인하고 싶다면 무료 전자책을 다운로드하시기 바랍니다.

프로젝트 구성

모바일 성능에 영향을 줄 수 있는 몇 가지 프로젝트 설정이 있습니다.

Accelerometer Frequency 감소 또는 비활성화 

Unity는 모바일 기기의 가속도 센서를 1초에도 몇 번씩 풀링합니다. 애플리케이션에서 가속도 센서를 사용하지 않는다면 비활성화하거나 빈도를 줄여 성능을 개선하세요.

 
모바일 게임에서 활용하지 않는다면 Accelerometer Frequency를 비활성화해야 합니다.확장

불필요한 플레이어 설정 또는 품질 설정 비활성화

플레이어 설정에서 지원되지 않는 플랫폼의 Auto Graphics API를 비활성화하면 셰이더 배리언트가 과도하게 생성되지 않도록 방지할 수 있습니다. 애플리케이션이 오래된 CPU를 지원하지 않는다면 해당 CPU에 대해 Target Architectures를 비활성화합니다.

품질 설정에서 불필요한 품질 수준을 비활성화합니다.

불필요한 물리 비활성화

게임에서 물리를 사용하지 않는다면 Auto Simulation Auto Sync Transforms를 선택 해제합니다. 해당 기능을 선택하면 별다른 이득 없이 애플리케이션의 속도가 저하될 수 있습니다.

올바른 프레임 속도 선택 

모바일 프로젝트에서는 프레임 속도와 배터리 수명, 서멀 스로틀링이 균형을 이루어야 합니다. 기기의 한계인 60fps까지 밀어 붙이기 보다는 30fps 정도에서 타협해 실행하는 것이 좋습니다. Unity는 모바일의 경우 30fps를 기본으로 설정합니다.

또한 Application.targetFrameRate를 활용해 런타임 중에 프레임 속도를 동적으로 조정할 수도 있습니다. 예를 들어 속도가 느리거나 비교적 정적인 씬의 경우 30fps 아래로 낮추고 게임플레이 중에는 더 높은 fps 설정을 유지할 수 있습니다.

대규모 계층 구조 사용 지양

Split your hierarchies. 게임 오브젝트가 계층 구조 내에 중첩될 필요가 없다면 부모 자식 관계를 간소화하세요. 계층 구조가 단순하면 씬에서 트랜스폼을 새로고침할 때 멀티스레딩의 이점을 누릴 수 있습니다. 계층 구조가 복잡하면 불필요한 트랜스폼 연산과 높은 가비지 컬렉션 비용이 발생합니다.

트랜스폼에 관한 베스트 프랙티스는 계층 구조 최적화와 이 Unite 세션을 참고하세요. 

트랜스폼 한 번에 이동

아울러 트랜스폼 이동 시, Transform.SetPositionAndRotation을 사용하여 위치와 회전을 한 번에 업데이트하세요. 이렇게 하면 트랜스폼을 두 번 수정함으로써 발생하는 오버헤드를 방지할 수 있습니다.

런타임에서 게임 오브젝트를 인스턴트화해야 한다면 다음과 같이 단순한 최적화로 인스턴스화 중에 부모 자식 관계를 설정하고 다시 포지셔닝할 수 있습니다.

GameObject.Instantiate(prefab, parent);

GameObject.Instantiate(prefab, parent, position, rotation);

Object.Instantiate에 대한 자세한 정보는 Scripting API를 참고하세요.

Vsync가 활성화되어 있다고 가정 

모바일 플랫폼은 절반 프레임을 렌더링하지 않습니다. 에디터에서 Vsync를 비활성화하더라도(Project Settings > Quality) 하드웨어 수준에서는 Vsync가 활성화되어 있습니다. GPU가 충분히 빠르게 새로고침할 수 없는 경우, 현재 프레임이 유지되면서 사실상 fps가 줄어듭니다. 

에셋

에셋 파이프라인은 애플리케이션의 성능에 지대한 영향을 줄 수 있습니다. 숙련된 테크니컬 아티스트가 에셋 형식, 사양, 임포트 설정을 정의하고 실행하여 프로젝트를 원활하게 진행할 수 있도록 팁을 제공합니다.

기본 설정에 의존하지 마세요. 플랫폼별 오버라이드 탭을 사용하면 텍스처, 메시 지오메트리와 같은 에셋을 최적화할 수 있습니다. 잘못된 설정으로 인해 빌드 크기가 커지고 빌드 시간이 길어지며 메모리 사용 상황이 열약해질 수 있습니다. 프리셋 기능으로 특정 프로젝트에 사용할 기본 설정을 커스터마이징하는 것이 좋습니다.

자세한 내용은 아트 에셋 베스트 프랙티스 가이드 또는 Unity Learn의 '모바일 애플리케이션용 3D 아트 최적화' 강의에서 확인하시기 바랍니다.

올바른 텍스처 임포트 

대부분의 메모리가 텍스처에 사용되기 때문에 임포트 설정이 매우 중요합니다. 일반적으로 다음 가이드라인을 따릅니다.

  • 최대 크기 줄이기: 시각적으로 허용 가능한 결과를 내는 최소한의 설정을 사용합니다. 이렇게 하면 결과물의 손상을 피하면서 빠르게 텍스처 메모리를 줄일 수 있습니다.
  • POT(Powers of Two) 사용: Unity에서 모바일 텍스처 압축 형식(PVRCT 또는 ETC)을 사용하려면 POT 텍스처가 필요합니다.
  • 텍스처 아틀라스 사용: 하나의 텍스처에 여러 텍스처를 배치하면 드로우 콜을 줄이고 렌더링 속도를 높일 수 있습니다. Unity 스프라이트 아틀라스 또는 타사의 TexturePacker 를 사용하여 아틀라스를 만들어 보세요.
  • Read/Write Enabled 옵션 해제: 활성화할 경우 이 옵션은 CPU와 GPU로 처리 가능한 메모리에서 사본을 만들기 때문에 텍스처의 메모리 사용 공간이 중복됩니다. 대부분의 경우 이 옵션은 비활성화 상태로 유지합니다. 런타임에서 텍스처를 생성할 경우 Texture2D.Apply 함수를 사용하고, makeNoLongerReadable 값은 true로 전달합니다.
  • 불필요한 밉맵 비활성화: 2D 스프라이트와 UI 그래픽처럼 화면상에 일정한 크기로 유지되는 텍스처에는 밉맵이 필요하지 않습니다(카메라의 거리에 따라 다른 3D 모델에 대해서는 밉맵을 활성화된 상태로 유지).
 
빌드 크기를 최적화하는 데 도움이 되는 적절한 텍스처 임포트 설정확장

텍스처 압축 

동일한 모델과 텍스처를 사용하는 두 가지 예를 생각해 보겠습니다. 왼쪽의 설정은 오른쪽에 비해 거의 8배 많은 메모리를 사용하지만 화질에서 큰 차이를 보이지 않습니다.

 
압축되지 않은 텍스처에는 더 많은 메모리가 필요합니다.확장

iOS와 Android 모두에서 ASTC(Adaptive Scalable Texture Compression)를 사용하세요. 개발 중인 대부분의 게임이 ASTC 압축을 지원하는 최소 사양 기기를 대상으로 합니다.

유일한 예외는 다음과 같습니다.

  • A7 이하 버전인 기기(예: iPhone 5, 5S)를 대상으로 하는 iOS 게임 - PVRTC 사용
  • 2016년 이전의 기기를 대상으로 하는 Android 게임 - ETC2 (Ericsson Texture Compression) 사용

PVRTC와 ETC 같은 압축 형식의 화질이 충분히 높지 않고 ASTC가 대상 플랫폼에서 완전히 지원되지 않는다면 32비트 텍스처 대신 16비트 텍스처를 사용해 보세요.

플랫폼별 추천 텍스처 압축 형식에 관한 자세한 정보는 매뉴얼을 참고하세요.

메시 임포트 설정

텍스처와 마찬가지로 메시도 신중하게 임포트하지 않으면 과도한 메모리 사용으로 이어질 수 있습니다. 메시의 메모리 사용량을 최소화하려면 다음 지침을 따르세요.

  • 메시 압축: 과감한 압축으로 디스크 공간을 줄일 수 있습니다(런타임 시 메모리에는 영향을 주지 않음). 메시 양자화로 인해 정확성이 떨어질 수 있으므로 압축 수준을 다양하게 조절해보고 모델에 적합한 수준을 파악하세요.
  • Read/Write Enabled 비활성화: 이 옵션을 활성화하면 각각 메시 사본이 시스템 메모리와 GPU 메모리에 유지되므로 메모리에서 메시가 중복됩니다. 대부분의 경우에는 비활성화해야 합니다(Unity 2019.2 이하에서는 이 옵션이 기본으로 선택되어 있음).
  • Rig과 BlendShapes 비활성화: 메시에 골격 또는 블렌드 셰이프 애니메이션이 필요하지 않다면 이 옵션을 비활성화합니다.
  • Normal과 Tangent 비활성화: 메시의 머티리얼에 노멀 또는 탄젠트가 필요하지 않은 것이 확실하다면 이 옵션을 선택 해제하여 추가로 메모리를 절감할 수 있습니다.
 
메시 임포트 설정 확인확장

폴리곤 개수 확인 

해상도가 높은 모델은 메모리 사용량이 더 많고 잠재적으로 GPU 시간이 더 길 수 있습니다. 백그라운드 지오메트리에 폴리곤이 50만 개나 필요할까요? DCC에서 모델 수를 줄여 보세요. 카메라의 시점에서 보이지 않는 폴리곤은 삭제하고, 고밀도 메시 대신 텍스처와 노멀 맵을 사용하여 정교한 디테일을 표현하세요.

AssetPostprocessor를 사용하여 임포트 설정 자동화 

에셋을 임포트할 때 AssetPostprocessor를 사용하여 스크립트를 실행할 수 있습니다. 이렇게 하면 모델, 텍스처, 오디오 등의 임포트 전후로 설정을 커스터마이즈하라는 메시지가 표시됩니다.

어드레서블 에셋 시스템 사용 

어드레서블 에셋 시스템을 사용하면 콘텐츠를 간단하게 관리할 수 있습니다. 이 통합된 시스템은 로컬 경로 또는 원격 콘텐츠 전송 네트워크(CDN)에서 비동기적으로 '주소' 또는 별칭에 따라 AssetBundle을 로드합니다.

 
확장

코드가 아닌 에셋(모델, 텍스처, 프리팹, 오디오, 전체 씬)을 AssetBundle에 나눌 경우 다운로드 가능한 콘텐츠(DLC)로 분리할 수 있습니다.

그런 다음 어드레서블을 사용하여 모바일 애플리케이션에 사용할 더 작은 초기 빌드를 만듭니다. 클라우드 콘텐츠 전송을 사용하면 게임 콘텐츠를 호스트하고 게임이 진행됨에 따라 플레이어에게 데이터를 전송할 수 있습니다.

 
어드레서블 에셋 시스템을 사용하여 '주소'에 따라 에셋 로드확장

여기를 클릭하여 어드레서블 에셋 시스템이 에셋 관리의 번거로움을 덜어주는 방법에 대해 알아보세요.

그래픽스 및 GPU 최적화

Unity는 프레임마다 렌더링해야 하는 오브젝트를 지정한 다음 드로우 콜을 만듭니다. 드로우 콜은 오브젝트(예: 삼각형)를 그리기 위한 그래픽스 API 호출이며, 배치는 함께 실행되는 드로우 콜의 그룹입니다.

프로젝트가 복잡해질수록 GPU의 워크로드를 최적화해 주는 파이프라인이 필요합니다. URP(Universal Render Pipeline, 유니버설 렌더 파이프라인)는 현재 싱글 패스 포워드 렌더러를 사용하여 모바일 플랫폼에서 고화질 그래픽을 구현합니다(디퍼드 렌더링은 향후 릴리즈에서 제공될 예정). 콘솔과 PC에서의 동일한 물리 기반 광원 및 머티리얼도 스마트폰이나 태블릿으로 확장할 수 있습니다.

다음 가이드라인은 그래픽의 속도를 높이는 데 도움이 될 수 있습니다.

드로우 콜 배칭 

함께 그릴 오브젝트를 배치로 구성하면 각 오브젝트를 그리는 데 필요한 상태 변화가 최소화됩니다. 그 결과 오브젝트를 렌더링하는 데 드는 CPU 비용이 감소하므로 성능이 향상됩니다. Unity는 여러 기법을 사용하여 여러 오브젝트를 보다 적은 수의 배치로 구성할 수 있습니다.

  • 동적 배칭: 작은 메시의 경우 Unity는 CPU에서 버텍스를 그룹화하고 변환한 다음 모두를 한 번에 그립니다. 참고: 로우 폴리 메시가 충분한 경우에만 이 기법을 사용하세요(버텍스 속성 900개, 버텍스 300개 미만). Dynamic Batcher는 이보다 큰 메시를 배칭하지 않으므로, 용도에 맞지 않게 활성화할 경우 프레임마다 배칭할 작은 메시를 찾느라 CPU 시간을 낭비하게 됩니다.
  • 정적 배칭: 움직이지 않는 지오메트리의 경우 Unity는 동일한 머티리얼을 공유하는 메시에 대한 드로우 콜을 줄일 수 있습니다. 동적 배칭에 비해 더 효율적이지만 메모리 사용량은 늘어납니다.
  • GPU 인스턴싱: 동일한 오브젝트의 수가 많을 때 이 기법을 사용하면 그래픽 하드웨어의 사용을 통해 더 효율적인 배칭이 가능합니다.
  • SRP 배칭: 유니버설 렌더 파이프라인 에셋 Advanced 항목에서 SRP Batcher를 활성화합니다. 이렇게 하면 씬에 따라 CPU 렌더링 시간이 크게 빨라집니다.
 
이와 같은 배칭 기법을 활용할 수 있도록 게임 오브젝트를 정리하세요.확장

프레임 디버거 사용 

프레임 디버거는 개별 드로우 콜에서 각 프레임이 구성된 방식을 보여줍니다. 게임이 렌더링되는 방식을 분석하는 데 도움이 되는 셰이더 속성의 문제 해결에 유용한 도구입니다.

 
각 프레임을 개별 단계로 나누는 프레임 디버거확장

프레임 디버거를 처음 접한다면 여기에서 이 소개 튜토리얼을 확인하세요.

동적 광원 수 줄이기 

모바일 게임에는 동적 광원을 지나치게 많이 추가하지 않는 것이 매우 중요합니다. 동적 메시에는 커스텀 셰이더 이펙트나 라이트 프로브, 정적 메시에는 베이크된 광원과 같은 대안을 고려해 보세요.

URP와 빌트인 파이프라인에서 실시간 광원의 구체적인 지원 및 제한 사항을 보려면 기능 비교 테이블을 참고하세요.

그림자 비활성화 

MeshRenderer 및 광원별로 그림자 드리우기를 비활성화할 수 있습니다. 가능하면 그림자를 비활성화하여 드로우 콜을 줄이세요. 

캐릭터 아래의 간단한 메시나 쿼드에 블러된 텍스처를 적용하여 가짜 그림자를 만들 거나, 커스텀 셰이더로 블롭 섀도우를 만듭니다.

 
Cast Shadow를 비활성화하여 드로우 콜 줄이기확장

라이트맵에 조명 베이크 

GI(Global Illumination, 전역 조명)로 정적 지오메트리에 극적인 조명을 추가하세요. 오브젝트를 Contribute GI로 표시하면 라이트맵의 형태로 고품질 조명을 저장할 수 있습니다.

Contribute GI 활성화  

이제 베이크된 그림자와 조명을 런타임 시 성능 저하 없이 렌더링할 수 있습니다. 프로그레시브 CPU 및 GPU 라이트매퍼로는 전역 조명의 베이킹을 가속화할 수 있습니다.

 

 
라이트매핑 설정(Windows > Rendering > Lighting Settings)과 라이트맵 크기를 조정하여 메모리 사용량 제한확장

Unity에서 라이트매핑을 시작하는 데 도움이 필요하다면 매뉴얼 가이드 조명 최적화에 관한 이 페이지를 참고하세요.

광원 레이어 사용 

광원이 여러 개인 복잡한 씬의 경우 레이어를 사용해 오브젝트를 분리한 다음 각 광원의 영향을 특정 컬링 마스크로 한정합니다. 

 
레이어를 사용하여 광원의 영향을 특정 컬링 마스크로 제한하기확장

움직이는 오브젝트에 라이트 프로브 사용

라이트 프로브는 씬 내의 빈 공간에 대해 베이크된 조명 정보를 저장하고 직접 또는 간접적으로 고품질 조명을 제공합니다. 여기에는 동적 광원에 비해 매우 빠르게 계산하는 구면 조화(Spherical Harmonics) 함수가 사용됩니다.

 
백그라운드의 동적 오브젝트를 비추는 라이트 프로브확장

디테일 수준(LOD) 사용 

오브젝트가 멀리 이동하면 디테일 수준을 통해 단순한 메시와 머티리얼, 셰이더를 사용하도록 조정하거나 전환하여 GPU 성능을 보조할 수 있습니다.

 
LOD 그룹을 사용하는 메시의 예시확장
 
다양한 해상도로 모델링한 소스 메시확장

오클루전 컬링을 사용하여 숨겨진 오브젝트 제거 

다른 오브젝트 뒤에 숨겨진 오브젝트는 계속 렌더링되며 리소스 비용을 발생시킬 수 있습니다. 오클루전 컬링을 사용하여 이러한 오브젝트를 폐기하세요. 

카메라 뷰 바깥의 절두체 컬링은 자동인 반면 오클루전 컬링은 베이크된 과정입니다. 오브젝트를 Static Occluders 또는 Occludees로 표시한 다음 Window > Rendering > Occlusion Culling을 통해 베이크하면 됩니다. 모든 씬에 필요하지는 않지만 많은 경우에 컬링으로 성능을 향상할 수 있습니다.

자세한 내용은 오클루전 컬링 사용하기 튜토리얼을 확인하세요.

모바일 기기의 네이티브 해상도 사용 지양 

스마트폰과 태블릿이 점점 발전함에 따라 새로 출시되는 기기일수록 매우 높은 해상도를 자랑하곤 합니다. 

Screen.SetResolution(width, height, false)을 사용하여 출력 해상도를 낮추면 어느 정도의 성능을 다시 확보할 수 있습니다. 여러 해상도를 프로파일링하여 화질과 속도 사이에서 최적의 균형을 찾아보세요.

카메라 사용 제한

모든 카메라는 작업 수행에 어느 정도의 오버헤드를 유발합니다. 렌더링에 필요한 카메라 컴포넌트만 사용하세요. 저사양 모바일 플랫폼에서는 카메라당 최대 1ms의 CPU 시간을 사용할 수 있습니다.

셰이더를 단순하게 유지 

유니버설 렌더 파이프라인은 이미 모바일 플랫폼에 최적화된 경량 릿 셰이더와 언릿 셰이더를 다양하게 포함하고 있습니다. 런타임 메모리 사용량에 상당한 효과를 미칠 수 있으므로 셰이더 베리에이션을 최대한 낮게 유지하세요. 기본 URP 셰이더가 필요에 맞지 않는다면 셰이더 그래프를 사용해 머티리얼의 외관을 커스터마이징할 수 있습니다. 여기에서 셰이더 그래프를 사용해 시각적으로 셰이더를 빌드하는 방법을 알아보세요.

 

 
셰이더 그래프로 커스텀 셰이더 만들기확장

오버드로우와 알파 블렌딩의 최소화

불필요한 투명 또는 반투명 이미지를 그리지 마세요. 모바일 플랫폼은 오버드로우와 알파 블렌딩의 영향을 크게 받습니다. 거의 보이지 않는 이미지 또는 이펙트를 겹치지 마세요. RenderDoc 그래픽 디버거를 사용해 오버드로우를 확인할 수 있습니다.

포스트 프로세싱 이펙트 제한 

글로우와 같은 전체 화면 포스트 프로세싱 이펙트는 성능을 크게 떨어뜨릴 수 있습니다. 프로젝트의 아트 방향성에 따라 주의하여 사용하세요.

 
모바일 애플리케이션에서는 포스트 프로세싱 이펙트를 단순하게 유지확장

Renderer.material 유의하기 

스크립트의 Renderer.material에 액세스하면 머티리얼이 복사되고 새로운 사본에 대한 참조가 반환됩니다. 이로 인해 이미 해당 머티리얼을 포함하는 기존 배치가 손상됩니다. 배칭된 오브젝트의 머티리얼에 액세스하려면 Renderer.sharedMaterial을 대신 사용하세요.

SkinnedMeshRenderers 최적화 

스킨드 메시를 렌더링하는 데는 비용이 많이 듭니다. SkinnedMeshRenderer를 사용하는 모든 오브젝트에 사용할 필요가 있는지 확인하세요. 게임 오브젝트에 가끔 애니메이션만 필요한 경우 BakeMesh 함수를 사용하여 스킨드 메시를 정적인 포즈로 고정하고 런타임 시에는 더 단순한 MeshRenderer로 대체하세요.

반사 프로브 최소화 

반사 프로브는 사실적인 반사를 만들어 낼 수 있지만 배치의 차원에서 봤을 때 비용이 매우 높습니다. 저해상도 큐브맵, 컬링 마스크, 텍스처 압축을 사용해 런타임 성능을 높이세요.

모바일 성능 최적화에 대한 전체 가이드 다운로드

이 포스팅은 모바일 성능 최적화 시리즈의 마지막 게시물이며, 팀에서 제공하는 유용한 팁의 전체 목록을 확인하고자 하는 분들을 위해 52페이지 분량의 전자책을 출간하였으며, 여기에서 다운로드 할 수 있습니다. 

 
확장

전자책 다운로드

통합 지원 서비스에 대해 자세히 알아보고 엔지니어, 전문가 조언 및 프로젝트에 맞는 베스트 프랙티스 가이드를 이용하려면 여기에서 유니티의 Success 플랜을 확인해 보세요.

원하는 내용을 찾지 못하셨나요?

유니티는 Unity 애플리케이션이 성능을 최대한 발휘할 수 있도록 지원하고 있습니다. 자세히 알고 싶은 최적화 주제가 있다면 댓글로 알려주세요. 

2021년 8월 3일 테크놀로지 | 12 분 소요
 
 

 

[출처] https://blog.unity.com/kr/technology/optimize-your-mobile-game-performance-expert-tips-on-graphics-and-assets

 

모바일 게임 성능 최적화: 그래픽과 에셋에 관한 전문가 팁 | Unity Blog

이 포스팅은 모바일 성능 최적화 시리즈의 마지막 게시물이며, 팀에서 제공하는 유용한 팁의 전체 목록을 확인하고자 하는 분들을 위해 52페이지 분량의 전자책을 출간하였으며, 여기에서 다운

blog.unity.com

 

반응형
Posted by blueasa
, |

  [참고 사항]

- Scale 제거하면 AnimationClip(.anim) 파일 용량이 오히려 늘어남

   = 유니티 에디터의 Animation Editor에서 사용하는 정보값을 넣는다고 함(아래 링크 내용 참조)

   = 빌드 할 때는 에디터 관련 정보는 제거하고 들어감(빌드해서 확인함)

   = 애니메이션 파일을 에셋번들 빌드해도 관련 정보 제거하고 줄어드는 걸 볼 수 있음

 

- Unity Editor에서 '메모리 프로파일러' 등으로 보면 런타임 메모리도 오히려 늘어남

   = 위의 에디터용 정보가 들어간채로 로드 되는 듯 함(에디터의 프로파일러를 모두 신뢰하지 말라고 함(?) 아래 참조링크 참조)

   = 실제 런타임 메모리는 실제 사용할 디바이스(폰 등)에서 확인해야 함(폰 빌드하고 메모리 프로파일러로 런타임 메모리 줄어드는 것 확인 함)

 

 

[링크] https://mgun.tistory.com/2046

 

animation 최적화

케릭터에 붙어있는 애니메이션이 많을수록 케릭터 로딩이 느려진다. 한 스테이지에 케릭터가 10개가 나온다고 가정하고, 각 케릭터가 가져야 할 애니메이션 갯수가 영웅일 경우 20개, 적군일 경

mgun.tistory.com

 

[참조] https://forum.unity.com/threads/animationutility-seteditorcurve-makes-clip-file-size-bigger.410230/

 

AnimationUtility.SetEditorCurve makes clip file size bigger?

Hi, I jusy try to duplicate AnimationClip data from FBX file, and remove some unuse curve. Oh it's Generic Animation, not Humaroid. First, I use Ctrl...

forum.unity.com

 

반응형
Posted by blueasa
, |