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)
);
}
}