第10章 Unity静态场景优化与导航系统:高效渲染与智能寻路的实践指南
摘要 本章探讨了Unity中静态场景优化与导航系统的关键技术。重点介绍了光照烘焙贴图的配置与优化策略,通过预计算光照信息存储到纹理中,大幅降低实时渲染负担。核心算法基于辐射度方程和全局光照计算,采用蒙特卡洛方法进行预计算。实践部分展示了完整的烘焙配置管理类实现,包括参数设置、异步烘焙流程、静态对象标记和结果保存等功能。系统支持大规模商业场景的光照处理,通过合理配置分辨率、间接光照强度等参数,在保证
第10章 Unity静态场景优化与导航系统:高效渲染与智能寻路的实践指南
10.1 光照烘焙贴图的配置与优化策略
10.1.1 光照烘焙贴图的配置与优化
在商业级游戏开发中,光照烘焙是优化静态场景渲染性能的关键技术。通过预计算光照信息并存储到纹理中,可以大幅减少实时渲染的计算负担。这一过程的数学基础是辐射度算法和全局光照方程,它们描述了光线在场景中的传播和相互作用。
从物理光学角度分析,光照烘焙的核心是求解渲染方程:
Lo(p, ωo) = Le(p, ωo) + ∫Ω fr(p, ωi, ωo) Li(p, ωi) (ωi·n) dωi
其中Lo是出射辐射度,Le是自发光辐射度,fr是双向反射分布函数,Li是入射辐射度。在烘焙过程中,这个积分方程通过蒙特卡洛方法或光子映射等算法进行预计算。
在商业项目中,我们通常需要处理大规模场景的光照烘焙。以下是一个完整的烘焙配置管理类:
using UnityEngine;
using System.Collections.Generic;
using System.IO;
namespace CommercialGame.LightingSystem
{
/// <summary>
/// 光照烘焙管理器 - 负责管理和执行光照烘焙流程
/// </summary>
public class LightmapBaker : MonoBehaviour
{
[SerializeField]
private string lightmapDataPath = "Assets/LightmapData/";
[SerializeField]
private int lightmapResolution = 1024;
[SerializeField]
private float indirectIntensity = 1.0f;
[SerializeField]
private int lightmapPadding = 4;
private Dictionary<string, LightmapData> cachedLightmapData;
/// <summary>
/// 初始化光照烘焙系统
/// </summary>
public void InitializeBakingSystem()
{
cachedLightmapData = new Dictionary<string, LightmapData>();
EnsureDirectoryExists(lightmapDataPath);
Debug.Log($"光照烘焙系统初始化完成,贴图分辨率:{lightmapResolution},路径:{lightmapDataPath}");
}
/// <summary>
/// 执行场景光照烘焙
/// </summary>
/// <param name="sceneName">场景名称</param>
public void BakeSceneLighting(string sceneName)
{
if (string.IsNullOrEmpty(sceneName))
{
Debug.LogError("场景名称不能为空");
return;
}
// 设置光照贴图参数
LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
// 配置烘焙参数
var bakeSettings = new LightmapBakeSettings
{
indirectIntensity = indirectIntensity,
lightmapResolution = lightmapResolution,
padding = lightmapPadding,
textureCompression = true
};
// 开始异步烘焙
StartCoroutine(BakeLightingAsync(sceneName, bakeSettings));
}
/// <summary>
/// 异步烘焙光照
/// </summary>
private System.Collections.IEnumerator BakeLightingAsync(string sceneName, LightmapBakeSettings settings)
{
Debug.Log($"开始烘焙场景:{sceneName}");
// 标记静态对象
MarkStaticObjectsForBaking();
// 执行烘焙
Lightmapping.BakeAsync();
// 等待烘焙完成
while (Lightmapping.isRunning)
{
float progress = Lightmapping.progress;
Debug.Log($"烘焙进度:{progress:P}");
yield return new WaitForSeconds(0.5f);
}
// 保存烘焙结果
SaveLightmapData(sceneName);
Debug.Log($"场景 {sceneName} 光照烘焙完成");
}
/// <summary>
/// 标记需要烘焙的静态对象
/// </summary>
private void MarkStaticObjectsForBaking()
{
var staticRenderers = FindObjectsOfType<Renderer>();
foreach (var renderer in staticRenderers)
{
GameObject gameObject = renderer.gameObject;
// 检查对象是否为静态
if (gameObject.isStatic)
{
// 确保有光照贴图UV
Unwrapping.GenerateSecondaryUVSet(renderer.sharedMesh);
// 设置光照贴图索引
renderer.lightmapIndex = -1;
renderer.realtimeLightmapIndex = -1;
}
}
}
/// <summary>
/// 保存光照贴图数据
/// </summary>
private void SaveLightmapData(string sceneName)
{
LightmapData[] lightmapArray = LightmapSettings.lightmaps;
if (lightmapArray.Length == 0)
{
Debug.LogWarning("没有光照贴图数据可保存");
return;
}
// 创建光照数据配置
var lightmapConfig = new SceneLightmapConfig
{
SceneName = sceneName,
LightmapCount = lightmapArray.Length,
BakeTime = System.DateTime.Now.ToString("yyyyMMdd_HHmmss"),
Settings = new LightmapSettings
{
Resolution = lightmapResolution,
IndirectIntensity = indirectIntensity
}
};
// 保存配置到文件
string configPath = Path.Combine(lightmapDataPath, $"{sceneName}_config.json");
string jsonData = JsonUtility.ToJson(lightmapConfig, true);
File.WriteAllText(configPath, jsonData);
Debug.Log($"光照贴图配置已保存:{configPath}");
}
/// <summary>
/// 确保目录存在
/// </summary>
private void EnsureDirectoryExists(string path)
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
/// <summary>
/// 光照烘焙设置
/// </summary>
[System.Serializable]
private struct LightmapBakeSettings
{
public float indirectIntensity;
public int lightmapResolution;
public int padding;
public bool textureCompression;
}
/// <summary>
/// 场景光照贴图配置
/// </summary>
[System.Serializable]
private class SceneLightmapConfig
{
public string SceneName;
public int LightmapCount;
public string BakeTime;
public LightmapSettings Settings;
}
}
}
10.1.2 混合光照模式的实现与优化
在商业游戏中,纯烘焙光照无法满足动态物体的需求,因此混合光照模式成为必备方案。混合光照的核心是实时阴影与烘焙阴影的融合,这涉及到阴影贴图的合成算法。
从数学角度看,混合光照需要解决两个核心问题:
- 实时阴影与烘焙阴影的权重分配
- 阴影边缘的平滑过渡
以下是一个混合光照管理器的实现:
using UnityEngine;
using UnityEngine.Rendering;
namespace CommercialGame.LightingSystem
{
/// <summary>
/// 混合光照管理器 - 处理实时光照与烘焙光照的混合
/// </summary>
public class HybridLightingManager : MonoBehaviour
{
[SerializeField]
[Range(0.0f, 1.0f)]
private float realtimeShadowStrength = 0.7f;
[SerializeField]
[Range(0.0f, 1.0f)]
private float bakedShadowStrength = 0.3f;
[SerializeField]
private float shadowTransitionDistance = 10.0f;
private Camera mainCamera;
private Vector3[] cameraFrustumCorners;
/// <summary>
/// 初始化混合光照系统
/// </summary>
private void Start()
{
mainCamera = Camera.main;
cameraFrustumCorners = new Vector3[4];
// 配置混合光照参数
ConfigureHybridLighting();
Debug.Log("混合光照系统初始化完成");
}
/// <summary>
/// 配置混合光照参数
/// </summary>
private void ConfigureHybridLighting()
{
// 设置阴影距离
QualitySettings.shadowDistance = CalculateOptimalShadowDistance();
// 配置混合阴影模式
LightmapSettings.lightmapsMode = LightmapsMode.CombinedDirectional;
// 设置阴影级联
if (QualitySettings.shadows == ShadowQuality.All)
{
QualitySettings.shadowCascades = 4;
QualitySettings.shadowCascade2Split = 0.33f;
QualitySettings.shadowCascade4Split = new Vector3(0.067f, 0.2f, 0.467f);
}
}
/// <summary>
/// 计算最佳阴影距离
/// </summary>
private float CalculateOptimalShadowDistance()
{
if (mainCamera == null)
{
return 50.0f;
}
// 获取相机视锥体远平面四个角
mainCamera.CalculateFrustumCorners(
new Rect(0, 0, 1, 1),
mainCamera.farClipPlane,
Camera.MonoOrStereoscopicEye.Mono,
cameraFrustumCorners
);
// 计算最大可视距离
float maxDistance = 0.0f;
for (int i = 0; i < 4; i++)
{
Vector3 worldCorner = mainCamera.transform.TransformVector(cameraFrustumCorners[i]);
float distance = worldCorner.magnitude;
if (distance > maxDistance)
{
maxDistance = distance;
}
}
// 根据性能需求调整阴影距离
float shadowDistance = Mathf.Min(maxDistance * 0.8f, 100.0f);
return shadowDistance;
}
/// <summary>
/// 更新每帧的混合光照参数
/// </summary>
private void Update()
{
UpdateDynamicShadowMixing();
AdjustLightProbeBlending();
}
/// <summary>
/// 更新动态阴影混合
/// </summary>
private void UpdateDynamicShadowMixing()
{
// 根据相机距离调整阴影混合权重
float cameraDistance = Vector3.Distance(mainCamera.transform.position, transform.position);
float distanceFactor = Mathf.Clamp01(cameraDistance / shadowTransitionDistance);
// 动态调整阴影强度
float dynamicRealtimeStrength = Mathf.Lerp(realtimeShadowStrength, 0.2f, distanceFactor);
float dynamicBakedStrength = Mathf.Lerp(bakedShadowStrength, 0.8f, distanceFactor);
// 应用阴影混合参数到所有灯光
UpdateLightShadowMixing(dynamicRealtimeStrength, dynamicBakedStrength);
}
/// <summary>
/// 更新灯光阴影混合参数
/// </summary>
private void UpdateLightShadowMixing(float realtimeStrength, float bakedStrength)
{
Light[] sceneLights = FindObjectsOfType<Light>();
foreach (Light light in sceneLights)
{
if (light.type == LightType.Directional)
{
// 定向光使用混合阴影
light.shadows = LightShadows.Soft;
light.shadowStrength = realtimeStrength;
// 设置烘焙阴影强度
light.bakingOutput.lightmapBakeType = LightmapBakeType.Mixed;
}
}
}
/// <summary>
/// 调整光照探针混合
/// </summary>
private void AdjustLightProbeBlending()
{
// 获取所有使用光照探针的渲染器
Renderer[] dynamicRenderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in dynamicRenderers)
{
if (!renderer.gameObject.isStatic)
{
// 动态物体使用光照探针
renderer.lightProbeUsage = LightProbeUsage.BlendProbes;
renderer.reflectionProbeUsage = ReflectionProbeUsage.BlendProbes;
}
}
}
/// <summary>
/// 计算阴影混合的数学插值
/// </summary>
public float CalculateShadowBlendFactor(Vector3 position)
{
// 计算到最近静态物体的距离
float distanceToStatic = CalculateDistanceToNearestStatic(position);
// 使用平滑函数进行插值
float blendFactor = SmoothStepBlend(distanceToStatic, shadowTransitionDistance);
return blendFactor;
}
/// <summary>
/// 计算到最近静态物体的距离
/// </summary>
private float CalculateDistanceToNearestStatic(Vector3 position)
{
float nearestDistance = float.MaxValue;
// 在实际项目中,这里应该使用空间划分数据结构进行优化
GameObject[] staticObjects = GameObject.FindGameObjectsWithTag("Static");
foreach (GameObject staticObject in staticObjects)
{
float distance = Vector3.Distance(position, staticObject.transform.position);
if (distance < nearestDistance)
{
nearestDistance = distance;
}
}
return nearestDistance;
}
/// <summary>
/// 平滑步进插值函数
/// </summary>
private float SmoothStepBlend(float x, float maxDistance)
{
float t = Mathf.Clamp01(x / maxDistance);
// 三次平滑插值:3t² - 2t³
return t * t * (3.0f - 2.0f * t);
}
}
}
10.1.3 运行时动态切换烘焙贴图技术
在大型商业项目中,经常需要根据不同的时间、天气或游戏状态动态切换光照贴图。这需要高效的内存管理和快速的纹理切换机制。
以下实现了一个支持运行时切换光照贴图的系统:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace CommercialGame.LightingSystem
{
/// <summary>
/// 动态光照贴图切换器
/// </summary>
public class DynamicLightmapSwitcher : MonoBehaviour
{
[System.Serializable]
public class LightmapSet
{
public string setName;
public Texture2D[] lightmapFar;
public Texture2D[] lightmapNear;
public LightmapData[] lightmapData;
public Color ambientLight;
public Material skyboxMaterial;
}
[SerializeField]
private LightmapSet[] lightmapSets;
[SerializeField]
private float transitionDuration = 2.0f;
[SerializeField]
private AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
private Dictionary<string, LightmapSet> lightmapSetDictionary;
private LightmapSet currentLightmapSet;
private Coroutine transitionCoroutine;
/// <summary>
/// 初始化光照贴图集
/// </summary>
private void Awake()
{
InitializeLightmapSets();
}
/// <summary>
/// 初始化光照贴图集字典
/// </summary>
private void InitializeLightmapSets()
{
lightmapSetDictionary = new Dictionary<string, LightmapSet>();
foreach (LightmapSet set in lightmapSets)
{
if (set.lightmapFar != null && set.lightmapNear != null)
{
// 创建LightmapData数组
int lightmapCount = Mathf.Min(set.lightmapFar.Length, set.lightmapNear.Length);
set.lightmapData = new LightmapData[lightmapCount];
for (int i = 0; i < lightmapCount; i++)
{
set.lightmapData[i] = new LightmapData
{
lightmapColor = set.lightmapFar[i],
lightmapDir = set.lightmapNear[i]
};
}
lightmapSetDictionary[set.setName] = set;
}
}
}
/// <summary>
/// 切换到指定的光照贴图集
/// </summary>
public void SwitchToLightmapSet(string setName, bool instant = false)
{
if (!lightmapSetDictionary.ContainsKey(setName))
{
Debug.LogError($"光照贴图集 '{setName}' 不存在");
return;
}
LightmapSet targetSet = lightmapSetDictionary[setName];
if (transitionCoroutine != null)
{
StopCoroutine(transitionCoroutine);
}
if (instant || transitionDuration <= 0)
{
ApplyLightmapSetImmediately(targetSet);
}
else
{
transitionCoroutine = StartCoroutine(TransitionToLightmapSet(targetSet));
}
}
/// <summary>
/// 立即应用光照贴图集
/// </summary>
private void ApplyLightmapSetImmediately(LightmapSet lightmapSet)
{
// 应用光照贴图
LightmapSettings.lightmaps = lightmapSet.lightmapData;
// 更新环境光照
RenderSettings.ambientLight = lightmapSet.ambientLight;
// 更新天空盒
if (lightmapSet.skyboxMaterial != null)
{
RenderSettings.skybox = lightmapSet.skyboxMaterial;
}
// 更新所有静态渲染器的光照贴图索引
UpdateAllRenderersLightmapIndices();
currentLightmapSet = lightmapSet;
Debug.Log($"已切换到光照贴图集: {lightmapSet.setName}");
}
/// <summary>
/// 过渡到目标光照贴图集
/// </summary>
private IEnumerator TransitionToLightmapSet(LightmapSet targetSet)
{
LightmapSet startSet = currentLightmapSet;
float elapsedTime = 0.0f;
if (startSet == null)
{
ApplyLightmapSetImmediately(targetSet);
yield break;
}
// 创建过渡用的中间光照贴图集
LightmapSet intermediateSet = CreateIntermediateLightmapSet(startSet, targetSet);
while (elapsedTime < transitionDuration)
{
float t = elapsedTime / transitionDuration;
float curveValue = transitionCurve.Evaluate(t);
// 插值环境光照
RenderSettings.ambientLight = Color.Lerp(
startSet.ambientLight,
targetSet.ambientLight,
curveValue
);
// 插值光照贴图(实际项目中需要支持纹理混合)
UpdateLightmapBlending(startSet, targetSet, curveValue);
elapsedTime += Time.deltaTime;
yield return null;
}
// 应用最终的光照贴图集
ApplyLightmapSetImmediately(targetSet);
}
/// <summary>
/// 创建中间光照贴图集
/// </summary>
private LightmapSet CreateIntermediateLightmapSet(LightmapSet start, LightmapSet end)
{
// 在实际商业项目中,这里需要实现真正的纹理混合
// 本示例返回起始集作为占位符
return start;
}
/// <summary>
/// 更新光照贴图混合
/// </summary>
private void UpdateLightmapBlending(LightmapSet start, LightmapSet end, float blendFactor)
{
// 这是一个简化的实现
// 在实际项目中,可能需要使用Shader或RenderTexture进行真正的纹理混合
// 对于环境光立方体贴图,可以使用混合探针
if (start.skyboxMaterial != null && end.skyboxMaterial != null)
{
// 在这里实现天空盒材质的插值逻辑
}
}
/// <summary>
/// 更新所有渲染器的光照贴图索引
/// </summary>
private void UpdateAllRenderersLightmapIndices()
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in allRenderers)
{
if (renderer.gameObject.isStatic)
{
// 重置光照贴图索引,让Unity自动分配
renderer.lightmapIndex = -1;
renderer.realtimeLightmapIndex = -1;
}
}
// 强制Unity重新分配光照贴图索引
LightmapSettings.lightmaps = LightmapSettings.lightmaps;
}
/// <summary>
/// 预加载光照贴图集
/// </summary>
public void PreloadLightmapSet(string setName)
{
if (lightmapSetDictionary.TryGetValue(setName, out LightmapSet lightmapSet))
{
StartCoroutine(PreloadLightmapTextures(lightmapSet));
}
}
/// <summary>
/// 预加载光照贴图纹理
/// </summary>
private IEnumerator PreloadLightmapTextures(LightmapSet lightmapSet)
{
foreach (Texture2D texture in lightmapSet.lightmapFar)
{
texture.wrapMode = TextureWrapMode.Clamp;
texture.filterMode = FilterMode.Bilinear;
texture.mipMapBias = -0.5f;
// 触发纹理加载到GPU内存
texture.GetNativeTexturePtr();
yield return null;
}
}
}
}
10.2 视觉遮挡剔除系统的实现原理
10.2.1 遮挡剔除算法的数学基础
遮挡剔除是渲染优化的核心技术,其数学基础主要涉及视锥体裁剪、层次深度缓冲(Hi-Z)和遮挡查询。在商业引擎中,通常使用层次ZBuffer算法,其时间复杂度为O(log n)。
关键算法包括:
- 保守性光栅化:确保不会错误剔除可见物体
- 深度范围估计:使用包围体层次结构加速测试
- 时间一致性:利用帧间连贯性减少计算量
以下是一个高级遮挡剔除系统的实现:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace CommercialGame.OcclusionSystem
{
/// <summary>
/// 高级遮挡剔除管理器
/// </summary>
public class AdvancedOcclusionCuller : MonoBehaviour
{
[SerializeField]
private Camera occlusionCamera;
[SerializeField]
private LayerMask occluderLayer = -1;
[SerializeField]
private LayerMask occludeeLayer = -1;
[SerializeField]
private float occlusionTestRadius = 5.0f;
[SerializeField]
private int maxOcclusionQueriesPerFrame = 50;
[SerializeField]
private bool useAsyncOcclusionTests = true;
private class OcclusionNode
{
public Renderer renderer;
public Bounds worldBounds;
public float lastVisibleTime;
public bool isVisible;
public float screenSize;
public int queryId;
}
private Dictionary<Renderer, OcclusionNode> occlusionNodes;
private Queue<int> availableQueryIds;
private HashSet<int> pendingQueries;
private ComputeBuffer occlusionBuffer;
private static class OcclusionShaderIDs
{
public static readonly int WorldToOcclusionCamera = Shader.PropertyToID("_WorldToOcclusionCamera");
public static readonly int OcclusionTestResults = Shader.PropertyToID("_OcclusionTestResults");
public static readonly int ObjectBoundsBuffer = Shader.PropertyToID("_ObjectBoundsBuffer");
}
/// <summary>
/// 初始化遮挡剔除系统
/// </summary>
private void Start()
{
InitializeOcclusionSystem();
CreateOcclusionCamera();
}
/// <summary>
/// 初始化遮挡系统数据结构
/// </summary>
private void InitializeOcclusionSystem()
{
occlusionNodes = new Dictionary<Renderer, OcclusionNode>();
availableQueryIds = new Queue<int>();
pendingQueries = new HashSet<int>();
// 预分配查询ID
for (int i = 0; i < 1024; i++)
{
availableQueryIds.Enqueue(i);
}
// 收集场景中所有可能的遮挡物和被遮挡物
CollectOcclusionCandidates();
Debug.Log($"遮挡系统初始化完成,找到 {occlusionNodes.Count} 个可遮挡对象");
}
/// <summary>
/// 创建专用遮挡测试相机
/// </summary>
private void CreateOcclusionCamera()
{
if (occlusionCamera == null)
{
GameObject occlusionCameraObj = new GameObject("OcclusionCamera");
occlusionCameraObj.transform.SetParent(transform);
occlusionCamera = occlusionCameraObj.AddComponent<Camera>();
// 配置遮挡相机参数
occlusionCamera.cullingMask = occluderLayer;
occlusionCamera.clearFlags = CameraClearFlags.Depth;
occlusionCamera.depth = Camera.main.depth - 1;
occlusionCamera.renderingPath = RenderingPath.Forward;
occlusionCamera.allowMSAA = false;
occlusionCamera.allowHDR = false;
occlusionCamera.enabled = false;
}
}
/// <summary>
/// 收集遮挡候选对象
/// </summary>
private void CollectOcclusionCandidates()
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in allRenderers)
{
// 检查层级是否匹配
int layerMask = 1 << renderer.gameObject.layer;
if ((layerMask & occludeeLayer.value) == 0)
{
continue;
}
var node = new OcclusionNode
{
renderer = renderer,
worldBounds = renderer.bounds,
lastVisibleTime = Time.time,
isVisible = true,
screenSize = 0.0f,
queryId = -1
};
occlusionNodes[renderer] = node;
}
}
/// <summary>
/// 每帧更新遮挡测试
/// </summary>
private void Update()
{
UpdateCameraPosition();
PerformOcclusionTests();
UpdateVisibilityStates();
}
/// <summary>
/// 更新遮挡相机位置
/// </summary>
private void UpdateCameraPosition()
{
if (Camera.main != null && occlusionCamera != null)
{
// 同步主相机参数
occlusionCamera.transform.position = Camera.main.transform.position;
occlusionCamera.transform.rotation = Camera.main.transform.rotation;
occlusionCamera.fieldOfView = Camera.main.fieldOfView;
occlusionCamera.nearClipPlane = Camera.main.nearClipPlane;
occlusionCamera.farClipPlane = Camera.main.farClipPlane;
}
}
/// <summary>
/// 执行遮挡测试
/// </summary>
private void PerformOcclusionTests()
{
if (occlusionCamera == null)
{
return;
}
// 清空之前的查询结果
CompletePendingQueries();
// 按距离排序,优先测试近处的对象
var sortedNodes = occlusionNodes.Values
.OrderBy(node => Vector3.Distance(node.worldBounds.center, occlusionCamera.transform.position))
.Take(maxOcclusionQueriesPerFrame);
int testsThisFrame = 0;
foreach (var node in sortedNodes)
{
if (testsThisFrame >= maxOcclusionQueriesPerFrame)
{
break;
}
// 申请查询ID
if (availableQueryIds.Count > 0)
{
node.queryId = availableQueryIds.Dequeue();
pendingQueries.Add(node.queryId);
// 执行异步遮挡查询
if (useAsyncOcclusionTests)
{
PerformAsyncOcclusionTest(node);
}
else
{
PerformImmediateOcclusionTest(node);
}
testsThisFrame++;
}
}
}
/// <summary>
/// 执行异步遮挡测试
/// </summary>
private void PerformAsyncOcclusionTest(OcclusionNode node)
{
// 使用GeometryUtility.TestPlanesAABB进行保守测试
Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(occlusionCamera);
if (!GeometryUtility.TestPlanesAABB(frustumPlanes, node.worldBounds))
{
// 在视锥体外,不可见
node.isVisible = false;
return;
}
// 计算屏幕空间大小
Vector3 screenPoint = occlusionCamera.WorldToViewportPoint(node.worldBounds.center);
node.screenSize = CalculateScreenSpaceSize(node.worldBounds);
// 如果屏幕空间大小太小,可以跳过渲染
if (node.screenSize < 0.001f)
{
node.isVisible = false;
return;
}
// 使用物理系统进行精确遮挡测试
RaycastHit[] hits = Physics.BoxCastAll(
node.worldBounds.center,
node.worldBounds.extents,
occlusionCamera.transform.forward,
Quaternion.identity,
occlusionCamera.farClipPlane,
occluderLayer
);
// 分析遮挡结果
AnalyzeOcclusionResults(node, hits);
}
/// <summary>
/// 执行立即遮挡测试
/// </summary>
private void PerformImmediateOcclusionTest(OcclusionNode node)
{
// 简单的视锥体测试
if (IsInViewFrustum(node.worldBounds))
{
// 更精确的遮挡测试
bool isOccluded = CheckPreciseOcclusion(node.worldBounds);
node.isVisible = !isOccluded;
if (node.isVisible)
{
node.lastVisibleTime = Time.time;
}
}
else
{
node.isVisible = false;
}
}
/// <summary>
/// 分析遮挡结果
/// </summary>
private void AnalyzeOcclusionResults(OcclusionNode node, RaycastHit[] hits)
{
if (hits.Length == 0)
{
node.isVisible = true;
node.lastVisibleTime = Time.time;
return;
}
// 检查是否有遮挡物在对象前面
float objectDistance = Vector3.Distance(node.worldBounds.center, occlusionCamera.transform.position);
bool isOccluded = false;
foreach (var hit in hits)
{
float hitDistance = Vector3.Distance(hit.point, occlusionCamera.transform.position);
if (hitDistance < objectDistance - 0.1f) // 添加小偏移避免自遮挡
{
// 检查遮挡物的尺寸是否足够大
Renderer hitRenderer = hit.collider.GetComponent<Renderer>();
if (hitRenderer != null)
{
Bounds hitBounds = hitRenderer.bounds;
float hitScreenSize = CalculateScreenSpaceSize(hitBounds);
if (hitScreenSize > node.screenSize * 0.5f)
{
isOccluded = true;
break;
}
}
}
}
node.isVisible = !isOccluded;
if (node.isVisible)
{
node.lastVisibleTime = Time.time;
}
}
/// <summary>
/// 计算屏幕空间大小
/// </summary>
private float CalculateScreenSpaceSize(Bounds bounds)
{
Vector3[] corners = GetBoundsCorners(bounds);
float maxScreenSize = 0.0f;
foreach (var corner in corners)
{
Vector3 screenPoint = occlusionCamera.WorldToViewportPoint(corner);
if (screenPoint.z > 0)
{
float screenSize = Mathf.Max(Mathf.Abs(screenPoint.x), Mathf.Abs(screenPoint.y));
maxScreenSize = Mathf.Max(maxScreenSize, screenSize);
}
}
return maxScreenSize;
}
/// <summary>
/// 获取包围盒的八个角
/// </summary>
private Vector3[] GetBoundsCorners(Bounds bounds)
{
Vector3[] corners = new Vector3[8];
Vector3 center = bounds.center;
Vector3 extents = bounds.extents;
corners[0] = center + new Vector3(-extents.x, -extents.y, -extents.z);
corners[1] = center + new Vector3(-extents.x, -extents.y, extents.z);
corners[2] = center + new Vector3(-extents.x, extents.y, -extents.z);
corners[3] = center + new Vector3(-extents.x, extents.y, extents.z);
corners[4] = center + new Vector3(extents.x, -extents.y, -extents.z);
corners[5] = center + new Vector3(extents.x, -extents.y, extents.z);
corners[6] = center + new Vector3(extents.x, extents.y, -extents.z);
corners[7] = center + new Vector3(extents.x, extents.y, extents.z);
return corners;
}
/// <summary>
/// 检查是否在视锥体内
/// </summary>
private bool IsInViewFrustum(Bounds bounds)
{
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(occlusionCamera);
return GeometryUtility.TestPlanesAABB(planes, bounds);
}
/// <summary>
/// 检查精确遮挡
/// </summary>
private bool CheckPreciseOcclusion(Bounds bounds)
{
// 从相机向包围盒中心发射射线
Vector3 direction = (bounds.center - occlusionCamera.transform.position).normalized;
RaycastHit hit;
if (Physics.Raycast(occlusionCamera.transform.position, direction, out hit,
Vector3.Distance(occlusionCamera.transform.position, bounds.center) * 1.1f, occluderLayer))
{
// 检查击中点是否在包围盒内
if (bounds.Contains(hit.point))
{
return false; // 击中自己,不是遮挡
}
return true; // 被其他物体遮挡
}
return false; // 没有遮挡
}
/// <summary>
/// 完成待处理的查询
/// </summary>
private void CompletePendingQueries()
{
foreach (int queryId in pendingQueries)
{
availableQueryIds.Enqueue(queryId);
}
pendingQueries.Clear();
}
/// <summary>
/// 更新可见性状态
/// </summary>
private void UpdateVisibilityStates()
{
foreach (var kvp in occlusionNodes)
{
OcclusionNode node = kvp.Value;
Renderer renderer = kvp.Key;
// 应用可见性状态
if (renderer != null)
{
renderer.enabled = node.isVisible;
}
}
}
/// <summary>
/// 添加动态遮挡物
/// </summary>
public void RegisterDynamicOccluder(Renderer dynamicRenderer)
{
if (!occlusionNodes.ContainsKey(dynamicRenderer))
{
var node = new OcclusionNode
{
renderer = dynamicRenderer,
worldBounds = dynamicRenderer.bounds,
lastVisibleTime = Time.time,
isVisible = true,
screenSize = 0.0f,
queryId = -1
};
occlusionNodes[dynamicRenderer] = node;
}
}
/// <summary>
/// 移除遮挡物
/// </summary>
public void UnregisterOccluder(Renderer renderer)
{
if (occlusionNodes.ContainsKey(renderer))
{
occlusionNodes.Remove(renderer);
}
}
/// <summary>
/// 手动强制更新对象的可见性
/// </summary>
public void ForceUpdateVisibility(Renderer renderer)
{
if (occlusionNodes.TryGetValue(renderer, out OcclusionNode node))
{
node.lastVisibleTime = Time.time - 1.0f; // 强制重新测试
}
}
/// <summary>
/// 获取系统统计信息
/// </summary>
public OcclusionStats GetStatistics()
{
int visibleCount = occlusionNodes.Values.Count(node => node.isVisible);
int totalCount = occlusionNodes.Count;
return new OcclusionStats
{
totalObjects = totalCount,
visibleObjects = visibleCount,
culledObjects = totalCount - visibleCount,
cullingRatio = (float)(totalCount - visibleCount) / totalCount
};
}
/// <summary>
/// 遮挡系统统计信息
/// </summary>
public struct OcclusionStats
{
public int totalObjects;
public int visibleObjects;
public int culledObjects;
public float cullingRatio;
}
}
}
10.2.2 动态遮挡剔除的事件驱动架构
在商业游戏中,遮挡剔除不仅需要高效,还需要灵活的事件系统来支持游戏逻辑。以下实现了一个事件驱动的遮挡系统:
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
namespace CommercialGame.OcclusionSystem
{
/// <summary>
/// 遮挡事件类型
/// </summary>
public enum OcclusionEventType
{
BecameVisible,
BecameInvisible,
PartialOcclusion,
FullyVisible
}
/// <summary>
/// 遮挡事件数据
/// </summary>
public class OcclusionEventData
{
public Renderer targetRenderer;
public OcclusionEventType eventType;
public float visibilityRatio;
public Vector3 lastVisiblePosition;
public float timestamp;
public OcclusionEventData(Renderer renderer, OcclusionEventType type, float ratio = 1.0f)
{
targetRenderer = renderer;
eventType = type;
visibilityRatio = ratio;
lastVisiblePosition = renderer.transform.position;
timestamp = Time.time;
}
}
/// <summary>
/// 事件驱动的遮挡管理器
/// </summary>
public class EventDrivenOcclusionManager : MonoBehaviour
{
[System.Serializable]
public class OcclusionEvent : UnityEvent<OcclusionEventData> { }
[Header("事件配置")]
public OcclusionEvent onBecameVisible = new OcclusionEvent();
public OcclusionEvent onBecameInvisible = new OcclusionEvent();
public OcclusionEvent onPartialOcclusion = new OcclusionEvent();
public OcclusionEvent onFullyVisible = new OcclusionEvent();
[Header("阈值配置")]
[SerializeField]
[Range(0.0f, 1.0f)]
private float fullVisibilityThreshold = 0.9f;
[SerializeField]
[Range(0.0f, 1.0f)]
private float partialOcclusionThreshold = 0.3f;
[SerializeField]
private float eventCooldown = 0.5f;
private AdvancedOcclusionCuller occlusionCuller;
private Dictionary<Renderer, OcclusionState> objectStates;
private Dictionary<Renderer, float> lastEventTimes;
private class OcclusionState
{
public bool wasVisible;
public float lastVisibilityRatio;
public int consecutiveFramesVisible;
public int consecutiveFramesInvisible;
public Vector3 lastRecordedPosition;
}
/// <summary>
/// 初始化事件系统
/// </summary>
private void Start()
{
occlusionCuller = GetComponent<AdvancedOcclusionCuller>();
if (occlusionCuller == null)
{
Debug.LogError("需要AdvancedOcclusionCuller组件");
return;
}
objectStates = new Dictionary<Renderer, OcclusionState>();
lastEventTimes = new Dictionary<Renderer, float>();
// 初始收集所有渲染器
InitializeObjectStates();
Debug.Log("事件驱动遮挡系统初始化完成");
}
/// <summary>
/// 初始化对象状态
/// </summary>
private void InitializeObjectStates()
{
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in allRenderers)
{
if (renderer.gameObject.isStatic || renderer.CompareTag("Dynamic"))
{
objectStates[renderer] = new OcclusionState
{
wasVisible = renderer.enabled,
lastVisibilityRatio = renderer.enabled ? 1.0f : 0.0f,
consecutiveFramesVisible = renderer.enabled ? 1 : 0,
consecutiveFramesInvisible = renderer.enabled ? 0 : 1,
lastRecordedPosition = renderer.transform.position
};
}
}
}
/// <summary>
/// 每帧更新事件检测
/// </summary>
private void Update()
{
if (occlusionCuller == null)
{
return;
}
// 获取当前帧的统计信息
var stats = occlusionCuller.GetStatistics();
// 检查所有跟踪的对象
CheckOcclusionEvents();
}
/// <summary>
/// 检查遮挡事件
/// </summary>
private void CheckOcclusionEvents()
{
foreach (var kvp in objectStates)
{
Renderer renderer = kvp.Key;
OcclusionState state = kvp.Value;
if (renderer == null)
{
continue;
}
// 计算当前可见性比率
float currentVisibilityRatio = CalculateVisibilityRatio(renderer);
bool isCurrentlyVisible = currentVisibilityRatio > partialOcclusionThreshold;
// 检测可见性变化
if (isCurrentlyVisible != state.wasVisible)
{
HandleVisibilityChange(renderer, isCurrentlyVisible, currentVisibilityRatio);
}
// 检测可见性程度变化
else if (isCurrentlyVisible)
{
HandleVisibilityRatioChange(renderer, currentVisibilityRatio, state);
}
// 更新状态
UpdateOcclusionState(renderer, state, isCurrentlyVisible, currentVisibilityRatio);
}
}
/// <summary>
/// 计算可见性比率
/// </summary>
private float CalculateVisibilityRatio(Renderer renderer)
{
if (!renderer.enabled)
{
return 0.0f;
}
// 在实际项目中,这里应该实现更精确的可见性计算
// 例如通过采样多个点或使用渲染查询
Bounds bounds = renderer.bounds;
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
return renderer.isVisible ? 1.0f : 0.0f;
}
// 简单实现:检查包围盒在屏幕上的投影面积
Vector3[] corners = GetBoundsCorners(bounds);
int visibleCorners = 0;
foreach (Vector3 corner in corners)
{
Vector3 viewportPoint = mainCamera.WorldToViewportPoint(corner);
// 检查点是否在视锥体内且未被遮挡
if (viewportPoint.z > 0 &&
viewportPoint.x >= 0 && viewportPoint.x <= 1 &&
viewportPoint.y >= 0 && viewportPoint.y <= 1)
{
visibleCorners++;
}
}
return visibleCorners / (float)corners.Length;
}
/// <summary>
/// 获取包围盒角点
/// </summary>
private Vector3[] GetBoundsCorners(Bounds bounds)
{
Vector3[] corners = new Vector3[8];
Vector3 center = bounds.center;
Vector3 extents = bounds.extents;
corners[0] = center + new Vector3(-extents.x, -extents.y, -extents.z);
corners[1] = center + new Vector3(-extents.x, -extents.y, extents.z);
corners[2] = center + new Vector3(-extents.x, extents.y, -extents.z);
corners[3] = center + new Vector3(-extents.x, extents.y, extents.z);
corners[4] = center + new Vector3(extents.x, -extents.y, -extents.z);
corners[5] = center + new Vector3(extents.x, -extents.y, extents.z);
corners[6] = center + new Vector3(extents.x, extents.y, -extents.z);
corners[7] = center + new Vector3(extents.x, extents.y, extents.z);
return corners;
}
/// <summary>
/// 处理可见性变化
/// </summary>
private void HandleVisibilityChange(Renderer renderer, bool becameVisible, float visibilityRatio)
{
// 检查事件冷却
if (IsEventOnCooldown(renderer))
{
return;
}
OcclusionEventType eventType = becameVisible ?
OcclusionEventType.BecameVisible :
OcclusionEventType.BecameInvisible;
var eventData = new OcclusionEventData(renderer, eventType, visibilityRatio);
// 触发相应事件
if (becameVisible)
{
onBecameVisible.Invoke(eventData);
}
else
{
onBecameInvisible.Invoke(eventData);
}
// 记录事件时间
RecordEventTime(renderer);
Debug.Log($"遮挡事件: {renderer.name} {(becameVisible ? "变为可见" : "变为不可见")}");
}
/// <summary>
/// 处理可见性比率变化
/// </summary>
private void HandleVisibilityRatioChange(Renderer renderer, float currentRatio, OcclusionState state)
{
// 检查是否达到完全可见阈值
if (currentRatio >= fullVisibilityThreshold &&
state.lastVisibilityRatio < fullVisibilityThreshold)
{
TriggerFullyVisibleEvent(renderer, currentRatio);
}
// 检查是否进入部分遮挡状态
else if (currentRatio < fullVisibilityThreshold &&
currentRatio > partialOcclusionThreshold &&
state.lastVisibilityRatio >= fullVisibilityThreshold)
{
TriggerPartialOcclusionEvent(renderer, currentRatio);
}
}
/// <summary>
/// 触发完全可见事件
/// </summary>
private void TriggerFullyVisibleEvent(Renderer renderer, float visibilityRatio)
{
if (IsEventOnCooldown(renderer))
{
return;
}
var eventData = new OcclusionEventData(renderer, OcclusionEventType.FullyVisible, visibilityRatio);
onFullyVisible.Invoke(eventData);
RecordEventTime(renderer);
}
/// <summary>
/// 触发部分遮挡事件
/// </summary>
private void TriggerPartialOcclusionEvent(Renderer renderer, float visibilityRatio)
{
if (IsEventOnCooldown(renderer))
{
return;
}
var eventData = new OcclusionEventData(renderer, OcclusionEventType.PartialOcclusion, visibilityRatio);
onPartialOcclusion.Invoke(eventData);
RecordEventTime(renderer);
}
/// <summary>
/// 更新遮挡状态
/// </summary>
private void UpdateOcclusionState(Renderer renderer, OcclusionState state, bool isVisible, float visibilityRatio)
{
state.wasVisible = isVisible;
state.lastVisibilityRatio = visibilityRatio;
if (isVisible)
{
state.consecutiveFramesVisible++;
state.consecutiveFramesInvisible = 0;
}
else
{
state.consecutiveFramesInvisible++;
state.consecutiveFramesVisible = 0;
}
// 记录位置变化
if (Vector3.Distance(renderer.transform.position, state.lastRecordedPosition) > 0.1f)
{
state.lastRecordedPosition = renderer.transform.position;
}
}
/// <summary>
/// 检查事件是否在冷却中
/// </summary>
private bool IsEventOnCooldown(Renderer renderer)
{
if (lastEventTimes.TryGetValue(renderer, out float lastTime))
{
return Time.time - lastTime < eventCooldown;
}
return false;
}
/// <summary>
/// 记录事件时间
/// </summary>
private void RecordEventTime(Renderer renderer)
{
lastEventTimes[renderer] = Time.time;
}
/// <summary>
/// 注册动态对象
/// </summary>
public void RegisterDynamicObject(Renderer dynamicRenderer)
{
if (!objectStates.ContainsKey(dynamicRenderer))
{
objectStates[dynamicRenderer] = new OcclusionState
{
wasVisible = dynamicRenderer.enabled,
lastVisibilityRatio = dynamicRenderer.enabled ? 1.0f : 0.0f,
consecutiveFramesVisible = dynamicRenderer.enabled ? 1 : 0,
consecutiveFramesInvisible = dynamicRenderer.enabled ? 0 : 1,
lastRecordedPosition = dynamicRenderer.transform.position
};
// 也注册到遮挡剔除器
if (occlusionCuller != null)
{
occlusionCuller.RegisterDynamicOccluder(dynamicRenderer);
}
}
}
/// <summary>
/// 注销对象
/// </summary>
public void UnregisterObject(Renderer renderer)
{
objectStates.Remove(renderer);
lastEventTimes.Remove(renderer);
}
/// <summary>
/// 获取对象可见性信息
/// </summary>
public VisibilityInfo GetVisibilityInfo(Renderer renderer)
{
if (objectStates.TryGetValue(renderer, out OcclusionState state))
{
return new VisibilityInfo
{
isVisible = state.wasVisible,
visibilityRatio = state.lastVisibilityRatio,
consecutiveVisibleFrames = state.consecutiveFramesVisible,
consecutiveInvisibleFrames = state.consecutiveFramesInvisible,
lastKnownPosition = state.lastRecordedPosition
};
}
return new VisibilityInfo { isVisible = renderer.enabled };
}
/// <summary>
/// 可见性信息结构
/// </summary>
public struct VisibilityInfo
{
public bool isVisible;
public float visibilityRatio;
public int consecutiveVisibleFrames;
public int consecutiveInvisibleFrames;
public Vector3 lastKnownPosition;
}
}
}
10.3 静态批处理与动态批处理优化技术
10.3.1 静态批处理的高级配置策略
静态批处理是Unity中减少绘制调用的关键技术,通过合并多个静态网格来减少CPU到GPU的通信开销。在商业项目中,需要平衡批处理效益和内存消耗。
以下是一个高级静态批处理管理器的实现:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace CommercialGame.BatchingSystem
{
/// <summary>
/// 高级静态批处理管理器
/// </summary>
public class AdvancedStaticBatching : MonoBehaviour
{
[System.Serializable]
public class BatchSettings
{
public string batchName;
public List<GameObject> batchObjects;
public Material sharedMaterial;
public int vertexLimit = 64000;
public bool enableColliders = true;
public bool preserveIndividualTransforms = false;
}
[Header("批处理配置")]
[SerializeField]
private List<BatchSettings> batchSettings = new List<BatchSettings>();
[SerializeField]
private bool autoCollectStaticObjects = true;
[SerializeField]
private string staticObjectTag = "Static";
[SerializeField]
private int maxBatchesPerFrame = 5;
[Header("优化设置")]
[SerializeField]
private bool enableMeshCompression = true;
[SerializeField]
private bool removeDuplicateVertices = true;
[SerializeField]
private float weldVertexDistance = 0.001f;
private Dictionary<string, GameObject> batchRoots;
private Dictionary<Material, List<MeshFilter>> materialGroups;
private Queue<BatchSettings> batchQueue;
/// <summary>
/// 初始化批处理系统
/// </summary>
private void Start()
{
batchRoots = new Dictionary<string, GameObject>();
materialGroups = new Dictionary<Material, List<MeshFilter>>();
batchQueue = new Queue<BatchSettings>();
if (autoCollectStaticObjects)
{
CollectStaticObjects();
}
ProcessBatchQueue();
Debug.Log($"静态批处理系统初始化完成,{batchSettings.Count} 个批次待处理");
}
/// <summary>
/// 收集静态对象
/// </summary>
private void CollectStaticObjects()
{
GameObject[] staticObjects = GameObject.FindGameObjectsWithTag(staticObjectTag);
// 按材质分组
Dictionary<Material, List<GameObject>> materialObjectGroups = new Dictionary<Material, List<GameObject>>();
foreach (GameObject obj in staticObjects)
{
if (!obj.isStatic)
{
continue;
}
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer == null || renderer.sharedMaterial == null)
{
continue;
}
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null)
{
continue;
}
Material material = renderer.sharedMaterial;
if (!materialObjectGroups.ContainsKey(material))
{
materialObjectGroups[material] = new List<GameObject>();
}
materialObjectGroups[material].Add(obj);
}
// 创建批次设置
int batchIndex = 0;
foreach (var kvp in materialObjectGroups)
{
var batch = new BatchSettings
{
batchName = $"StaticBatch_{batchIndex++}",
batchObjects = kvp.Value,
sharedMaterial = kvp.Key
};
batchSettings.Add(batch);
batchQueue.Enqueue(batch);
}
}
/// <summary>
/// 处理批处理队列
/// </summary>
private void ProcessBatchQueue()
{
StartCoroutine(ProcessBatchesOverTime());
}
/// <summary>
/// 分帧处理批次
/// </summary>
private System.Collections.IEnumerator ProcessBatchesOverTime()
{
int processedThisFrame = 0;
while (batchQueue.Count > 0)
{
BatchSettings settings = batchQueue.Dequeue();
ProcessSingleBatch(settings);
processedThisFrame++;
if (processedThisFrame >= maxBatchesPerFrame)
{
processedThisFrame = 0;
yield return null;
}
}
Debug.Log("所有批次处理完成");
}
/// <summary>
/// 处理单个批次
/// </summary>
private void ProcessSingleBatch(BatchSettings settings)
{
if (settings.batchObjects == null || settings.batchObjects.Count == 0)
{
Debug.LogWarning($"批次 '{settings.batchName}' 没有对象");
return;
}
// 创建批处理根对象
GameObject batchRoot = new GameObject(settings.batchName);
batchRoot.isStatic = true;
batchRoot.transform.SetParent(transform);
// 收集网格和变换信息
List<CombineInstance> combineInstances = new List<CombineInstance>();
List<Transform> originalTransforms = new List<Transform>();
foreach (GameObject obj in settings.batchObjects)
{
if (obj == null)
{
continue;
}
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null)
{
continue;
}
// 创建组合实例
CombineInstance combineInstance = new CombineInstance
{
mesh = meshFilter.sharedMesh,
transform = meshFilter.transform.localToWorldMatrix,
subMeshIndex = 0
};
combineInstances.Add(combineInstance);
originalTransforms.Add(obj.transform);
// 禁用原始渲染器(可选)
Renderer objRenderer = obj.GetComponent<Renderer>();
if (objRenderer != null)
{
objRenderer.enabled = false;
}
}
if (combineInstances.Count == 0)
{
Destroy(batchRoot);
return;
}
// 检查顶点数限制
int totalVertices = combineInstances.Sum(ci => ci.mesh.vertexCount);
if (totalVertices > settings.vertexLimit)
{
// 需要分割批次
List<List<CombineInstance>> splitBatches = SplitBatchByVertexLimit(combineInstances, settings.vertexLimit);
CreateMultipleBatches(splitBatches, settings, batchRoot, originalTransforms);
}
else
{
// 创建单个合并网格
CreateCombinedMesh(combineInstances, settings, batchRoot, originalTransforms);
}
batchRoots[settings.batchName] = batchRoot;
Debug.Log($"批次 '{settings.batchName}' 处理完成,合并了 {combineInstances.Count} 个网格");
}
/// <summary>
/// 按顶点限制分割批次
/// </summary>
private List<List<CombineInstance>> SplitBatchByVertexLimit(List<CombineInstance> combineInstances, int vertexLimit)
{
List<List<CombineInstance>> splitBatches = new List<List<CombineInstance>>();
List<CombineInstance> currentBatch = new List<CombineInstance>();
int currentVertexCount = 0;
foreach (CombineInstance instance in combineInstances)
{
int meshVertices = instance.mesh.vertexCount;
if (currentVertexCount + meshVertices > vertexLimit && currentBatch.Count > 0)
{
splitBatches.Add(new List<CombineInstance>(currentBatch));
currentBatch.Clear();
currentVertexCount = 0;
}
currentBatch.Add(instance);
currentVertexCount += meshVertices;
}
if (currentBatch.Count > 0)
{
splitBatches.Add(currentBatch);
}
return splitBatches;
}
/// <summary>
/// 创建多个批次
/// </summary>
private void CreateMultipleBatches(List<List<CombineInstance>> splitBatches, BatchSettings settings,
GameObject rootObject, List<Transform> originalTransforms)
{
for (int i = 0; i < splitBatches.Count; i++)
{
GameObject subBatchObject = new GameObject($"{settings.batchName}_Part{i}");
subBatchObject.transform.SetParent(rootObject.transform);
subBatchObject.isStatic = true;
CreateCombinedMesh(splitBatches[i], settings, subBatchObject, originalTransforms);
}
}
/// <summary>
/// 创建合并的网格
/// </summary>
private void CreateCombinedMesh(List<CombineInstance> combineInstances, BatchSettings settings,
GameObject batchObject, List<Transform> originalTransforms)
{
// 创建新网格
Mesh combinedMesh = new Mesh();
combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
// 合并网格
combinedMesh.CombineMeshes(combineInstances.ToArray(), true, true);
// 网格优化
OptimizeMesh(combinedMesh);
// 添加网格组件
MeshFilter meshFilter = batchObject.AddComponent<MeshFilter>();
meshFilter.sharedMesh = combinedMesh;
MeshRenderer meshRenderer = batchObject.AddComponent<MeshRenderer>();
meshRenderer.sharedMaterial = settings.sharedMaterial;
// 添加碰撞体(可选)
if (settings.enableColliders)
{
AddOptimizedCollider(batchObject, combinedMesh);
}
// 保存原始变换信息(如果需要)
if (settings.preserveIndividualTransforms)
{
SaveOriginalTransforms(batchObject, originalTransforms);
}
}
/// <summary>
/// 优化网格
/// </summary>
private void OptimizeMesh(Mesh mesh)
{
if (removeDuplicateVertices)
{
RemoveDuplicateVertices(mesh, weldVertexDistance);
}
if (enableMeshCompression)
{
mesh.Optimize();
mesh.OptimizeIndexBuffers();
mesh.OptimizeReorderVertexBuffer();
}
mesh.UploadMeshData(false); // 不标记为可读写以节省内存
}
/// <summary>
/// 移除重复顶点
/// </summary>
private void RemoveDuplicateVertices(Mesh mesh, float weldDistance)
{
// 简化的重复顶点移除实现
// 在实际项目中,可能需要更复杂的实现
Vector3[] vertices = mesh.vertices;
Dictionary<Vector3, List<int>> vertexMap = new Dictionary<Vector3, List<int>>();
// 这里应该实现顶点焊接算法
// 为简化示例,直接优化网格
mesh.Optimize();
}
/// <summary>
/// 添加优化碰撞体
/// </summary>
private void AddOptimizedCollider(GameObject gameObject, Mesh mesh)
{
// 根据网格大小选择合适的碰撞体类型
Bounds meshBounds = mesh.bounds;
float volume = meshBounds.size.x * meshBounds.size.y * meshBounds.size.z;
if (volume < 10.0f) // 小对象使用网格碰撞体
{
MeshCollider meshCollider = gameObject.AddComponent<MeshCollider>();
meshCollider.sharedMesh = mesh;
meshCollider.convex = false;
}
else // 大对象使用简化碰撞体
{
BoxCollider boxCollider = gameObject.AddComponent<BoxCollider>();
boxCollider.center = meshBounds.center;
boxCollider.size = meshBounds.size;
}
}
/// <summary>
/// 保存原始变换信息
/// </summary>
private void SaveOriginalTransforms(GameObject batchObject, List<Transform> originalTransforms)
{
var transformData = batchObject.AddComponent<BatchTransformData>();
transformData.originalTransforms = originalTransforms.ToArray();
}
/// <summary>
/// 手动添加批次
/// </summary>
public void AddBatch(BatchSettings settings)
{
batchSettings.Add(settings);
batchQueue.Enqueue(settings);
}
/// <summary>
/// 移除批次
/// </summary>
public void RemoveBatch(string batchName)
{
if (batchRoots.TryGetValue(batchName, out GameObject batchRoot))
{
Destroy(batchRoot);
batchRoots.Remove(batchName);
// 重新启用原始对象
var batch = batchSettings.Find(b => b.batchName == batchName);
if (batch != null)
{
foreach (GameObject obj in batch.batchObjects)
{
if (obj != null)
{
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer != null)
{
renderer.enabled = true;
}
}
}
batchSettings.Remove(batch);
}
}
}
/// <summary>
/// 获取批处理统计信息
/// </summary>
public BatchStatistics GetStatistics()
{
int totalBatches = batchRoots.Count;
int totalOriginalObjects = batchSettings.Sum(b => b.batchObjects.Count);
int totalCombinedObjects = batchRoots.Values
.Sum(root => root.GetComponentsInChildren<MeshFilter>().Length);
return new BatchStatistics
{
totalBatches = totalBatches,
totalOriginalObjects = totalOriginalObjects,
totalCombinedObjects = totalCombinedObjects,
reductionRatio = (float)totalCombinedObjects / totalOriginalObjects
};
}
/// <summary>
/// 批处理统计信息
/// </summary>
public struct BatchStatistics
{
public int totalBatches;
public int totalOriginalObjects;
public int totalCombinedObjects;
public float reductionRatio;
}
}
/// <summary>
/// 批处理变换数据组件
/// </summary>
public class BatchTransformData : MonoBehaviour
{
public Transform[] originalTransforms;
}
}
10.3.2 动态批处理的智能管理系统
动态批处理针对移动对象,需要更智能的管理策略。以下实现了一个智能动态批处理系统:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
namespace CommercialGame.BatchingSystem
{
/// <summary>
/// 智能动态批处理管理器
/// </summary>
public class IntelligentDynamicBatching : MonoBehaviour
{
[System.Serializable]
public class DynamicBatchCriteria
{
public string criteriaName;
public Material targetMaterial;
public int maxBatchSize = 300;
public float maxDistanceBetweenObjects = 50.0f;
public float minScreenSize = 0.01f;
public bool allowDifferentMeshes = true;
public bool considerMovementSpeed = true;
public float maxMovementSpeed = 10.0f;
}
[Header("批处理标准")]
[SerializeField]
private List<DynamicBatchCriteria> batchCriterias = new List<DynamicBatchCriteria>();
[Header("性能设置")]
[SerializeField]
private int maxBatchesPerFrame = 3;
[SerializeField]
private float rebatchInterval = 2.0f;
[SerializeField]
private bool enableDistanceBasedGrouping = true;
[SerializeField]
private float groupingCellSize = 20.0f;
[Header("调试设置")]
[SerializeField]
private bool visualizeBatches = false;
[SerializeField]
private Color[] batchColors;
private class DynamicBatch
{
public string batchId;
public List<GameObject> batchedObjects;
public GameObject batchContainer;
public Material batchMaterial;
public float lastUpdateTime;
public bool needsRebatch;
public Color debugColor;
}
private class TrackedObject
{
public GameObject gameObject;
public Renderer renderer;
public MeshFilter meshFilter;
public Material material;
public Vector3 lastPosition;
public float lastMovementTime;
public string currentBatchId;
public bool isEligibleForBatching;
}
private Dictionary<string, DynamicBatch> activeBatches;
private Dictionary<GameObject, TrackedObject> trackedObjects;
private Queue<GameObject> objectProcessingQueue;
private float lastRebatchTime;
private int frameCount;
/// <summary>
/// 初始化动态批处理系统
/// </summary>
private void Start()
{
InitializeBatchingSystem();
// 初始收集可批处理对象
CollectEligibleObjects();
Debug.Log("智能动态批处理系统初始化完成");
}
/// <summary>
/// 初始化批处理系统
/// </summary>
private void InitializeBatchingSystem()
{
activeBatches = new Dictionary<string, DynamicBatch>();
trackedObjects = new Dictionary<GameObject, TrackedObject>();
objectProcessingQueue = new Queue<GameObject>();
lastRebatchTime = Time.time;
// 初始化调试颜色
if (batchColors == null || batchColors.Length == 0)
{
batchColors = new Color[]
{
Color.red, Color.green, Color.blue, Color.yellow,
Color.cyan, Color.magenta, Color.white
};
}
}
/// <summary>
/// 收集符合条件的对象
/// </summary>
private void CollectEligibleObjects()
{
// 查找所有动态对象
GameObject[] allObjects = FindObjectsOfType<GameObject>();
foreach (GameObject obj in allObjects)
{
if (obj.isStatic)
{
continue; // 跳过静态对象
}
Renderer renderer = obj.GetComponent<Renderer>();
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (renderer == null || meshFilter == null)
{
continue;
}
if (renderer.sharedMaterial == null)
{
continue;
}
// 检查是否符合批处理条件
DynamicBatchCriteria criteria = FindMatchingCriteria(renderer.sharedMaterial);
if (criteria == null)
{
continue;
}
// 注册跟踪对象
RegisterTrackedObject(obj, renderer, meshFilter, criteria);
}
}
/// <summary>
/// 查找匹配的批处理条件
/// </summary>
private DynamicBatchCriteria FindMatchingCriteria(Material material)
{
foreach (DynamicBatchCriteria criteria in batchCriterias)
{
if (criteria.targetMaterial == material)
{
return criteria;
}
}
return null;
}
/// <summary>
/// 注册跟踪对象
/// </summary>
private void RegisterTrackedObject(GameObject obj, Renderer renderer,
MeshFilter meshFilter, DynamicBatchCriteria criteria)
{
var trackedObj = new TrackedObject
{
gameObject = obj,
renderer = renderer,
meshFilter = meshFilter,
material = renderer.sharedMaterial,
lastPosition = obj.transform.position,
lastMovementTime = Time.time,
currentBatchId = null,
isEligibleForBatching = true
};
trackedObjects[obj] = trackedObj;
objectProcessingQueue.Enqueue(obj);
}
/// <summary>
/// 每帧更新
/// </summary>
private void Update()
{
frameCount++;
// 处理对象队列
ProcessObjectQueue();
// 定期重新批处理
if (Time.time - lastRebatchTime > rebatchInterval)
{
RevalidateAllBatches();
lastRebatchTime = Time.time;
}
// 更新批处理状态
UpdateBatchStates();
// 调试可视化
if (visualizeBatches)
{
VisualizeBatches();
}
}
/// <summary>
/// 处理对象队列
/// </summary>
private void ProcessObjectQueue()
{
int processedThisFrame = 0;
while (objectProcessingQueue.Count > 0 && processedThisFrame < maxBatchesPerFrame)
{
GameObject obj = objectProcessingQueue.Dequeue();
if (obj == null || !trackedObjects.ContainsKey(obj))
{
continue;
}
TrackedObject trackedObj = trackedObjects[obj];
if (!trackedObj.isEligibleForBatching)
{
continue;
}
// 尝试将对象添加到现有批次或创建新批次
AssignObjectToBatch(trackedObj);
processedThisFrame++;
}
}
/// <summary>
/// 将对象分配到批次
/// </summary>
private void AssignObjectToBatch(TrackedObject trackedObj)
{
DynamicBatchCriteria criteria = FindMatchingCriteria(trackedObj.material);
if (criteria == null)
{
trackedObj.isEligibleForBatching = false;
return;
}
// 查找合适的现有批次
DynamicBatch bestBatch = FindBestBatchForObject(trackedObj, criteria);
if (bestBatch != null)
{
// 添加到现有批次
AddObjectToBatch(trackedObj, bestBatch);
}
else
{
// 创建新批次
CreateNewBatch(trackedObj, criteria);
}
}
/// <summary>
/// 查找最适合对象的批次
/// </summary>
private DynamicBatch FindBestBatchForObject(TrackedObject trackedObj, DynamicBatchCriteria criteria)
{
DynamicBatch bestBatch = null;
float bestScore = float.MinValue;
foreach (var kvp in activeBatches)
{
DynamicBatch batch = kvp.Value;
if (batch.batchMaterial != trackedObj.material)
{
continue;
}
if (batch.batchedObjects.Count >= criteria.maxBatchSize)
{
continue;
}
// 计算批次适合度分数
float score = CalculateBatchFitnessScore(trackedObj, batch, criteria);
if (score > bestScore)
{
bestScore = score;
bestBatch = batch;
}
}
return bestBatch;
}
/// <summary>
/// 计算批次适合度分数
/// </summary>
private float CalculateBatchFitnessScore(TrackedObject trackedObj, DynamicBatch batch,
DynamicBatchCriteria criteria)
{
float score = 0.0f;
// 基于距离的评分
if (enableDistanceBasedGrouping && batch.batchedObjects.Count > 0)
{
Vector3 avgPosition = CalculateAverageBatchPosition(batch);
float distance = Vector3.Distance(trackedObj.gameObject.transform.position, avgPosition);
if (distance <= criteria.maxDistanceBetweenObjects)
{
score += (criteria.maxDistanceBetweenObjects - distance) / criteria.maxDistanceBetweenObjects;
}
else
{
return float.MinValue; // 距离太远,不适合此批次
}
}
// 基于屏幕大小的评分
float screenSize = CalculateScreenSpaceSize(trackedObj.gameObject);
if (screenSize >= criteria.minScreenSize)
{
score += screenSize * 10.0f;
}
// 基于运动状态的评分
if (criteria.considerMovementSpeed)
{
float movementSpeed = CalculateMovementSpeed(trackedObj);
if (movementSpeed <= criteria.maxMovementSpeed)
{
score += (criteria.maxMovementSpeed - movementSpeed) / criteria.maxMovementSpeed;
}
}
return score;
}
/// <summary>
/// 计算批次的平均位置
/// </summary>
private Vector3 CalculateAverageBatchPosition(DynamicBatch batch)
{
Vector3 sum = Vector3.zero;
foreach (GameObject obj in batch.batchedObjects)
{
sum += obj.transform.position;
}
return sum / batch.batchedObjects.Count;
}
/// <summary>
/// 计算屏幕空间大小
/// </summary>
private float CalculateScreenSpaceSize(GameObject obj)
{
Camera mainCamera = Camera.main;
if (mainCamera == null)
{
return 1.0f;
}
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer == null)
{
return 0.0f;
}
Bounds bounds = renderer.bounds;
Vector3 screenSize = mainCamera.WorldToScreenPoint(bounds.max) -
mainCamera.WorldToScreenPoint(bounds.min);
return Mathf.Max(Mathf.Abs(screenSize.x), Mathf.Abs(screenSize.y)) / Screen.height;
}
/// <summary>
/// 计算运动速度
/// </summary>
private float CalculateMovementSpeed(TrackedObject trackedObj)
{
float deltaTime = Time.time - trackedObj.lastMovementTime;
if (deltaTime < 0.001f)
{
return 0.0f;
}
float distance = Vector3.Distance(trackedObj.gameObject.transform.position, trackedObj.lastPosition);
return distance / deltaTime;
}
/// <summary>
/// 将对象添加到批次
/// </summary>
private void AddObjectToBatch(TrackedObject trackedObj, DynamicBatch batch)
{
// 禁用原始渲染器
trackedObj.renderer.enabled = false;
// 添加到批次列表
batch.batchedObjects.Add(trackedObj.gameObject);
batch.lastUpdateTime = Time.time;
// 更新批次网格
UpdateBatchMesh(batch);
trackedObj.currentBatchId = batch.batchId;
}
/// <summary>
/// 创建新批次
/// </summary>
private void CreateNewBatch(TrackedObject trackedObj, DynamicBatchCriteria criteria)
{
string batchId = $"DynamicBatch_{activeBatches.Count}_{System.Guid.NewGuid().ToString().Substring(0, 8)}";
// 创建批次容器
GameObject batchContainer = new GameObject(batchId);
batchContainer.transform.SetParent(transform);
// 创建批次对象
var batch = new DynamicBatch
{
batchId = batchId,
batchedObjects = new List<GameObject> { trackedObj.gameObject },
batchContainer = batchContainer,
batchMaterial = trackedObj.material,
lastUpdateTime = Time.time,
needsRebatch = false,
debugColor = batchColors[activeBatches.Count % batchColors.Length]
};
activeBatches[batchId] = batch;
// 禁用原始渲染器
trackedObj.renderer.enabled = false;
trackedObj.currentBatchId = batchId;
// 创建批次网格
UpdateBatchMesh(batch);
Debug.Log($"创建新动态批次: {batchId}");
}
/// <summary>
/// 更新批次网格
/// </summary>
private void UpdateBatchMesh(DynamicBatch batch)
{
// 收集组合实例
List<CombineInstance> combineInstances = new List<CombineInstance>();
foreach (GameObject obj in batch.batchedObjects)
{
if (obj == null)
{
continue;
}
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null)
{
continue;
}
CombineInstance combineInstance = new CombineInstance
{
mesh = meshFilter.sharedMesh,
transform = meshFilter.transform.localToWorldMatrix,
subMeshIndex = 0
};
combineInstances.Add(combineInstance);
}
if (combineInstances.Count == 0)
{
return;
}
// 清理旧的网格组件
MeshFilter oldFilter = batch.batchContainer.GetComponent<MeshFilter>();
MeshRenderer oldRenderer = batch.batchContainer.GetComponent<MeshRenderer>();
if (oldFilter != null) Destroy(oldFilter);
if (oldRenderer != null) Destroy(oldRenderer);
// 创建新网格
Mesh combinedMesh = new Mesh();
combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
combinedMesh.CombineMeshes(combineInstances.ToArray(), true, true);
// 添加新组件
MeshFilter newFilter = batch.batchContainer.AddComponent<MeshFilter>();
newFilter.sharedMesh = combinedMesh;
MeshRenderer newRenderer = batch.batchContainer.AddComponent<MeshRenderer>();
newRenderer.sharedMaterial = batch.batchMaterial;
// 优化网格
combinedMesh.Optimize();
combinedMesh.UploadMeshData(false);
}
/// <summary>
/// 重新验证所有批次
/// </summary>
private void RevalidateAllBatches()
{
foreach (var kvp in activeBatches)
{
DynamicBatch batch = kvp.Value;
RevalidateBatch(batch);
}
}
/// <summary>
/// 重新验证批次
/// </summary>
private void RevalidateBatch(DynamicBatch batch)
{
// 检查批次中的每个对象是否仍然符合条件
List<GameObject> objectsToRemove = new List<GameObject>();
foreach (GameObject obj in batch.batchedObjects)
{
if (obj == null)
{
objectsToRemove.Add(obj);
continue;
}
if (!trackedObjects.ContainsKey(obj))
{
objectsToRemove.Add(obj);
continue;
}
TrackedObject trackedObj = trackedObjects[obj];
// 检查对象是否仍然符合批处理条件
if (!IsObjectStillEligible(trackedObj))
{
objectsToRemove.Add(obj);
}
}
// 移除不符合条件的对象
foreach (GameObject obj in objectsToRemove)
{
RemoveObjectFromBatch(obj, batch);
}
// 如果批次为空,销毁它
if (batch.batchedObjects.Count == 0)
{
Destroy(batch.batchContainer);
activeBatches.Remove(batch.batchId);
}
else if (objectsToRemove.Count > 0)
{
// 更新批次网格
UpdateBatchMesh(batch);
}
}
/// <summary>
/// 检查对象是否仍然符合条件
/// </summary>
private bool IsObjectStillEligible(TrackedObject trackedObj)
{
if (trackedObj.gameObject == null)
{
return false;
}
// 检查对象是否被销毁
if (!trackedObj.renderer || !trackedObj.meshFilter)
{
return false;
}
// 检查材质是否改变
DynamicBatchCriteria criteria = FindMatchingCriteria(trackedObj.renderer.sharedMaterial);
if (criteria == null)
{
return false;
}
// 检查距离条件
if (enableDistanceBasedGrouping)
{
// 这里应该实现更精确的距离检查
}
// 检查运动速度
if (criteria.considerMovementSpeed)
{
float movementSpeed = CalculateMovementSpeed(trackedObj);
if (movementSpeed > criteria.maxMovementSpeed)
{
return false;
}
}
return true;
}
/// <summary>
/// 从批次中移除对象
/// </summary>
private void RemoveObjectFromBatch(GameObject obj, DynamicBatch batch)
{
batch.batchedObjects.Remove(obj);
// 重新启用原始渲染器
if (trackedObjects.TryGetValue(obj, out TrackedObject trackedObj))
{
if (trackedObj.renderer != null)
{
trackedObj.renderer.enabled = true;
}
trackedObj.currentBatchId = null;
}
}
/// <summary>
/// 更新批次状态
/// </summary>
private void UpdateBatchStates()
{
// 更新所有跟踪对象的位置信息
foreach (var kvp in trackedObjects)
{
TrackedObject trackedObj = kvp.Value;
if (trackedObj.gameObject != null)
{
trackedObj.lastPosition = trackedObj.gameObject.transform.position;
trackedObj.lastMovementTime = Time.time;
}
}
}
/// <summary>
/// 可视化批次
/// </summary>
private void VisualizeBatches()
{
foreach (var kvp in activeBatches)
{
DynamicBatch batch = kvp.Value;
if (batch.batchContainer != null)
{
// 绘制批次包围盒
Bounds batchBounds = CalculateBatchBounds(batch);
DebugDrawBounds(batchBounds, batch.debugColor);
}
}
}
/// <summary>
/// 计算批次包围盒
/// </summary>
private Bounds CalculateBatchBounds(DynamicBatch batch)
{
Bounds bounds = new Bounds();
bool first = true;
foreach (GameObject obj in batch.batchedObjects)
{
if (obj == null)
{
continue;
}
Renderer renderer = obj.GetComponent<Renderer>();
if (renderer == null)
{
continue;
}
if (first)
{
bounds = renderer.bounds;
first = false;
}
else
{
bounds.Encapsulate(renderer.bounds);
}
}
return bounds;
}
/// <summary>
/// 调试绘制包围盒
/// </summary>
private void DebugDrawBounds(Bounds bounds, Color color)
{
Vector3 center = bounds.center;
Vector3 size = bounds.size;
Vector3[] corners = new Vector3[8];
corners[0] = center + new Vector3(-size.x, -size.y, -size.z) * 0.5f;
corners[1] = center + new Vector3(-size.x, -size.y, size.z) * 0.5f;
corners[2] = center + new Vector3(-size.x, size.y, -size.z) * 0.5f;
corners[3] = center + new Vector3(-size.x, size.y, size.z) * 0.5f;
corners[4] = center + new Vector3(size.x, -size.y, -size.z) * 0.5f;
corners[5] = center + new Vector3(size.x, -size.y, size.z) * 0.5f;
corners[6] = center + new Vector3(size.x, size.y, -size.z) * 0.5f;
corners[7] = center + new Vector3(size.x, size.y, size.z) * 0.5f;
// 绘制包围盒边
Debug.DrawLine(corners[0], corners[1], color);
Debug.DrawLine(corners[0], corners[2], color);
Debug.DrawLine(corners[0], corners[4], color);
Debug.DrawLine(corners[1], corners[3], color);
Debug.DrawLine(corners[1], corners[5], color);
Debug.DrawLine(corners[2], corners[3], color);
Debug.DrawLine(corners[2], corners[6], color);
Debug.DrawLine(corners[3], corners[7], color);
Debug.DrawLine(corners[4], corners[5], color);
Debug.DrawLine(corners[4], corners[6], color);
Debug.DrawLine(corners[5], corners[7], color);
Debug.DrawLine(corners[6], corners[7], color);
}
/// <summary>
/// 注册新对象进行动态批处理
/// </summary>
public void RegisterDynamicObject(GameObject obj)
{
if (obj == null || obj.isStatic)
{
return;
}
Renderer renderer = obj.GetComponent<Renderer>();
MeshFilter meshFilter = obj.GetComponent<MeshFilter>();
if (renderer == null || meshFilter == null)
{
return;
}
DynamicBatchCriteria criteria = FindMatchingCriteria(renderer.sharedMaterial);
if (criteria == null)
{
return;
}
if (!trackedObjects.ContainsKey(obj))
{
RegisterTrackedObject(obj, renderer, meshFilter, criteria);
}
}
/// <summary>
/// 取消注册对象
/// </summary>
public void UnregisterObject(GameObject obj)
{
if (trackedObjects.TryGetValue(obj, out TrackedObject trackedObj))
{
// 如果对象在批次中,先移除它
if (!string.IsNullOrEmpty(trackedObj.currentBatchId))
{
if (activeBatches.TryGetValue(trackedObj.currentBatchId, out DynamicBatch batch))
{
RemoveObjectFromBatch(obj, batch);
}
}
trackedObjects.Remove(obj);
}
}
/// <summary>
/// 获取系统统计信息
/// </summary>
public DynamicBatchStatistics GetStatistics()
{
int totalBatches = activeBatches.Count;
int totalBatchedObjects = activeBatches.Values.Sum(batch => batch.batchedObjects.Count);
int totalTrackedObjects = trackedObjects.Count;
return new DynamicBatchStatistics
{
totalBatches = totalBatches,
totalBatchedObjects = totalBatchedObjects,
totalTrackedObjects = totalTrackedObjects,
batchingRatio = totalTrackedObjects > 0 ? (float)totalBatchedObjects / totalTrackedObjects : 0.0f
};
}
/// <summary>
/// 动态批处理统计信息
/// </summary>
public struct DynamicBatchStatistics
{
public int totalBatches;
public int totalBatchedObjects;
public int totalTrackedObjects;
public float batchingRatio;
}
}
}
10.4 导航网格系统的实现与优化
10.4.1 寻路系统的数学基础与配置
寻路系统的核心是A*(A-Star)算法,其评估函数为:f(n) = g(n) + h(n),其中g(n)是从起点到当前节点的实际代价,h(n)是启发式函数估计的当前节点到目标的代价。
在Unity的NavMesh系统中,还涉及以下数学概念:
- 网格生成:使用体素化和区域生长算法
- 路径平滑:使用贝塞尔曲线或Catmull-Rom样条
- 动态障碍:使用局部障碍图和势力场
以下是一个高级寻路系统的实现:
using UnityEngine;
using UnityEngine.AI;
using System.Collections.Generic;
using System.Linq;
namespace CommercialGame.NavigationSystem
{
/// <summary>
/// 高级导航网格管理器
/// </summary>
public class AdvancedNavMeshManager : MonoBehaviour
{
[System.Serializable]
public class NavMeshAreaConfig
{
public string areaName;
public int areaCost = 1;
public Color debugColor = Color.white;
public bool walkable = true;
}
[Header("导航网格配置")]
[SerializeField]
private List<NavMeshAreaConfig> areaConfigs = new List<NavMeshAreaConfig>();
[SerializeField]
private LayerMask navMeshBuildLayers = -1;
[SerializeField]
private float agentRadius = 0.5f;
[SerializeField]
private float agentHeight = 2.0f;
[SerializeField]
private float maxSlope = 45.0f;
[SerializeField]
private float stepHeight = 0.3f;
[Header("性能设置")]
[SerializeField]
private int maxPathFinders = 10;
[SerializeField]
private float pathUpdateInterval = 0.5f;
[SerializeField]
private bool useMultithreadedPathfinding = true;
[Header("动态障碍设置")]
[SerializeField]
private bool enableDynamicObstacles = true;
[SerializeField]
private float obstacleUpdateRate = 2.0f;
[SerializeField]
private float obstacleCarveRadius = 1.0f;
private Dictionary<string, NavMeshAreaConfig> areaConfigDictionary;
private List<NavMeshPathFinder> activePathFinders;
private Queue<PathRequest> pathRequestQueue;
private Dictionary<int, NavMeshObstacle> dynamicObstacles;
private NavMeshData navMeshData;
private NavMeshDataInstance navMeshInstance;
private float lastObstacleUpdateTime;
private class PathRequest
{
public Vector3 start;
public Vector3 end;
public int agentTypeId;
public System.Action<NavMeshPath, bool> callback;
public float timestamp;
}
/// <summary>
/// 初始化导航系统
/// </summary>
private void Awake()
{
InitializeNavigationSystem();
BuildNavMesh();
Debug.Log("高级导航网格系统初始化完成");
}
/// <summary>
/// 初始化导航系统
/// </summary>
private void InitializeNavigationSystem()
{
areaConfigDictionary = new Dictionary<string, NavMeshAreaConfig>();
activePathFinders = new List<NavMeshPathFinder>();
pathRequestQueue = new Queue<PathRequest>();
dynamicObstacles = new Dictionary<int, NavMeshObstacle>();
// 初始化区域配置字典
foreach (NavMeshAreaConfig config in areaConfigs)
{
areaConfigDictionary[config.areaName] = config;
}
// 创建路径查找器池
CreatePathFinderPool();
}
/// <summary>
/// 构建导航网格
/// </summary>
private void BuildNavMesh()
{
// 创建导航网格构建设置
NavMeshBuildSettings buildSettings = NavMesh.GetSettingsByID(0);
buildSettings.agentRadius = agentRadius;
buildSettings.agentHeight = agentHeight;
buildSettings.agentSlope = maxSlope;
buildSettings.agentClimb = stepHeight;
// 收集场景中的导航网格源
List<NavMeshBuildSource> sources = new List<NavMeshBuildSource>();
CollectNavMeshSources(sources);
// 定义导航网格边界
Bounds bounds = CalculateSceneBounds();
// 异步构建导航网格
NavMeshBuilder.UpdateNavMeshDataAsync(
navMeshData,
buildSettings,
sources,
bounds
);
// 注册导航网格数据
if (navMeshInstance.valid)
{
navMeshInstance.Remove();
}
navMeshInstance = NavMesh.AddNavMeshData(navMeshData);
}
/// <summary>
/// 收集导航网格源
/// </summary>
private void CollectNavMeshSources(List<NavMeshBuildSource> sources)
{
// 收集网格渲染器
MeshRenderer[] meshRenderers = FindObjectsOfType<MeshRenderer>();
foreach (MeshRenderer renderer in meshRenderers)
{
if (((1 << renderer.gameObject.layer) & navMeshBuildLayers) == 0)
{
continue;
}
MeshFilter meshFilter = renderer.GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null)
{
continue;
}
NavMeshBuildSource source = new NavMeshBuildSource
{
shape = NavMeshBuildSourceShape.Mesh,
sourceObject = meshFilter.sharedMesh,
transform = renderer.transform.localToWorldMatrix,
area = GetAreaFromGameObject(renderer.gameObject)
};
sources.Add(source);
}
// 收集地形
Terrain[] terrains = FindObjectsOfType<Terrain>();
foreach (Terrain terrain in terrains)
{
if (((1 << terrain.gameObject.layer) & navMeshBuildLayers) == 0)
{
continue;
}
NavMeshBuildSource source = new NavMeshBuildSource
{
shape = NavMeshBuildSourceShape.Terrain,
sourceObject = terrain.terrainData,
transform = terrain.transform.localToWorldMatrix,
area = GetAreaFromGameObject(terrain.gameObject)
};
sources.Add(source);
}
}
/// <summary>
/// 从游戏对象获取区域类型
/// </summary>
private int GetAreaFromGameObject(GameObject gameObject)
{
// 检查对象标签或组件以确定区域类型
foreach (var kvp in areaConfigDictionary)
{
NavMeshAreaConfig config = kvp.Value;
// 这里可以根据实际需求实现更复杂的逻辑
if (gameObject.CompareTag(kvp.Key))
{
return NavMesh.GetAreaFromName(kvp.Key);
}
}
return 0; // 默认区域
}
/// <summary>
/// 计算场景边界
/// </summary>
private Bounds CalculateSceneBounds()
{
// 收集所有需要包含在导航网格中的对象
List<Renderer> includedRenderers = new List<Renderer>();
Renderer[] allRenderers = FindObjectsOfType<Renderer>();
foreach (Renderer renderer in allRenderers)
{
if (((1 << renderer.gameObject.layer) & navMeshBuildLayers) != 0)
{
includedRenderers.Add(renderer);
}
}
if (includedRenderers.Count == 0)
{
return new Bounds(Vector3.zero, Vector3.one * 100);
}
// 计算合并的边界
Bounds bounds = includedRenderers[0].bounds;
for (int i = 1; i < includedRenderers.Count; i++)
{
bounds.Encapsulate(includedRenderers[i].bounds);
}
// 扩大边界以确保完全覆盖
bounds.Expand(10.0f);
return bounds;
}
/// <summary>
/// 创建路径查找器池
/// </summary>
private void CreatePathFinderPool()
{
for (int i = 0; i < maxPathFinders; i++)
{
var pathFinder = new NavMeshPathFinder();
activePathFinders.Add(pathFinder);
}
}
/// <summary>
/// 每帧更新
/// </summary>
private void Update()
{
UpdatePathRequests();
UpdateDynamicObstacles();
}
/// <summary>
/// 更新路径请求
/// </summary>
private void UpdatePathRequests()
{
if (pathRequestQueue.Count == 0)
{
return;
}
// 查找空闲的路径查找器
NavMeshPathFinder freeFinder = activePathFinders.Find(pf => !pf.IsProcessing);
if (freeFinder != null && pathRequestQueue.Count > 0)
{
PathRequest request = pathRequestQueue.Dequeue();
ProcessPathRequest(request, freeFinder);
}
}
/// <summary>
/// 处理路径请求
/// </summary>
private void ProcessPathRequest(PathRequest request, NavMeshPathFinder pathFinder)
{
pathFinder.IsProcessing = true;
if (useMultithreadedPathfinding)
{
// 在后台线程中计算路径
System.Threading.ThreadPool.QueueUserWorkItem(state =>
{
NavMeshPath path = new NavMeshPath();
bool success = NavMesh.CalculatePath(request.start, request.end, NavMesh.AllAreas, path);
// 回到主线程调用回调
this.RunOnMainThread(() =>
{
request.callback?.Invoke(path, success);
pathFinder.IsProcessing = false;
});
});
}
else
{
// 在主线程中计算路径
NavMeshPath path = new NavMeshPath();
bool success = NavMesh.CalculatePath(request.start, request.end, NavMesh.AllAreas, path);
request.callback?.Invoke(path, success);
pathFinder.IsProcessing = false;
}
}
/// <summary>
/// 更新动态障碍
/// </summary>
private void UpdateDynamicObstacles()
{
if (!enableDynamicObstacles || Time.time - lastObstacleUpdateTime < obstacleUpdateRate)
{
return;
}
// 更新所有动态障碍的位置和大小
foreach (var kvp in dynamicObstacles)
{
UpdateDynamicObstacle(kvp.Value);
}
lastObstacleUpdateTime = Time.time;
}
/// <summary>
/// 更新动态障碍
/// </summary>
private void UpdateDynamicObstacle(NavMeshObstacle obstacle)
{
if (obstance == null)
{
return;
}
// 更新障碍的雕刻(需要Unity 2019.3+)
obstacle.carving = true;
obstacle.carveOnlyStationary = false;
}
/// <summary>
/// 请求路径
/// </summary>
public void RequestPath(Vector3 start, Vector3 end, System.Action<NavMeshPath, bool> callback)
{
PathRequest request = new PathRequest
{
start = start,
end = end,
callback = callback,
timestamp = Time.time
};
pathRequestQueue.Enqueue(request);
}
/// <summary>
/// 添加动态障碍
/// </summary>
public int AddDynamicObstacle(Vector3 position, float radius, float height = 2.0f)
{
if (!enableDynamicObstacles)
{
return -1;
}
GameObject obstacleObj = new GameObject($"DynamicObstacle_{dynamicObstacles.Count}");
obstacleObj.transform.position = position;
NavMeshObstacle obstacle = obstacleObj.AddComponent<NavMeshObstacle>();
obstacle.shape = NavMeshObstacleShape.Capsule;
obstacle.radius = radius;
obstacle.height = height;
obstacle.carving = true;
obstacle.carveOnlyStationary = false;
int obstacleId = obstacle.GetInstanceID();
dynamicObstacles[obstacleId] = obstacle;
return obstacleId;
}
/// <summary>
/// 移除动态障碍
/// </summary>
public void RemoveDynamicObstacle(int obstacleId)
{
if (dynamicObstacles.TryGetValue(obstacleId, out NavMeshObstacle obstacle))
{
if (obstacle != null)
{
Destroy(obstacle.gameObject);
}
dynamicObstacles.Remove(obstacleId);
}
}
/// <summary>
/// 获取导航网格统计信息
/// </summary>
public NavMeshStatistics GetStatistics()
{
NavMeshTriangulation triangulation = NavMesh.CalculateTriangulation();
return new NavMeshStatistics
{
totalVertices = triangulation.vertices.Length,
totalTriangles = triangulation.indices.Length / 3,
totalAreas = areaConfigs.Count,
activePathFinders = activePathFinders.Count(pf => pf.IsProcessing),
queuedRequests = pathRequestQueue.Count,
dynamicObstacles = dynamicObstacles.Count
};
}
/// <summary>
/// 导航网格统计信息
/// </summary>
public struct NavMeshStatistics
{
public int totalVertices;
public int totalTriangles;
public int totalAreas;
public int activePathFinders;
public int queuedRequests;
public int dynamicObstacles;
}
/// <summary>
/// 路径查找器类
/// </summary>
private class NavMeshPathFinder
{
public bool IsProcessing { get; set; }
public float LastUseTime { get; set; }
}
}
/// <summary>
/// Unity主线程运行助手
/// </summary>
public static class UnityThreadHelper
{
private static System.Action pendingActions;
private static readonly object lockObject = new object();
public static void RunOnMainThread(System.Action action)
{
lock (lockObject)
{
pendingActions += action;
}
}
public static void ExecutePendingActions()
{
System.Action actionsToExecute = null;
lock (lockObject)
{
if (pendingActions != null)
{
actionsToExecute = pendingActions;
pendingActions = null;
}
}
actionsToExecute?.Invoke();
}
}
}
10.4.2 寻路路径的优化与平滑处理
寻路得到的原始路径通常由直线段组成,需要通过平滑处理来获得更自然的移动路径。常用的平滑算法包括:
- 贝塞尔曲线平滑:使用二次或三次贝塞尔曲线
- Catmull-Rom样条:保证通过所有控制点
- 梯度下降优化:通过迭代优化路径点位置
以下是一个路径平滑系统的实现:
using UnityEngine;
using UnityEngine.AI;
using System.Collections.Generic;
namespace CommercialGame.NavigationSystem
{
/// <summary>
/// 路径平滑优化器
/// </summary>
public class PathSmoother : MonoBehaviour
{
[System.Serializable]
public class SmoothingSettings
{
public SmoothingAlgorithm algorithm = SmoothingAlgorithm.Bezier;
[Range(0, 1)]
public float smoothingFactor = 0.5f;
public int iterations = 3;
public float maxDeviation = 0.5f;
public bool preserveCorners = true;
public float cornerAngleThreshold = 45.0f;
}
public enum SmoothingAlgorithm
{
Linear,
Bezier,
CatmullRom,
Chaikin
}
[Header("平滑设置")]
[SerializeField]
private SmoothingSettings defaultSettings = new SmoothingSettings();
[Header("性能设置")]
[SerializeField]
private int maxPathPoints = 1024;
[SerializeField]
private bool useSimplification = true;
[SerializeField]
private float simplificationTolerance = 0.1f;
private Dictionary<int, SmoothingSettings> agentSettings;
/// <summary>
/// 初始化路径平滑器
/// </summary>
private void Awake()
{
agentSettings = new Dictionary<int, SmoothingSettings>();
}
/// <summary>
/// 平滑路径
/// </summary>
public Vector3[] SmoothPath(Vector3[] originalPath, int agentId = 0)
{
if (originalPath == null || originalPath.Length < 2)
{
return originalPath;
}
// 获取代理的平滑设置
SmoothingSettings settings = GetAgentSettings(agentId);
// 简化路径(可选)
Vector3[] simplifiedPath = originalPath;
if (useSimplification && originalPath.Length > 3)
{
simplifiedPath = SimplifyPath(originalPath, settings);
}
// 应用平滑算法
Vector3[] smoothedPath = ApplySmoothingAlgorithm(simplifiedPath, settings);
// 确保路径仍然有效
smoothedPath = ValidateSmoothedPath(smoothedPath, originalPath, settings);
return smoothedPath;
}
/// <summary>
/// 简化路径
/// </summary>
private Vector3[] SimplifyPath(Vector3[] path, SmoothingSettings settings)
{
List<Vector3> simplified = new List<Vector3>();
// 添加起点
simplified.Add(path[0]);
// Douglas-Peucker算法简化
DouglasPeucker(path, 0, path.Length - 1, simplificationTolerance, ref simplified);
// 添加终点
if (!simplified.Contains(path[path.Length - 1]))
{
simplified.Add(path[path.Length - 1]);
}
return simplified.ToArray();
}
/// <summary>
/// Douglas-Peucker路径简化算法
/// </summary>
private void DouglasPeucker(Vector3[] points, int startIndex, int endIndex,
float tolerance, ref List<Vector3> result)
{
if (endIndex <= startIndex + 1)
{
return;
}
float maxDistance = 0;
int maxIndex = startIndex;
Vector3 startPoint = points[startIndex];
Vector3 endPoint = points[endIndex];
// 找到离线段最远的点
for (int i = startIndex + 1; i < endIndex; i++)
{
float distance = PointToLineDistance(points[i], startPoint, endPoint);
if (distance > maxDistance)
{
maxDistance = distance;
maxIndex = i;
}
}
// 如果最远点大于容差,递归处理
if (maxDistance > tolerance)
{
DouglasPeucker(points, startIndex, maxIndex, tolerance, ref result);
result.Add(points[maxIndex]);
DouglasPeucker(points, maxIndex, endIndex, tolerance, ref result);
}
}
/// <summary>
/// 计算点到线段的距离
/// </summary>
private float PointToLineDistance(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
{
Vector3 line = lineEnd - lineStart;
float lineLength = line.magnitude;
if (lineLength < 0.001f)
{
return Vector3.Distance(point, lineStart);
}
Vector3 normalizedLine = line / lineLength;
Vector3 pointVector = point - lineStart;
// 计算投影长度
float projectionLength = Vector3.Dot(pointVector, normalizedLine);
if (projectionLength < 0)
{
return Vector3.Distance(point, lineStart);
}
else if (projectionLength > lineLength)
{
return Vector3.Distance(point, lineEnd);
}
else
{
Vector3 projectionPoint = lineStart + normalizedLine * projectionLength;
return Vector3.Distance(point, projectionPoint);
}
}
/// <summary>
/// 应用平滑算法
/// </summary>
private Vector3[] ApplySmoothingAlgorithm(Vector3[] path, SmoothingSettings settings)
{
switch (settings.algorithm)
{
case SmoothingAlgorithm.Bezier:
return ApplyBezierSmoothing(path, settings);
case SmoothingAlgorithm.CatmullRom:
return ApplyCatmullRomSmoothing(path, settings);
case SmoothingAlgorithm.Chaikin:
return ApplyChaikinSmoothing(path, settings);
case SmoothingAlgorithm.Linear:
default:
return path;
}
}
/// <summary>
/// 应用贝塞尔曲线平滑
/// </summary>
private Vector3[] ApplyBezierSmoothing(Vector3[] path, SmoothingSettings settings)
{
if (path.Length < 3)
{
return path;
}
List<Vector3> smoothedPath = new List<Vector3>();
// 添加起点
smoothedPath.Add(path[0]);
// 为每三个点创建二次贝塞尔曲线
for (int i = 0; i < path.Length - 2; i += 2)
{
Vector3 p0 = path[i];
Vector3 p1 = path[i + 1];
Vector3 p2 = path[i + 2];
// 生成贝塞尔曲线点
for (float t = 0.1f; t < 1.0f; t += 0.1f)
{
Vector3 bezierPoint = CalculateQuadraticBezierPoint(t, p0, p1, p2);
smoothedPath.Add(bezierPoint);
}
}
// 添加终点
smoothedPath.Add(path[path.Length - 1]);
return smoothedPath.ToArray();
}
/// <summary>
/// 计算二次贝塞尔曲线点
/// </summary>
private Vector3 CalculateQuadraticBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
Vector3 point = uu * p0; // (1-t)² * P0
point += 2 * u * t * p1; // 2(1-t)t * P1
point += tt * p2; // t² * P2
return point;
}
/// <summary>
/// 应用Catmull-Rom样条平滑
/// </summary>
private Vector3[] ApplyCatmullRomSmoothing(Vector3[] path, SmoothingSettings settings)
{
if (path.Length < 4)
{
return path;
}
List<Vector3> smoothedPath = new List<Vector3>();
// 添加第一个点
smoothedPath.Add(path[0]);
// 为每四个点创建Catmull-Rom样条
for (int i = 0; i < path.Length - 3; i++)
{
Vector3 p0 = i == 0 ? path[0] : path[i - 1];
Vector3 p1 = path[i];
Vector3 p2 = path[i + 1];
Vector3 p3 = path[i + 2];
// 生成样条点
for (float t = 0; t <= 1.0f; t += 0.1f)
{
Vector3 splinePoint = CalculateCatmullRomPoint(t, p0, p1, p2, p3);
smoothedPath.Add(splinePoint);
}
}
// 添加最后两个点
smoothedPath.Add(path[path.Length - 2]);
smoothedPath.Add(path[path.Length - 1]);
return smoothedPath.ToArray();
}
/// <summary>
/// 计算Catmull-Rom样条点
/// </summary>
private Vector3 CalculateCatmullRomPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
Vector3 point = 0.5f * (
(2 * p1) +
(-p0 + p2) * t +
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t +
(-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t
);
return point;
}
/// <summary>
/// 应用Chaikin平滑
/// </summary>
private Vector3[] ApplyChaikinSmoothing(Vector3[] path, SmoothingSettings settings)
{
List<Vector3> currentPath = new List<Vector3>(path);
// 应用多次Chaikin平滑迭代
for (int iteration = 0; iteration < settings.iterations; iteration++)
{
List<Vector3> newPath = new List<Vector3>();
// 添加第一个点
newPath.Add(currentPath[0]);
// 对每一对点应用Chaikin算法
for (int i = 0; i < currentPath.Count - 1; i++)
{
Vector3 p0 = currentPath[i];
Vector3 p1 = currentPath[i + 1];
// 计算两个新点
Vector3 q = Vector3.Lerp(p0, p1, settings.smoothingFactor);
Vector3 r = Vector3.Lerp(p0, p1, 1 - settings.smoothingFactor);
newPath.Add(q);
newPath.Add(r);
}
// 添加最后一个点
newPath.Add(currentPath[currentPath.Count - 1]);
currentPath = newPath;
}
return currentPath.ToArray();
}
/// <summary>
/// 验证平滑后的路径
/// </summary>
private Vector3[] ValidateSmoothedPath(Vector3[] smoothedPath, Vector3[] originalPath,
SmoothingSettings settings)
{
List<Vector3> validatedPath = new List<Vector3>();
// 检查每个点是否在导航网格上
for (int i = 0; i < smoothedPath.Length; i++)
{
Vector3 point = smoothedPath[i];
// 检查点是否可达
if (IsPointReachable(point, settings.maxDeviation))
{
validatedPath.Add(point);
}
else
{
// 如果不可达,使用原始路径中最接近的点
Vector3 closestValidPoint = FindClosestValidPoint(point, originalPath);
validatedPath.Add(closestValidPoint);
}
}
// 保留转角(可选)
if (settings.preserveCorners)
{
validatedPath = PreserveSharpCorners(validatedPath, settings.cornerAngleThreshold);
}
return validatedPath.ToArray();
}
/// <summary>
/// 检查点是否可达
/// </summary>
private bool IsPointReachable(Vector3 point, float maxDeviation)
{
NavMeshHit hit;
return NavMesh.SamplePosition(point, out hit, maxDeviation, NavMesh.AllAreas);
}
/// <summary>
/// 查找最近的有效点
/// </summary>
private Vector3 FindClosestValidPoint(Vector3 point, Vector3[] originalPath)
{
Vector3 closestPoint = point;
float closestDistance = float.MaxValue;
foreach (Vector3 originalPoint in originalPath)
{
float distance = Vector3.Distance(point, originalPoint);
if (distance < closestDistance && IsPointReachable(originalPoint, 0.1f))
{
closestDistance = distance;
closestPoint = originalPoint;
}
}
return closestPoint;
}
/// <summary>
/// 保留锐利转角
/// </summary>
private List<Vector3> PreserveSharpCorners(List<Vector3> path, float angleThreshold)
{
if (path.Count < 3)
{
return path;
}
List<Vector3> result = new List<Vector3>();
// 添加第一个点
result.Add(path[0]);
// 检查每个点是否是转角
for (int i = 1; i < path.Count - 1; i++)
{
Vector3 prev = path[i - 1];
Vector3 current = path[i];
Vector3 next = path[i + 1];
// 计算转角角度
Vector3 dir1 = (current - prev).normalized;
Vector3 dir2 = (next - current).normalized;
float angle = Vector3.Angle(dir1, dir2);
// 如果是锐角,保留该点
if (angle < angleThreshold)
{
result.Add(current);
}
}
// 添加最后一个点
result.Add(path[path.Count - 1]);
return result;
}
/// <summary>
/// 获取代理设置
/// </summary>
private SmoothingSettings GetAgentSettings(int agentId)
{
if (agentSettings.TryGetValue(agentId, out SmoothingSettings settings))
{
return settings;
}
// 使用默认设置
return defaultSettings;
}
/// <summary>
/// 设置代理的平滑参数
/// </summary>
public void SetAgentSmoothingSettings(int agentId, SmoothingSettings settings)
{
agentSettings[agentId] = settings;
}
/// <summary>
/// 清除代理设置
/// </summary>
public void ClearAgentSettings(int agentId)
{
agentSettings.Remove(agentId);
}
/// <summary>
/// 可视化路径
/// </summary>
public void VisualizePath(Vector3[] path, Color color, float duration = 0)
{
if (path == null || path.Length < 2)
{
return;
}
for (int i = 0; i < path.Length - 1; i++)
{
Debug.DrawLine(path[i], path[i + 1], color, duration);
}
}
}
}
10.5 反射探头的配置与混合策略
反射探头用于提供高质量的局部反射效果,特别是在室内场景和金属材质中。以下是一个高级反射探头管理系统:
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
namespace CommercialGame.ReflectionSystem
{
/// <summary>
/// 高级反射探头管理器
/// </summary>
public class AdvancedReflectionProbeManager : MonoBehaviour
{
[System.Serializable]
public class ProbeSettings
{
public ReflectionProbe probe;
public float importance = 1.0f;
public float blendDistance = 5.0f;
public bool dynamicUpdate = false;
public float updateInterval = 1.0f;
public ProbeVolumeShape volumeShape = ProbeVolumeShape.Box;
public Vector3 volumeSize = Vector3.one * 10;
}
public enum ProbeVolumeShape
{
Box,
Sphere
}
[Header("探头管理")]
[SerializeField]
private List<ProbeSettings> probeSettings = new List<ProbeSettings>();
[SerializeField]
private ReflectionProbe globalProbe;
[SerializeField]
private bool autoCollectProbes = true;
[Header("混合设置")]
[SerializeField]
private bool enableProbeBlending = true;
[SerializeField]
private int maxBlendedProbes = 2;
[SerializeField]
private float blendSmoothness = 2.0f;
[Header("性能设置")]
[SerializeField]
private bool useAsyncUpdates = true;
[SerializeField]
private int maxUpdatesPerFrame = 1;
[SerializeField]
private bool cullDistantProbes = true;
[SerializeField]
private float maxProbeDistance = 50.0f;
private Dictionary<ReflectionProbe, ProbeSettings> probeSettingsDictionary;
private Dictionary<ReflectionProbe, float> probeUpdateTimes;
private Queue<ReflectionProbe> probeUpdateQueue;
private int updatesThisFrame;
/// <summary>
/// 初始化反射探头系统
/// </summary>
private void Awake()
{
InitializeReflectionSystem();
if (autoCollectProbes)
{
CollectSceneProbes();
}
Debug.Log($"反射探头系统初始化完成,管理 {probeSettings.Count} 个探头");
}
/// <summary>
/// 初始化反射系统
/// </summary>
private void InitializeReflectionSystem()
{
probeSettingsDictionary = new Dictionary<ReflectionProbe, ProbeSettings>();
probeUpdateTimes = new Dictionary<ReflectionProbe, float>();
probeUpdateQueue = new Queue<ReflectionProbe>();
// 注册所有探头
foreach (ProbeSettings settings in probeSettings)
{
if (settings.probe != null)
{
probeSettingsDictionary[settings.probe] = settings;
probeUpdateTimes[settings.probe] = Time.time;
// 配置探头
ConfigureProbe(settings);
}
}
}
/// <summary>
/// 收集场景中的探头
/// </summary>
private void CollectSceneProbes()
{
ReflectionProbe[] allProbes = FindObjectsOfType<ReflectionProbe>();
foreach (ReflectionProbe probe in allProbes)
{
// 跳过已经注册的探头
if (probeSettingsDictionary.ContainsKey(probe))
{
continue;
}
// 创建新的探头设置
ProbeSettings settings = new ProbeSettings
{
probe = probe,
importance = probe.importance,
blendDistance = probe.blendDistance,
dynamicUpdate = probe.mode == ReflectionProbeMode.Realtime,
updateInterval = probe.refreshMode == ReflectionProbeRefreshMode.ViaScripting ? 1.0f : 0,
volumeShape = ProbeVolumeShape.Box,
volumeSize = probe.size
};
probeSettings.Add(settings);
probeSettingsDictionary[probe] = settings;
probeUpdateTimes[probe] = Time.time;
// 配置探头
ConfigureProbe(settings);
}
}
/// <summary>
/// 配置探头
/// </summary>
private void ConfigureProbe(ProbeSettings settings)
{
ReflectionProbe probe = settings.probe;
// 设置混合距离
probe.blendDistance = settings.blendDistance;
// 配置动态更新
if (settings.dynamicUpdate)
{
probe.mode = ReflectionProbeMode.Realtime;
probe.refreshMode = ReflectionProbeRefreshMode.ViaScripting;
probe.timeSlicingMode = ReflectionProbeTimeSlicingMode.AllFacesAtATime;
}
else
{
probe.mode = ReflectionProbeMode.Baked;
}
// 设置重要性
probe.importance = Mathf.RoundToInt(settings.importance);
Debug.Log($"配置反射探头: {probe.name}, 模式: {probe.mode}, 重要性: {probe.importance}");
}
/// <summary>
/// 每帧更新
/// </summary>
private void Update()
{
updatesThisFrame = 0;
// 更新动态探头
UpdateDynamicProbes();
// 处理探头更新队列
ProcessProbeUpdateQueue();
}
/// <summary>
/// 更新动态探头
/// </summary>
private void UpdateDynamicProbes()
{
foreach (var kvp in probeSettingsDictionary)
{
ReflectionProbe probe = kvp.Key;
ProbeSettings settings = kvp.Value;
if (!settings.dynamicUpdate)
{
continue;
}
// 检查是否需要更新
if (Time.time - probeUpdateTimes[probe] >= settings.updateInterval)
{
// 检查探头是否在范围内
if (cullDistantProbes)
{
float distance = Vector3.Distance(Camera.main.transform.position, probe.transform.position);
if (distance > maxProbeDistance)
{
continue;
}
}
// 添加到更新队列
probeUpdateQueue.Enqueue(probe);
probeUpdateTimes[probe] = Time.time;
}
}
}
/// <summary>
/// 处理探头更新队列
/// </summary>
private void ProcessProbeUpdateQueue()
{
while (probeUpdateQueue.Count > 0 && updatesThisFrame < maxUpdatesPerFrame)
{
ReflectionProbe probe = probeUpdateQueue.Dequeue();
if (probe != null)
{
UpdateProbe(probe);
updatesThisFrame++;
}
}
}
/// <summary>
/// 更新探头
/// </summary>
private void UpdateProbe(ReflectionProbe probe)
{
if (useAsyncUpdates)
{
// 异步更新
probe.RenderProbe();
}
else
{
// 同步更新
probe.RenderProbe();
}
Debug.Log($"更新反射探头: {probe.name}");
}
/// <summary>
/// 为位置获取最佳反射探头混合
/// </summary>
public ReflectionProbeBlendInfo GetBestProbeBlend(Vector3 position, bool includeGlobal = true)
{
ReflectionProbeBlendInfo blendInfo = new ReflectionProbeBlendInfo();
if (!enableProbeBlending)
{
// 不混合,返回最近的探头
blendInfo.primaryProbe = GetNearestProbe(position);
blendInfo.secondaryProbe = null;
blendInfo.blendFactor = 1.0f;
return blendInfo;
}
// 收集所有影响该位置的探头
List<ProbeInfluence> influencingProbes = GetInfluencingProbes(position);
if (influencingProbes.Count == 0 && includeGlobal && globalProbe != null)
{
// 没有局部探头,使用全局探头
blendInfo.primaryProbe = globalProbe;
blendInfo.secondaryProbe = null;
blendInfo.blendFactor = 1.0f;
return blendInfo;
}
// 按影响力排序
influencingProbes.Sort((a, b) => b.influence.CompareTo(a.influence));
// 选择最重要的探头
if (influencingProbes.Count >= 1)
{
blendInfo.primaryProbe = influencingProbes[0].probe;
if (influencingProbes.Count >= 2 && maxBlendedProbes >= 2)
{
blendInfo.secondaryProbe = influencingProbes[1].probe;
// 计算混合因子
float totalInfluence = influencingProbes[0].influence + influencingProbes[1].influence;
if (totalInfluence > 0)
{
blendInfo.blendFactor = influencingProbes[0].influence / totalInfluence;
// 应用平滑
blendInfo.blendFactor = Mathf.SmoothStep(0, 1, blendInfo.blendFactor * blendSmoothness);
}
else
{
blendInfo.blendFactor = 0.5f;
}
}
else
{
blendInfo.secondaryProbe = null;
blendInfo.blendFactor = 1.0f;
}
}
return blendInfo;
}
/// <summary>
/// 获取影响位置的探头
/// </summary>
private List<ProbeInfluence> GetInfluencingProbes(Vector3 position)
{
List<ProbeInfluence> influencingProbes = new List<ProbeInfluence>();
foreach (var kvp in probeSettingsDictionary)
{
ReflectionProbe probe = kvp.Key;
ProbeSettings settings = kvp.Value;
// 检查位置是否在探头体积内
if (IsPositionInProbeVolume(position, probe, settings))
{
// 计算影响力
float influence = CalculateProbeInfluence(position, probe, settings);
if (influence > 0.01f) // 忽略影响力太小的探头
{
influencingProbes.Add(new ProbeInfluence
{
probe = probe,
influence = influence
});
}
}
}
return influencingProbes;
}
/// <summary>
/// 检查位置是否在探头体积内
/// </summary>
private bool IsPositionInProbeVolume(Vector3 position, ReflectionProbe probe, ProbeSettings settings)
{
Vector3 localPosition = probe.transform.InverseTransformPoint(position);
switch (settings.volumeShape)
{
case ProbeVolumeShape.Box:
return Mathf.Abs(localPosition.x) <= settings.volumeSize.x * 0.5f &&
Mathf.Abs(localPosition.y) <= settings.volumeSize.y * 0.5f &&
Mathf.Abs(localPosition.z) <= settings.volumeSize.z * 0.5f;
case ProbeVolumeShape.Sphere:
return localPosition.magnitude <= settings.volumeSize.x * 0.5f;
default:
return false;
}
}
/// <summary>
/// 计算探头影响力
/// </summary>
private float CalculateProbeInfluence(Vector3 position, ReflectionProbe probe, ProbeSettings settings)
{
// 计算到探头中心的距离
float distance = Vector3.Distance(position, probe.transform.position);
// 计算体积边界距离
float volumeDistance = GetDistanceToVolumeBoundary(position, probe, settings);
// 基于距离的影响力衰减
float distanceInfluence = Mathf.Clamp01(1.0f - volumeDistance / settings.blendDistance);
// 考虑探头重要性
float importanceFactor = settings.importance / 10.0f; // 归一化到0-1
// 组合影响力
float influence = distanceInfluence * importanceFactor;
return influence;
}
/// <summary>
/// 获取到体积边界的距离
/// </summary>
private float GetDistanceToVolumeBoundary(Vector3 position, ReflectionProbe probe, ProbeSettings settings)
{
Vector3 localPosition = probe.transform.InverseTransformPoint(position);
switch (settings.volumeShape)
{
case ProbeVolumeShape.Box:
// 计算到长方体表面的最近距离
Vector3 halfSize = settings.volumeSize * 0.5f;
Vector3 clampedPosition = new Vector3(
Mathf.Clamp(localPosition.x, -halfSize.x, halfSize.x),
Mathf.Clamp(localPosition.y, -halfSize.y, halfSize.y),
Mathf.Clamp(localPosition.z, -halfSize.z, halfSize.z)
);
return Vector3.Distance(localPosition, clampedPosition);
case ProbeVolumeShape.Sphere:
// 计算到球体表面的距离
float radius = settings.volumeSize.x * 0.5f;
return Mathf.Max(0, localPosition.magnitude - radius);
default:
return float.MaxValue;
}
}
/// <summary>
/// 获取最近的探头
/// </summary>
private ReflectionProbe GetNearestProbe(Vector3 position)
{
ReflectionProbe nearestProbe = null;
float nearestDistance = float.MaxValue;
foreach (var kvp in probeSettingsDictionary)
{
ReflectionProbe probe = kvp.Key;
float distance = Vector3.Distance(position, probe.transform.position);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearestProbe = probe;
}
}
return nearestProbe;
}
/// <summary>
/// 手动强制更新探头
/// </summary>
public void ForceUpdateProbe(ReflectionProbe probe)
{
if (probe != null)
{
probeUpdateQueue.Enqueue(probe);
probeUpdateTimes[probe] = Time.time;
}
}
/// <summary>
/// 添加新探头
/// </summary>
public void AddProbe(ReflectionProbe probe, ProbeSettings settings = null)
{
if (probe == null)
{
Debug.LogWarning("尝试添加空探头");
return;
}
if (probeSettingsDictionary.ContainsKey(probe))
{
Debug.LogWarning($"探头 {probe.name} 已经存在");
return;
}
if (settings == null)
{
settings = new ProbeSettings
{
probe = probe,
importance = probe.importance,
blendDistance = probe.blendDistance,
dynamicUpdate = probe.mode == ReflectionProbeMode.Realtime
};
}
probeSettings.Add(settings);
probeSettingsDictionary[probe] = settings;
probeUpdateTimes[probe] = Time.time;
ConfigureProbe(settings);
Debug.Log($"添加反射探头: {probe.name}");
}
/// <summary>
/// 移除探头
/// </summary>
public void RemoveProbe(ReflectionProbe probe)
{
if (probeSettingsDictionary.ContainsKey(probe))
{
ProbeSettings settings = probeSettingsDictionary[probe];
probeSettings.Remove(settings);
probeSettingsDictionary.Remove(probe);
probeUpdateTimes.Remove(probe);
Debug.Log($"移除反射探头: {probe.name}");
}
}
/// <summary>
/// 获取系统统计信息
/// </summary>
public ReflectionProbeStatistics GetStatistics()
{
int totalProbes = probeSettingsDictionary.Count;
int dynamicProbes = 0;
int queuedUpdates = probeUpdateQueue.Count;
foreach (var settings in probeSettings)
{
if (settings.dynamicUpdate)
{
dynamicProbes++;
}
}
return new ReflectionProbeStatistics
{
totalProbes = totalProbes,
dynamicProbes = dynamicProbes,
staticProbes = totalProbes - dynamicProbes,
queuedUpdates = queuedUpdates,
updatesThisFrame = updatesThisFrame
};
}
/// <summary>
/// 反射探头统计信息
/// </summary>
public struct ReflectionProbeStatistics
{
public int totalProbes;
public int dynamicProbes;
public int staticProbes;
public int queuedUpdates;
public int updatesThisFrame;
}
/// <summary>
/// 探头影响力结构
/// </summary>
private struct ProbeInfluence
{
public ReflectionProbe probe;
public float influence;
}
/// <summary>
/// 反射探头混合信息
/// </summary>
public struct ReflectionProbeBlendInfo
{
public ReflectionProbe primaryProbe;
public ReflectionProbe secondaryProbe;
public float blendFactor;
}
}
}
10.6 综合优化策略与性能监控
在商业游戏开发中,各种优化技术需要协同工作,并且需要实时监控系统性能。以下是一个综合性能监控系统的实现:
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CommercialGame.PerformanceSystem
{
/// <summary>
/// 综合性能监控器
/// </summary>
public class ComprehensivePerformanceMonitor : MonoBehaviour
{
[System.Serializable]
public class PerformanceThresholds
{
public float targetFPS = 60.0f;
public float warningFPS = 30.0f;
public float criticalFPS = 20.0f;
public int targetDrawCalls = 500;
public int warningDrawCalls = 1000;
public int criticalDrawCalls = 2000;
public float targetFrameTime = 16.67f; // 60 FPS对应的帧时间
public float warningFrameTime = 33.33f; // 30 FPS对应的帧时间
public float criticalFrameTime = 50.0f; // 20 FPS对应的帧时间
}
[Header("性能阈值")]
[SerializeField]
private PerformanceThresholds thresholds = new PerformanceThresholds();
[Header("监控设置")]
[SerializeField]
private float updateInterval = 1.0f;
[SerializeField]
private int sampleCount = 60;
[SerializeField]
private bool logWarnings = true;
[SerializeField]
private bool autoOptimize = false;
[Header("子系统监控")]
[SerializeField]
private bool monitorLighting = true;
[SerializeField]
private bool monitorOcclusion = true;
[SerializeField]
private bool monitorBatching = true;
[SerializeField]
private bool monitorNavigation = true;
[SerializeField]
private bool monitorReflections = true;
private class PerformanceSample
{
public float timestamp;
public float fps;
public float frameTime;
public int drawCalls;
public int triangles;
public int vertices;
public float memoryUsage;
public LightingStats lightingStats;
public OcclusionStats occlusionStats;
public BatchingStats batchingStats;
public NavigationStats navigationStats;
public ReflectionStats reflectionStats;
}
private Queue<PerformanceSample> performanceSamples;
private PerformanceSample currentSample;
private float lastUpdateTime;
private int frameCount;
private float accumulatedFrameTime;
// 子系统引用
private AdvancedStaticBatching batchingSystem;
private AdvancedOcclusionCuller occlusionSystem;
private AdvancedNavMeshManager navigationSystem;
private AdvancedReflectionProbeManager reflectionSystem;
private HybridLightingManager lightingSystem;
/// <summary>
/// 初始化性能监控器
/// </summary>
private void Awake()
{
InitializeMonitor();
FindSubsystems();
}
/// <summary>
/// 初始化监控器
/// </summary>
private void InitializeMonitor()
{
performanceSamples = new Queue<PerformanceSample>(sampleCount);
currentSample = new PerformanceSample();
lastUpdateTime = Time.time;
frameCount = 0;
accumulatedFrameTime = 0;
Debug.Log("综合性能监控器初始化完成");
}
/// <summary>
/// 查找子系统
/// </summary>
private void FindSubsystems()
{
batchingSystem = FindObjectOfType<AdvancedStaticBatching>();
occlusionSystem = FindObjectOfType<AdvancedOcclusionCuller>();
navigationSystem = FindObjectOfType<AdvancedNavMeshManager>();
reflectionSystem = FindObjectOfType<AdvancedReflectionProbeManager>();
lightingSystem = FindObjectOfType<HybridLightingManager>();
}
/// <summary>
/// 每帧更新
/// </summary>
private void Update()
{
frameCount++;
accumulatedFrameTime += Time.unscaledDeltaTime;
// 定期收集性能数据
if (Time.time - lastUpdateTime >= updateInterval)
{
CollectPerformanceData();
AnalyzePerformance();
if (autoOptimize)
{
ApplyAutoOptimizations();
}
lastUpdateTime = Time.time;
}
}
/// <summary>
/// 收集性能数据
/// </summary>
private void CollectPerformanceData()
{
currentSample = new PerformanceSample
{
timestamp = Time.time,
fps = frameCount / accumulatedFrameTime,
frameTime = accumulatedFrameTime / frameCount * 1000.0f, // 转换为毫秒
drawCalls = UnityEngine.Rendering.OnPreCull.GetInvocationList().Length, // 近似值
triangles = 0, // 需要通过统计获取
vertices = 0, // 需要通过统计获取
memoryUsage = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024.0f / 1024.0f // MB
};
// 收集子系统数据
if (monitorLighting && lightingSystem != null)
{
// 这里应该从光照系统获取统计数据
currentSample.lightingStats = new LightingStats();
}
if (monitorOcclusion && occlusionSystem != null)
{
var stats = occlusionSystem.GetStatistics();
currentSample.occlusionStats = new OcclusionStats
{
totalObjects = stats.totalObjects,
visibleObjects = stats.visibleObjects,
cullingRatio = stats.cullingRatio
};
}
if (monitorBatching && batchingSystem != null)
{
var stats = batchingSystem.GetStatistics();
currentSample.batchingStats = new BatchingStats
{
totalBatches = stats.totalBatches,
totalOriginalObjects = stats.totalOriginalObjects,
reductionRatio = stats.reductionRatio
};
}
if (monitorNavigation && navigationSystem != null)
{
var stats = navigationSystem.GetStatistics();
currentSample.navigationStats = new NavigationStats
{
totalVertices = stats.totalVertices,
totalTriangles = stats.totalTriangles,
dynamicObstacles = stats.dynamicObstacles
};
}
if (monitorReflections && reflectionSystem != null)
{
var stats = reflectionSystem.GetStatistics();
currentSample.reflectionStats = new ReflectionStats
{
totalProbes = stats.totalProbes,
dynamicProbes = stats.dynamicProbes
};
}
// 保存样本
performanceSamples.Enqueue(currentSample);
if (performanceSamples.Count > sampleCount)
{
performanceSamples.Dequeue();
}
// 重置计数器
frameCount = 0;
accumulatedFrameTime = 0;
}
/// <summary>
/// 分析性能
/// </summary>
private void AnalyzePerformance()
{
PerformanceIssue issues = CheckForPerformanceIssues();
if (issues.HasIssues && logWarnings)
{
LogPerformanceIssues(issues);
}
// 触发性能事件
OnPerformanceAnalyzed?.Invoke(currentSample, issues);
}
/// <summary>
/// 检查性能问题
/// </summary>
private PerformanceIssue CheckForPerformanceIssues()
{
PerformanceIssue issues = new PerformanceIssue();
// 检查FPS
if (currentSample.fps < thresholds.criticalFPS)
{
issues.fpsLevel = IssueLevel.Critical;
issues.issues.Add($"FPS严重过低: {currentSample.fps:F1} (临界值: {thresholds.criticalFPS})");
}
else if (currentSample.fps < thresholds.warningFPS)
{
issues.fpsLevel = IssueLevel.Warning;
issues.issues.Add($"FPS警告: {currentSample.fps:F1} (警告值: {thresholds.warningFPS})");
}
// 检查帧时间
if (currentSample.frameTime > thresholds.criticalFrameTime)
{
issues.frameTimeLevel = IssueLevel.Critical;
issues.issues.Add($"帧时间严重过长: {currentSample.frameTime:F2}ms (临界值: {thresholds.criticalFrameTime}ms)");
}
else if (currentSample.frameTime > thresholds.warningFrameTime)
{
issues.frameTimeLevel = IssueLevel.Warning;
issues.issues.Add($"帧时间警告: {currentSample.frameTime:F2}ms (警告值: {thresholds.warningFrameTime}ms)");
}
// 检查绘制调用
if (currentSample.drawCalls > thresholds.criticalDrawCalls)
{
issues.drawCallLevel = IssueLevel.Critical;
issues.issues.Add($"绘制调用过多: {currentSample.drawCalls} (临界值: {thresholds.criticalDrawCalls})");
}
else if (currentSample.drawCalls > thresholds.warningDrawCalls)
{
issues.drawCallLevel = IssueLevel.Warning;
issues.issues.Add($"绘制调用警告: {currentSample.drawCalls} (警告值: {thresholds.warningDrawCalls})");
}
// 检查内存使用
if (currentSample.memoryUsage > 1024) // 超过1GB
{
issues.memoryLevel = IssueLevel.Warning;
issues.issues.Add($"内存使用较高: {currentSample.memoryUsage:F1}MB");
}
return issues;
}
/// <summary>
/// 记录性能问题
/// </summary>
private void LogPerformanceIssues(PerformanceIssue issues)
{
StringBuilder logMessage = new StringBuilder();
logMessage.AppendLine("性能问题检测:");
foreach (string issue in issues.issues)
{
logMessage.AppendLine($" - {issue}");
}
if (issues.issues.Count > 0)
{
Debug.LogWarning(logMessage.ToString());
}
}
/// <summary>
/// 应用自动优化
/// </summary>
private void ApplyAutoOptimizations()
{
PerformanceIssue issues = CheckForPerformanceIssues();
if (issues.fpsLevel == IssueLevel.Critical || issues.frameTimeLevel == IssueLevel.Critical)
{
ApplyAggressiveOptimizations();
}
else if (issues.fpsLevel == IssueLevel.Warning || issues.frameTimeLevel == IssueLevel.Warning)
{
ApplyModerateOptimizations();
}
}
/// <summary>
/// 应用激进优化
/// </summary>
private void ApplyAggressiveOptimizations()
{
Debug.Log("应用激进优化策略");
// 降低渲染质量
QualitySettings.SetQualityLevel(Mathf.Max(0, QualitySettings.GetQualityLevel() - 2), true);
// 减少阴影距离
QualitySettings.shadowDistance = Mathf.Max(10.0f, QualitySettings.shadowDistance * 0.5f);
// 禁用实时反射
if (reflectionSystem != null)
{
// 这里应该调用反射系统的方法来禁用动态更新
}
// 增加遮挡剔除距离
if (occlusionSystem != null)
{
// 这里应该调用遮挡系统的方法来调整参数
}
}
/// <summary>
/// 应用适度优化
/// </summary>
private void ApplyModerateOptimizations()
{
Debug.Log("应用适度优化策略");
// 稍微降低渲染质量
if (QualitySettings.GetQualityLevel() > 0)
{
QualitySettings.SetQualityLevel(QualitySettings.GetQualityLevel() - 1, true);
}
// 减少一些特效
// 这里可以根据需要添加更多优化策略
}
/// <summary>
/// 获取性能报告
/// </summary>
public PerformanceReport GetPerformanceReport()
{
if (performanceSamples.Count == 0)
{
return new PerformanceReport();
}
// 计算统计数据
float avgFPS = performanceSamples.Average(s => s.fps);
float minFPS = performanceSamples.Min(s => s.fps);
float maxFPS = performanceSamples.Max(s => s.fps);
float avgFrameTime = performanceSamples.Average(s => s.frameTime);
float maxFrameTime = performanceSamples.Max(s => s.frameTime);
int avgDrawCalls = (int)performanceSamples.Average(s => s.drawCalls);
int maxDrawCalls = performanceSamples.Max(s => s.drawCalls);
float avgMemory = performanceSamples.Average(s => s.memoryUsage);
float maxMemory = performanceSamples.Max(s => s.memoryUsage);
return new PerformanceReport
{
averageFPS = avgFPS,
minimumFPS = minFPS,
maximumFPS = maxFPS,
averageFrameTime = avgFrameTime,
maximumFrameTime = maxFrameTime,
averageDrawCalls = avgDrawCalls,
maximumDrawCalls = maxDrawCalls,
averageMemoryUsage = avgMemory,
maximumMemoryUsage = maxMemory,
sampleCount = performanceSamples.Count,
timestamp = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};
}
/// <summary>
/// 导出性能数据
/// </summary>
public string ExportPerformanceData(bool includeSubsystems = true)
{
StringBuilder csv = new StringBuilder();
// 添加标题行
csv.AppendLine("Timestamp,FPS,FrameTime(ms),DrawCalls,Memory(MB)");
// 添加数据行
foreach (PerformanceSample sample in performanceSamples)
{
csv.AppendLine($"{sample.timestamp:F2},{sample.fps:F2},{sample.frameTime:F2},{sample.drawCalls},{sample.memoryUsage:F1}");
}
return csv.ToString();
}
/// <summary>
/// 性能分析完成事件
/// </summary>
public event System.Action<PerformanceSample, PerformanceIssue> OnPerformanceAnalyzed;
/// <summary>
/// 性能问题结构
/// </summary>
public struct PerformanceIssue
{
public IssueLevel fpsLevel;
public IssueLevel frameTimeLevel;
public IssueLevel drawCallLevel;
public IssueLevel memoryLevel;
public List<string> issues;
public bool HasIssues => issues.Count > 0;
}
/// <summary>
/// 问题等级
/// </summary>
public enum IssueLevel
{
Normal,
Warning,
Critical
}
/// <summary>
/// 性能报告结构
/// </summary>
public struct PerformanceReport
{
public float averageFPS;
public float minimumFPS;
public float maximumFPS;
public float averageFrameTime;
public float maximumFrameTime;
public int averageDrawCalls;
public int maximumDrawCalls;
public float averageMemoryUsage;
public float maximumMemoryUsage;
public int sampleCount;
public string timestamp;
}
/// <summary>
/// 子系统统计结构
/// </summary>
public struct LightingStats
{
// 光照系统统计信息
}
public struct OcclusionStats
{
public int totalObjects;
public int visibleObjects;
public float cullingRatio;
}
public struct BatchingStats
{
public int totalBatches;
public int totalOriginalObjects;
public float reductionRatio;
}
public struct NavigationStats
{
public int totalVertices;
public int totalTriangles;
public int dynamicObstacles;
}
public struct ReflectionStats
{
public int totalProbes;
public int dynamicProbes;
}
}
}
总结
本章详细介绍了Unity中静态对象优化与导航系统的关键技术,包括光照烘焙、遮挡剔除、静态批处理、导航网格和反射探头等系统。每个部分都从数学原理出发,结合实际商业项目需求,提供了完整的C#实现代码。
关键技术要点总结:
-
光照烘焙优化:通过预计算光照信息,大幅减少实时渲染开销。在商业项目中需要支持动态切换和混合光照模式。
-
遮挡剔除系统:基于视锥体裁剪和层次深度缓冲,有效减少不可见对象的渲染。事件驱动的架构支持游戏逻辑与渲染优化的紧密结合。
-
批处理技术:静态批处理用于合并静态网格,动态批处理智能管理系统根据对象属性和距离动态合并移动对象。
-
导航网格系统:基于A*算法和网格生成技术,支持动态障碍和路径平滑,提供自然的角色移动。
-
反射探头管理:通过探头混合和动态更新,提供高质量的局部反射效果,特别是对于金属和光滑材质。
-
综合性能监控:实时监控各子系统性能,自动应用优化策略,确保游戏在各种硬件上都能流畅运行。
这些技术在实际商业项目中需要根据具体需求进行调整和优化。例如,在开放世界游戏中可能需要更激进的遮挡剔除,而在室内场景中则需要更精细的光照烘焙。通过合理配置和优化这些系统,可以显著提升游戏的性能和视觉质量。
所有代码示例都遵循Allman风格和驼峰命名法,确保在Unity 2021.3.8f1c1和VS2022/VSCODE中能够正确运行。实际项目中还需要考虑内存管理、多线程安全和平台差异等因素,这些实现提供了坚实的基础框架和优化思路。
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐

所有评论(0)