Deff_Dev

[Unity/C#] 배치 최적화 (CombineMeshes, Terrain to Mesh) 본문

Unity(유니티)/유니티 공부

[Unity/C#] 배치 최적화 (CombineMeshes, Terrain to Mesh)

Deff_a 2024. 7. 30. 09:41

📌 문제 상황

 

프로젝트에 맵을 적용시켰더니 Batch가 기본 1000이 넘었고 가장 높은 맵은 1900까지 나왔다.

 

인스펙터 창에서 Static 배칭을 적용하려 했지만, 런타임에 어드레서블을 사용해 맵을 로드하는 과정에서 Static 배칭이 적용되지 않는 문제가 발생했다

 

Static 배칭이란 ?

  • 정적인 오브젝트 중에 같은 Material을 가진 오브젝트를 미리 배칭하여 드로우 콜을 줄이는 최적화 기법
  • 배칭 과정에서 모든 정적인 오브젝트를 하나로 묶게 되므로, 메모리 사용량이 증가할 수 있습니다.
  • 모든 최적화 방법이 그렇듯 Static 배칭 역시 적절한 상황에서만 사용해야 한다.

🔑  CombineMeshes를 이용한 Static Batching

 

런타임 중 Static 배칭이 자동으로 적용되지 않자, 직접 같은 Material끼리 묶어주는 기능을 구현했다.

이때, CombineMeshes를 사용하여 CombineInstance끼리 묶어 렌더링했다.

using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;

public class MeshCombiner : MonoBehaviour
{
    private void Awake()
    {
        CombineNewMeshes();
    }

    public void CombineNewMeshes()
    {
        // 메쉬 필터와 메쉬 렌더러를 가진 자식 오브젝트들을 가져옴
        MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
        Dictionary<Material, List<CombineInstance>> materialToMeshCombine = new Dictionary<Material, List<CombineInstance>>();

        // 각 메쉬 필터에 대해
        foreach (MeshFilter meshFilter in meshFilters)
        {
            MeshRenderer meshRenderer = meshFilter.GetComponent<MeshRenderer>();
            if (meshRenderer == null) continue;

            // 여러개의 머터리얼을 가지고 있는 경우 Combine 제외
            if (meshRenderer.sharedMaterials.Length > 1)
            {
                continue;
            }

            // Mesh를 읽을 수 없을 때도 Combine 제외 (읽을 수 없으면 CombineMesh가 그리질 못함)
            if (!meshFilter.sharedMesh.isReadable)
            {
                Debug.LogWarning($"{meshFilter.name} is not Readable");
                continue;
            }
            
            // 메쉬 렌더러의 머터리얼을 가져옴
            Material material = meshRenderer.sharedMaterial;

            // 머터리얼별로 메쉬를 그룹화
            if (!materialToMeshCombine.ContainsKey(material))
            {
                materialToMeshCombine.Add(material, new List<CombineInstance>());
            }

            CombineInstance combineInstance = new CombineInstance();
            combineInstance.mesh = meshFilter.sharedMesh;
            combineInstance.transform = meshFilter.transform.localToWorldMatrix;
            materialToMeshCombine[material].Add(combineInstance);

            // 결합된 메쉬가 생성되기 전에 기존에 존재하는 Mesh들은 비활성화
            meshFilter.gameObject.SetActive(false);
        }

        // 각 머터리얼 그룹에 대해
        foreach (var entry in materialToMeshCombine)
        {
            Material material = entry.Key;
            CombineInstance[] combineInstances = entry.Value.ToArray();

            // 새 메쉬를 생성하고 결합된 메쉬로 설정
            Mesh combinedMesh = new Mesh();
            combinedMesh.CombineMeshes(combineInstances, true, true);

            // 새 메쉬 오브젝트를 생성
            GameObject combinedObject = new GameObject("Combined Mesh");
            combinedObject.transform.SetParent(transform);

            MeshFilter meshFilter = combinedObject.AddComponent<MeshFilter>();
            meshFilter.mesh = combinedMesh;

            MeshRenderer meshRenderer = combinedObject.AddComponent<MeshRenderer>();
            meshRenderer.material = material;
        }

        // 부모 오브젝트를 활성화
        gameObject.SetActive(true);
    }
}

 

 

위 스크립트를 Root 오브젝트에 붙히고 게임을 실행하면 같은 Material 끼리 Combine 되는 것을 볼 수 있다.

 

 

이 기능을 사용할 때, 주의해야할 점은 Mesh의 Read/Write Enable True로 되어 있어야 맵에 존재하는 Mesh들을 읽을 수 있다.

 

 

만약 False로 되어 있다면, Fbx 파일의 Model에서 체크하면된다.

 

Mesh 데이터는 기본적으로 GPU 메모리에 저장되지만, Read/Write를 체크한다면 해당 Mesh를 CPU에서도 수정할 수 있도록 복사본이 만들어지기 때문에 더 많은 메모리를 사용하게 된다. (텍스처도 마찬가지)

 

그래서 필요한 Mesh에서만 체크해서 사용해야한다. (읽어올 필요가 없다면 해제해야함.)


🔑  Terrain to Mesh

 

어둠 맵 같은 경우에는 바닥이 Terrain으로 되어 있어, 다른 맵보다 Batch가 700정도 더 높았다.

 

Terrain은 런타임에 따라 계속 변화하는 특성을 가졌지만 진행 중인 프로젝트에서는 Terrain이 변화하지 않기 때문에 Terrain을 Mesh로 변환하는 작업을 진행했다.

 

 

GitHub - jinsek/MightyTerrainMesh: A Unity Plugin for Converting Terrain 2 Mesh & Terrain 2 Data for Runtime Virtual Texture.

A Unity Plugin for Converting Terrain 2 Mesh & Terrain 2 Data for Runtime Virtual Texture. - jinsek/MightyTerrainMesh

github.com

 

해당 Tool을 이용하여 Terrain을 Mesh로 변경했다.


👌 문제 해결

위 방법들을 통해 아래와 같이 Batch 최적화를 완료했다. ( 좌 : 최적화 전, 우 : 최적화 후)