【Editor】优化后处理合批分组算法

main
zhangaotian 2025-08-01 17:40:16 +08:00
parent 0d7a13e2c8
commit 9523cc47d9
2 changed files with 234 additions and 48 deletions

View File

@ -12,6 +12,7 @@ using Object = UnityEngine.Object;
public abstract class PostProcessor
{
#region 常量
/// <summary>
/// 后处理命名后缀
/// </summary>
@ -56,6 +57,7 @@ public abstract class PostProcessor
/// 像素
/// </summary>
private const int UPP = 100;
#endregion
/// <summary>
@ -348,10 +350,10 @@ public abstract class PostProcessor
try
{
Debug.Log($"[PostProcessor] 执行检查器: {checker.CheckerName}");
// 执行检查
object checkResult = checker.CheckScene(activeScene, rootObjects);
if (checkResult != null)
{
hasErrors = true;
@ -399,12 +401,12 @@ public abstract class PostProcessor
private List<BaseSceneFileChecker> GetAllSceneFileCheckers()
{
var sceneCheckers = new List<BaseSceneFileChecker>();
try
{
// 获取所有检查器
var allCheckers = FileCheckerRegistry.Instance.GetAllCheckers();
// 筛选出场景文件检查器
foreach (var checker in allCheckers)
{
@ -418,7 +420,7 @@ public abstract class PostProcessor
{
Debug.LogError($"[PostProcessor] 获取场景文件检查器时发生异常: {ex.Message}");
}
return sceneCheckers;
}
@ -503,9 +505,11 @@ public abstract class PostProcessor
{
if (PostProcessInstructor.enableBatchGrouping)
{
var limitRange = MeshCombineUtils.CalculateCurrentHexagonMapBounds(); // 可以根据实际地图调整
DebugUtil.Log($"边界宽:{limitRange.x},高:{limitRange.y}");
// 按照距离对渲染器进行分组
Dictionary<string, List<Renderer>> rendererGroups =
MeshCombineUtils.GroupRenderersByProximity(renderers, PostProcessInstructor.batchGroupDistance);
var rendererGroups = MeshCombineUtils.GroupRenderersByProximity(
renderers, PostProcessInstructor.batchGroupDistance, limitRange.x, limitRange.y);
SceneCombineUtil.Instance.CombineGameObjectsByGroups(rendererGroups, combinedRoot.transform,
outputPath,
(int)PostProcessInstructor.postProcessTextureUnitSize,
@ -805,6 +809,7 @@ public abstract class PostProcessor
#endregion
#region 剧情Timeline
/// <summary>
/// 设置剧情timeline
/// </summary>
@ -831,7 +836,7 @@ public abstract class PostProcessor
EditorSceneManager.SaveScene(EditorSceneManager.GetActiveScene());
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();*/
}
#endregion
}

View File

@ -6,6 +6,11 @@ using UnityEngine;
/// </summary>
public static class MeshCombineUtils
{
/// <summary>
/// 无限制常量,用于表示某个维度无边界限制
/// </summary>
public const float UNLIMITED = -1f;
/// <summary>
/// 收集渲染器
/// </summary>
@ -31,44 +36,6 @@ public static class MeshCombineUtils
return allRenderers;
}
/// <summary>
/// 根据相互距离对渲染器进行分组
/// 只要物体与组内任意一个物体距离小于 maxDistance就将其加入该组
/// </summary>
public static Dictionary<string, List<Renderer>> GroupRenderersByProximity(List<Renderer> renderers,
float maxDistance)
{
var groups = new Dictionary<string, List<Renderer>>();
var groupIndex = 0;
// 已分组的渲染器
var processedRenderers = new HashSet<Renderer>();
// 对所有渲染器进行处理
while (processedRenderers.Count < renderers.Count)
{
// 找到第一个未处理的渲染器作为新组的起点
Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers);
if (startRenderer == null)
break;
// 创建新组并添加起点渲染器
var currentGroup = new List<Renderer> { startRenderer };
processedRenderers.Add(startRenderer);
// 扩展当前组
ExpandGroupByProximity(renderers, currentGroup, processedRenderers, maxDistance);
// 添加组到结果
groups.Add($"Group_{groupIndex}", currentGroup);
groupIndex++;
}
Debug.Log($"基于邻近距离分组:将{renderers.Count}个渲染器分成{groups.Count}个组,最大距离为{maxDistance}");
return groups;
}
/// <summary>
/// 查找第一个未处理的渲染器
/// </summary>
@ -81,13 +48,14 @@ public static class MeshCombineUtils
return renderer;
}
}
return null;
}
/// <summary>
/// 通过邻近距离扩展组
/// </summary>
private static void ExpandGroupByProximity(List<Renderer> allRenderers, List<Renderer> currentGroup,
private static void ExpandGroupByProximity(List<Renderer> allRenderers, List<Renderer> currentGroup,
HashSet<Renderer> processedRenderers, float maxDistance)
{
bool addedNew = true;
@ -125,6 +93,7 @@ public static class MeshCombineUtils
return true;
}
}
return false;
}
@ -153,6 +122,218 @@ public static class MeshCombineUtils
return sum / group.Count;
}
/// <summary>
/// 根据相互距离对渲染器进行分组(优化算法)
/// 只要物体与组内任意一个物体距离小于 maxDistance就将其加入该组
/// 可选择性添加最大宽高限制,防止组范围过大
/// </summary>
/// <param name="renderers">要分组的渲染器列表</param>
/// <param name="maxDistance">相邻物体的最大距离</param>
/// <param name="maxWidth">组的最大宽度(-1表示宽度无限制</param>
/// <param name="maxHeight">组的最大高度(-1表示高度无限制</param>
/// <returns>分组结果</returns>
public static Dictionary<string, List<Renderer>> GroupRenderersByProximity(List<Renderer> renderers,
float maxDistance, float maxWidth = UNLIMITED, float maxHeight = UNLIMITED)
{
var groups = new Dictionary<string, List<Renderer>>();
var groupIndex = 0;
// 检查是否有边界限制(只要有一个维度限制就启用边界检查)
bool hasBoundingLimit = maxWidth > 0 || maxHeight > 0;
var maxGroupBounds = hasBoundingLimit ? new Vector2(maxWidth, maxHeight) : Vector2.zero;
// 已分组的渲染器
var processedRenderers = new HashSet<Renderer>();
// 对所有渲染器进行处理
while (processedRenderers.Count < renderers.Count)
{
// 找到第一个未处理的渲染器作为新组的起点
Renderer startRenderer = FindFirstUnprocessedRenderer(renderers, processedRenderers);
if (startRenderer == null)
break;
// 创建新组并添加起点渲染器
var currentGroup = new List<Renderer> { startRenderer };
processedRenderers.Add(startRenderer);
// 扩展当前组
if (hasBoundingLimit)
{
ExpandGroupWithBoundingBoxCheck(renderers, currentGroup, processedRenderers, maxDistance,
maxGroupBounds);
}
else
{
ExpandGroupByProximity(renderers, currentGroup, processedRenderers, maxDistance);
}
// 添加组到结果
groups.Add($"Group_{groupIndex}", currentGroup);
// 计算最终组的边界
var finalBounds = CalculateGroupBounds(currentGroup);
float finalWidth = finalBounds.max.x - finalBounds.min.x;
float finalHeight = finalBounds.max.z - finalBounds.min.z;
Debug.Log($"[分组] 组{groupIndex}完成,包含{currentGroup.Count}个物体,最终尺寸:{finalWidth:F2}x{finalHeight:F2}");
Debug.Log($"[分组] =====================================");
groupIndex++;
}
string limitInfo;
if (!hasBoundingLimit)
{
limitInfo = ",无边界限制";
}
else
{
string widthLimit = maxWidth > 0 ? maxWidth.ToString() : "无限制";
string heightLimit = maxHeight > 0 ? maxHeight.ToString() : "无限制";
limitInfo = $",最大范围为{widthLimit}x{heightLimit}";
}
return groups;
}
/// <summary>
/// 计算正六边形地图边界
/// </summary>
/// <param name="hexagonCountWidth">水平方向六边形个数</param>
/// <param name="hexagonCountHeight">垂直方向六边形个数</param>
/// <param name="hexagonInscribedDiameter">内接圆直径</param>
/// <param name="pointyTop">是否为尖头向上排列true=尖头向上, false=平边向上)</param>
/// <returns>边界范围(x=宽度, y=高度)</returns>
public static Vector2 CalculateHexagonMapBounds(int hexagonCountWidth, int hexagonCountHeight, float hexagonInscribedDiameter, bool pointyTop = true)
{
// 六边形内接圆半径
float inscribedRadius = hexagonInscribedDiameter / 2f;
// 六边形边长 = 内接圆半径 / cos(30°) = 内接圆半径 / (√3/2) = 内接圆半径 * 2 / √3
float hexagonSideLength = inscribedRadius * 2f / Mathf.Sqrt(3f);
float singleHexWidth, singleHexHeight;
if (pointyTop)
{
// 尖头向上排列 ◊(如图所示的排列方式)
// 单个六边形水平宽度 = √3 * 边长
singleHexWidth = Mathf.Sqrt(3f) * hexagonSideLength;
// 垂直方向:蜂窝排列时,行与行之间的间距 = 1.5 * 边长
singleHexHeight = 1.5f * hexagonSideLength;
}
else
{
// 平边向上排列
// 水平方向:蜂窝排列时,列与列之间的间距 = 1.5 * 边长
singleHexWidth = 1.5f * hexagonSideLength;
// 单个六边形垂直高度 = √3 * 边长
singleHexHeight = Mathf.Sqrt(3f) * hexagonSideLength;
}
// 总的最大范围
float maxWidth = hexagonCountWidth * singleHexWidth;
float maxHeight = hexagonCountHeight * singleHexHeight;
return new Vector2(maxWidth, maxHeight);
}
/// <summary>
/// 为当前项目六边形地图计算边界的便利方法
/// </summary>
/// <param name="hexagonCountWidth">水平方向六边形个数</param>
/// <param name="hexagonCountHeight">垂直方向六边形个数</param>
/// <returns>边界范围</returns>
public static Vector2 CalculateCurrentHexagonMapBounds(int hexagonCountWidth = 12, int hexagonCountHeight = 9)
{
// 使用项目配置内接圆直径3尖头向上排列
return CalculateHexagonMapBounds(hexagonCountWidth, hexagonCountHeight, 3f, true);
}
/// <summary>
/// 带边界框检查的组扩展
/// </summary>
private static void ExpandGroupWithBoundingBoxCheck(List<Renderer> allRenderers, List<Renderer> currentGroup,
HashSet<Renderer> processedRenderers, float maxDistance, Vector2 maxGroupBounds)
{
bool addedNew = true;
while (addedNew)
{
addedNew = false;
// 查找与当前组中任意一个渲染器距离在阈值内的未处理渲染器
foreach (var renderer in allRenderers)
{
if (processedRenderers.Contains(renderer))
continue;
// 检查是否与组内任何一个渲染器距离小于阈值
if (!IsCloseToAnyInGroup(renderer, currentGroup, maxDistance))
continue;
// 检查添加该渲染器后组的边界框是否超过最大范围
if (WouldExceedGroupBounds(renderer, currentGroup, maxGroupBounds))
continue;
currentGroup.Add(renderer);
processedRenderers.Add(renderer);
addedNew = true;
}
}
}
/// <summary>
/// 检查添加新渲染器后是否会超过组的最大边界
/// </summary>
private static bool WouldExceedGroupBounds(Renderer newRenderer, List<Renderer> currentGroup,
Vector2 maxGroupBounds)
{
// 计算当前组的边界框
var bounds = CalculateGroupBounds(currentGroup);
// 添加新渲染器的位置
var newPos = newRenderer.transform.position;
// 计算添加新渲染器后的边界框
float minX = Mathf.Min(bounds.min.x, newPos.x);
float maxX = Mathf.Max(bounds.max.x, newPos.x);
float minZ = Mathf.Min(bounds.min.z, newPos.z);
float maxZ = Mathf.Max(bounds.max.z, newPos.z);
// 计算新的宽度和高度在XZ平面上因为相机是倾斜的
float newWidth = maxX - minX;
float newHeight = maxZ - minZ;
// 检查是否超过最大范围只检查大于0的限制维度
bool exceedsWidth = maxGroupBounds.x > 0 && newWidth > maxGroupBounds.x;
bool exceedsHeight = maxGroupBounds.y > 0 && newHeight > maxGroupBounds.y;
bool wouldExceed = exceedsWidth || exceedsHeight;
return wouldExceed;
}
/// <summary>
/// 计算组的边界框
/// </summary>
private static Bounds CalculateGroupBounds(List<Renderer> group)
{
if (group.Count == 0)
return new Bounds();
var firstPos = group[0].transform.position;
var bounds = new Bounds(firstPos, Vector3.zero);
foreach (var renderer in group)
{
bounds.Encapsulate(renderer.transform.position);
}
return bounds;
}
/// <summary>
/// 根据圆形区域对渲染器进行分组(原始算法,保留作参考)
/// </summary>
@ -208,4 +389,4 @@ public static class MeshCombineUtils
Debug.Log($"基于圆形区域分组:将{renderers.Count}个渲染器分成{groups.Count}个组,半径为{maxDistance}");
return groups;
}
}
}