> 技术文档 > Unity 场景物品优化方案

Unity 场景物品优化方案

1. 静态合批 (Static Batching) 配置步骤:

(1) 标记静态物体 选中需要合批的物体 在Inspector右上角勾选

(2) 材质共享设置 Static (或至少勾选 Batching Static) 需要合批的物体要使用一个材质球,即Mesh Render组件中挂载相同的Materials文件 验证合批效果 运行后查看Stats面板: Batches 数值下降 Saved by batching数值上升


2. GPU Instancing 配置步骤:

(1) 确保Shader为可GPU Instancing类型 即Material面板的最下方有Enable GPU Instancing选项: (2)勾选 Enable GPU Instancing选项

(3) 验证效果 运行后查看Stats面板: Batches 数值下降 Saved by batching数值上升

二、物品失活激活设置

1. Occlusion Culling 烘焙

烘焙步骤:

(1) 标记Occluder/Occludee 在Inspector右上角勾选

(2) 烘焙设置 Occulader Static 和 Occuladee Static Window -> Rendering -> Occlusion Culling打开烘焙界面


在Bake窗口点击右下角Bake开始烘焙 点击摄像机对象,右侧Occlusion界面进入Visualization
可观察到摄像机视野外的物体没有被渲染

2.动态加载拆卸 (AdvancedDynamicLoadManager)
(1)要将物体标签设置为\"Dynamic\",AdvancedDynamicLoadManager会将物体划分在网格中, 玩家当前网格和相邻网格上的物体才会被遍历激活,并放入activeObjects激活物品列表中,这样每次遍 历activeObjects列表中的物体,判断当物体与玩家距离过远时,失活即可

(2)参数:

loadDistance 加载距离

unloadDistance 卸载距离

maxUpdatesPerFrame 每帧更新物体数

cellSize 网格单元格大小

(3)使用: 将该脚本挂载在角色上即可

缺点: 由于是分区域进行激活,所以可视物体位于激活区域外时,会出现物体未显示的情况,需要调整cellSize 进行匹配

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class a : MonoBehaviour
{
    [Header(\"距离设置\")]
    [SerializeField] private float loadDistance = 50f;
    [SerializeField] private float unloadDistance = 60f;
    [Header(\"性能设置\")]
    [SerializeField] private int maxUpdatesPerFrame = 30;  // 每帧最多更新的物体数
    [SerializeField] private float cellSize = 20f;         // 网格单元大小
    private Dictionary<Vector3Int, List> spatialGrid = new
Dictionary<Vector3Int, List>();
    private HashSet activeObjects = new HashSet();
    private Vector3 lastPosition;
    private float sqrLoadDist;
    private float sqrUnloadDist;
    private class DynamicObject
    {
        public GameObject obj;
        public Transform transform;
        public bool isActive;
    }
    void Start()
    {
        sqrLoadDist = loadDistance * loadDistance;
        sqrUnloadDist = unloadDistance * unloadDistance;
        lastPosition = transform.position;
        GameObject[] allObjects = GameObject.FindGameObjectsWithTag(\"Dynamic\");
        foreach (var obj in allObjects)
        {
            Vector3Int cellPos = GetCellPosition(obj.transform.position);
            if (!spatialGrid.ContainsKey(cellPos))
            {
                spatialGrid[cellPos] = new List();
            }
            var dynObj = new DynamicObject
            {
                obj = obj,
                transform = obj.transform,
                isActive = false
            };
            // 初始时根据距离决定是否激活
            float sqrDist = (obj.transform.position -
transform.position).sqrMagnitude;
            if (sqrDist <= sqrLoadDist)
            {
                obj.SetActive(true);
                dynObj.isActive = true;
                activeObjects.Add(dynObj);
            }
            else
            {
                obj.SetActive(false);
            }
            spatialGrid[cellPos].Add(dynObj);
        }
    }
    void Update()
    {
        if ((transform.position - lastPosition).sqrMagnitude < 1f) return;
        lastPosition = transform.position;
        Vector3Int currentCell = GetCellPosition(transform.position);
        // 1. 处理需要激活的物体(玩家所在单元格及相邻单元格)
        for (int x = -1; x <= 1; x++)
        {
            for (int z = -1; z <= 1; z++)
            {
                Vector3Int cell = new Vector3Int(currentCell.x + x, 0,
currentCell.z + z);
                if (spatialGrid.TryGetValue(cell, out var objects))
                {
                    ProcessObjectsInCell(objects, true);
                }
            }
        }
        // 2. 处理需要失活的物体(已激活但距离过远的)
        int processed = 0;
        var toDeactivate = new List();
        foreach (var obj in activeObjects)
        {
            if (processed >= maxUpdatesPerFrame) break;
            float sqrDist = (obj.transform.position -
transform.position).sqrMagnitude;
            if (sqrDist > sqrUnloadDist)
            {
                toDeactivate.Add(obj);
                processed++;
            }
        }
        foreach (var obj in toDeactivate)
        {
            obj.obj.SetActive(false);
            obj.isActive = false;
            activeObjects.Remove(obj);
        }
    }
    private void ProcessObjectsInCell(List objects, bool
checkActivation)
    {
        int processed = 0;
        Vector3 playerPos = transform.position;
        foreach (var obj in objects)
        {
            if (processed >= maxUpdatesPerFrame) return;
            float sqrDist = (obj.transform.position - playerPos).sqrMagnitude;
            if (checkActivation && !obj.isActive && sqrDist < sqrLoadDist)
            {
                obj.obj.SetActive(true);
                obj.isActive = true;
                activeObjects.Add(obj);
                processed++;
            }
        }
    }
    private Vector3Int GetCellPosition(Vector3 worldPos)
    {
        return new Vector3Int(
            Mathf.FloorToInt(worldPos.x / cellSize),
            0,
            Mathf.FloorToInt(worldPos.z / cellSize)
        );
    }
}