Files
aibis-dream/Assets/Scripts/Framework/Utility/SpriteMerger.cs
T
2025-12-16 20:08:11 +08:00

734 lines
26 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
namespace AibisDream.Framework
{
/// <summary>
/// 合并结果,包含合并后的Sprite和世界位置信息
/// </summary>
public struct MergeResult
{
public Sprite sprite;
public Vector3 worldPosition; // 世界空间中心位置
public Bounds worldBounds; // 世界空间包围盒
}
/// <summary>
/// SpriteRenderer合并工具类
/// </summary>
public static class SpriteMerger
{
/// <summary>
/// 合并多个SpriteRenderer为一个Sprite
/// </summary>
/// <param name="renderers">要合并的SpriteRenderer数组</param>
/// <param name="textureWidth">输出纹理宽度(像素),0则自动计算</param>
/// <param name="textureHeight">输出纹理高度(像素),0则自动计算</param>
/// <param name="pixelsPerUnit">Sprite的pixelsPerUnit</param>
/// <returns>合并结果,包含Sprite和世界位置信息</returns>
public static MergeResult MergeSpriteRenderers(
SpriteRenderer[] renderers,
int textureWidth = 0,
int textureHeight = 0,
float pixelsPerUnit = 100f)
{
if (renderers == null || renderers.Length == 0)
{
Debug.LogError("SpriteRenderer数组为空");
return new MergeResult();
}
// 过滤掉无效的renderer
var validRenderers = renderers.Where(r => r != null && r.sprite != null).ToArray();
if (validRenderers.Length == 0)
{
Debug.LogError("没有有效的SpriteRenderer");
return new MergeResult();
}
// 1. 计算世界空间包围盒
Bounds worldBounds = CalculateWorldBounds(validRenderers);
// 2. 确定纹理尺寸
if (textureWidth <= 0 || textureHeight <= 0)
{
// 根据包围盒自动计算尺寸,保持宽高比
float aspect = worldBounds.size.x / worldBounds.size.y;
if (aspect > 1f)
{
textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.x * pixelsPerUnit));
textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureWidth / aspect));
}
else
{
textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.y * pixelsPerUnit));
textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureHeight * aspect));
}
}
// 3. 创建RenderTexture和临时Camera
RenderTexture renderTexture = null;
Camera tempCamera = null;
try
{
renderTexture = CreateRenderTexture(textureWidth, textureHeight);
tempCamera = CreateTempCamera(worldBounds, renderTexture);
// 4. 渲染SpriteRenderer到RenderTexture
RenderSpritesToTexture(validRenderers, tempCamera);
// 5. 读取为Texture2D
Texture2D texture2D = ReadRenderTexture(renderTexture);
// 6. 转换为Sprite
Sprite sprite = TextureToSprite(texture2D, pixelsPerUnit);
// 7. 返回结果
return new MergeResult
{
sprite = sprite,
worldPosition = worldBounds.center,
worldBounds = worldBounds
};
}
finally
{
// 清理资源
CleanupResources(tempCamera, renderTexture);
}
}
/// <summary>
/// 计算世界空间包围盒
/// </summary>
private static Bounds CalculateWorldBounds(SpriteRenderer[] renderers)
{
if (renderers.Length == 0) return new Bounds();
bool first = true;
Bounds bounds = new Bounds();
foreach (var renderer in renderers)
{
if (renderer.sprite == null) continue;
// 获取Sprite的本地边界
Bounds spriteBounds = renderer.sprite.bounds;
// 获取世界位置(只考虑位置,不考虑旋转缩放)
Vector3 worldPos = renderer.transform.position;
// 计算世界空间中的四个角点
Vector3 center = worldPos + spriteBounds.center;
Vector3 size = spriteBounds.size;
Vector3[] corners = new Vector3[]
{
center + new Vector3(-size.x / 2, -size.y / 2, 0),
center + new Vector3(size.x / 2, -size.y / 2, 0),
center + new Vector3(-size.x / 2, size.y / 2, 0),
center + new Vector3(size.x / 2, size.y / 2, 0)
};
if (first)
{
bounds = new Bounds(corners[0], Vector3.zero);
first = false;
}
foreach (var corner in corners)
{
bounds.Encapsulate(corner);
}
}
return bounds;
}
/// <summary>
/// 创建RenderTexture
/// </summary>
private static RenderTexture CreateRenderTexture(int width, int height)
{
RenderTexture rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
rt.antiAliasing = 1;
rt.filterMode = FilterMode.Bilinear;
rt.anisoLevel = 0;
rt.Create();
return rt;
}
/// <summary>
/// 创建临时Camera
/// </summary>
private static Camera CreateTempCamera(Bounds worldBounds, RenderTexture targetTexture)
{
GameObject cameraObj = new GameObject("TempSpriteMergerCamera");
Camera cam = cameraObj.AddComponent<Camera>();
cam.orthographic = true;
cam.orthographicSize = worldBounds.size.y / 2f;
cam.nearClipPlane = 0.1f;
cam.farClipPlane = 1000f;
cam.clearFlags = CameraClearFlags.SolidColor;
cam.backgroundColor = new Color(0, 0, 0, 0); // 透明背景
cam.cullingMask = 0; // 不渲染任何Layer,我们手动绘制
cam.targetTexture = targetTexture;
// 设置相机位置在包围盒中心,面向Z轴正方向
cam.transform.position = new Vector3(
worldBounds.center.x,
worldBounds.center.y,
worldBounds.center.z - 100f
);
cam.transform.rotation = Quaternion.identity;
return cam;
}
/// <summary>
/// 渲染SpriteRenderer到RenderTexture
/// </summary>
private static void RenderSpritesToTexture(
SpriteRenderer[] renderers,
Camera camera)
{
// 按sortingOrder排序
var sortedRenderers = renderers
.OrderBy(r => r.sortingLayerID)
.ThenBy(r => r.sortingOrder)
.ToArray();
// 使用CommandBuffer来确保立即渲染
CommandBuffer cmd = new CommandBuffer();
cmd.name = "SpriteMerger";
// 设置RenderTexture
cmd.SetRenderTarget(camera.targetTexture);
cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0));
// 为每个SpriteRenderer创建Mesh并绘制
foreach (var renderer in sortedRenderers)
{
if (renderer.sprite == null) continue;
Mesh mesh = CreateSpriteMesh(renderer);
if (mesh == null) continue;
Material material = renderer.sharedMaterial != null ? renderer.sharedMaterial : GetDefaultSpriteMaterial();
if (material == null) continue;
MaterialPropertyBlock mpb = new MaterialPropertyBlock();
renderer.GetPropertyBlock(mpb);
// 应用颜色
Color color = renderer.color;
mpb.SetColor("_Color", color);
// 使用单位矩阵(顶点已经是世界空间坐标)
Matrix4x4 matrix = Matrix4x4.identity;
// 绘制Mesh到CommandBuffer
cmd.DrawMesh(mesh, matrix, material, 0, 0, mpb);
}
// 执行CommandBuffer
Graphics.ExecuteCommandBuffer(cmd);
cmd.Dispose();
}
/// <summary>
/// 为SpriteRenderer创建Mesh(使用世界空间坐标)
/// </summary>
private static Mesh CreateSpriteMesh(SpriteRenderer renderer)
{
Sprite sprite = renderer.sprite;
if (sprite == null) return null;
// 获取Sprite的UV和三角形信息
Vector2[] uvs = sprite.uv;
ushort[] triangles = sprite.triangles;
// 计算世界位置
Vector3 worldPos = renderer.transform.position;
Vector3 spriteBoundsCenter = sprite.bounds.center;
Vector3 spriteBoundsSize = sprite.bounds.size;
// Sprite的世界中心位置
Vector3 spriteWorldCenter = worldPos + spriteBoundsCenter;
// 计算Sprite的四个角点(相对于sprite中心,单位:世界单位)
float halfWidth = spriteBoundsSize.x / 2f;
float halfHeight = spriteBoundsSize.y / 2f;
// 构建四个角的世界坐标
Vector3[] worldCorners = new Vector3[]
{
spriteWorldCenter + new Vector3(-halfWidth, -halfHeight, 0), // 左下
spriteWorldCenter + new Vector3(-halfWidth, halfHeight, 0), // 左上
spriteWorldCenter + new Vector3(halfWidth, halfHeight, 0), // 右上
spriteWorldCenter + new Vector3(halfWidth, -halfHeight, 0) // 右下
};
// 创建顶点数组(使用UV数量,根据UV映射到正确的角点)
Vector3[] vertices = new Vector3[uvs.Length];
// 将UV坐标映射到世界坐标的角点
for (int i = 0; i < uvs.Length; i++)
{
Vector2 uv = uvs[i];
// 找到最接近的角点UV
int cornerIndex = 0;
float minDist = float.MaxValue;
Vector2[] cornerUVs = new Vector2[]
{
new Vector2(0, 0), // 左下
new Vector2(0, 1), // 左上
new Vector2(1, 1), // 右上
new Vector2(1, 0) // 右下
};
for (int j = 0; j < cornerUVs.Length; j++)
{
float dist = Vector2.Distance(uv, cornerUVs[j]);
if (dist < minDist)
{
minDist = dist;
cornerIndex = j;
}
}
// 使用世界空间坐标
vertices[i] = worldCorners[cornerIndex];
}
// 创建Mesh
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.uv = uvs;
mesh.triangles = System.Array.ConvertAll(triangles, x => (int)x);
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
/// <summary>
/// 获取默认Sprite材质
/// </summary>
private static Material GetDefaultSpriteMaterial()
{
// 使用Unity内置的Sprites/Default材质
return new Material(Shader.Find("Sprites/Default"));
}
/// <summary>
/// 从RenderTexture读取像素数据
/// </summary>
private static Texture2D ReadRenderTexture(RenderTexture renderTexture)
{
RenderTexture previousActive = RenderTexture.active;
RenderTexture.active = renderTexture;
Texture2D texture2D = new Texture2D(
renderTexture.width,
renderTexture.height,
TextureFormat.RGBA32,
false
);
texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
texture2D.Apply();
RenderTexture.active = previousActive;
return texture2D;
}
/// <summary>
/// 将Texture2D转换为Sprite
/// </summary>
private static Sprite TextureToSprite(Texture2D texture, float pixelsPerUnit)
{
Sprite sprite = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f), // pivot在中心
pixelsPerUnit
);
return sprite;
}
/// <summary>
/// 清理临时资源
/// </summary>
private static void CleanupResources(Camera camera, RenderTexture renderTexture)
{
if (camera != null)
{
if (camera.gameObject != null)
{
UnityEngine.Object.DestroyImmediate(camera.gameObject);
}
}
if (renderTexture != null)
{
renderTexture.Release();
UnityEngine.Object.DestroyImmediate(renderTexture);
}
}
#region 异步版本
/// <summary>
/// 异步合并多个SpriteRenderer为一个Sprite(协程版本)
/// </summary>
/// <param name="renderers">要合并的SpriteRenderer数组</param>
/// <param name="onComplete">完成回调</param>
/// <param name="textureWidth">输出纹理宽度(像素),0则自动计算</param>
/// <param name="textureHeight">输出纹理高度(像素),0则自动计算</param>
/// <param name="pixelsPerUnit">Sprite的pixelsPerUnit</param>
/// <param name="spritesPerFrame">每帧处理的Sprite数量,用于控制性能</param>
/// <returns>协程</returns>
public static IEnumerator MergeSpriteRenderersAsync(
SpriteRenderer[] renderers,
Action<MergeResult> onComplete,
int textureWidth = 0,
int textureHeight = 0,
float pixelsPerUnit = 100f,
int spritesPerFrame = 5)
{
if (renderers == null || renderers.Length == 0)
{
Debug.LogError("SpriteRenderer数组为空");
onComplete?.Invoke(new MergeResult());
yield break;
}
// 过滤掉无效的renderer
var validRenderers = renderers.Where(r => r != null && r.sprite != null).ToArray();
if (validRenderers.Length == 0)
{
Debug.LogError("没有有效的SpriteRenderer");
onComplete?.Invoke(new MergeResult());
yield break;
}
// 1. 计算世界空间包围盒(分帧执行)
Bounds worldBounds = new Bounds();
bool boundsCalculated = false;
yield return CalculateWorldBoundsAsync(validRenderers, result =>
{
worldBounds = result;
boundsCalculated = true;
});
while (!boundsCalculated)
{
yield return null;
}
// 2. 确定纹理尺寸
if (textureWidth <= 0 || textureHeight <= 0)
{
float aspect = worldBounds.size.x / worldBounds.size.y;
if (aspect > 1f)
{
textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.x * pixelsPerUnit));
textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureWidth / aspect));
}
else
{
textureHeight = Mathf.NextPowerOfTwo(Mathf.CeilToInt(worldBounds.size.y * pixelsPerUnit));
textureWidth = Mathf.NextPowerOfTwo(Mathf.CeilToInt(textureHeight * aspect));
}
}
// 3. 创建RenderTexture和临时Camera
RenderTexture renderTexture = null;
Camera tempCamera = null;
try
{
renderTexture = CreateRenderTexture(textureWidth, textureHeight);
tempCamera = CreateTempCamera(worldBounds, renderTexture);
// 4. 异步渲染SpriteRenderer到RenderTexture(分帧执行)
yield return RenderSpritesToTextureAsync(validRenderers, tempCamera, spritesPerFrame);
// 5. 读取为Texture2D(分帧执行)
Texture2D texture2D = null;
bool textureRead = false;
yield return ReadRenderTextureAsync(renderTexture, result =>
{
texture2D = result;
textureRead = true;
});
while (!textureRead)
{
yield return null;
}
// 6. 转换为Sprite
Sprite sprite = TextureToSprite(texture2D, pixelsPerUnit);
// 7. 返回结果
onComplete?.Invoke(new MergeResult
{
sprite = sprite,
worldPosition = worldBounds.center,
worldBounds = worldBounds
});
}
finally
{
// 清理资源
CleanupResources(tempCamera, renderTexture);
}
}
/// <summary>
/// 异步计算世界空间包围盒
/// </summary>
private static IEnumerator CalculateWorldBoundsAsync(SpriteRenderer[] renderers, Action<Bounds> onComplete)
{
if (renderers.Length == 0)
{
onComplete?.Invoke(new Bounds());
yield break;
}
bool first = true;
Bounds bounds = new Bounds();
// 分帧计算,每帧处理一部分
int processed = 0;
int batchSize = 10; // 每帧处理的renderer数量
while (processed < renderers.Length)
{
int endIndex = Mathf.Min(processed + batchSize, renderers.Length);
for (int i = processed; i < endIndex; i++)
{
var renderer = renderers[i];
if (renderer.sprite == null) continue;
Bounds spriteBounds = renderer.sprite.bounds;
Vector3 worldPos = renderer.transform.position;
Vector3 center = worldPos + spriteBounds.center;
Vector3 size = spriteBounds.size;
Vector3[] corners = new Vector3[]
{
center + new Vector3(-size.x / 2, -size.y / 2, 0),
center + new Vector3(size.x / 2, -size.y / 2, 0),
center + new Vector3(-size.x / 2, size.y / 2, 0),
center + new Vector3(size.x / 2, size.y / 2, 0)
};
if (first)
{
bounds = new Bounds(corners[0], Vector3.zero);
first = false;
}
foreach (var corner in corners)
{
bounds.Encapsulate(corner);
}
}
processed = endIndex;
yield return null; // 每批处理完后让出一帧
}
onComplete?.Invoke(bounds);
}
/// <summary>
/// 异步渲染SpriteRenderer到RenderTexture(分帧执行)
/// </summary>
private static IEnumerator RenderSpritesToTextureAsync(
SpriteRenderer[] renderers,
Camera camera,
int spritesPerFrame)
{
// 按sortingOrder排序
var sortedRenderers = renderers
.OrderBy(r => r.sortingLayerID)
.ThenBy(r => r.sortingOrder)
.ToArray();
// 使用CommandBuffer来确保立即渲染
CommandBuffer cmd = new CommandBuffer();
cmd.name = "SpriteMerger";
// 设置RenderTexture
cmd.SetRenderTarget(camera.targetTexture);
cmd.ClearRenderTarget(true, true, new Color(0, 0, 0, 0));
// 预先创建所有Mesh(分帧执行)
List<Mesh> meshes = new List<Mesh>();
List<Material> materials = new List<Material>();
List<MaterialPropertyBlock> mpbs = new List<MaterialPropertyBlock>();
int processed = 0;
while (processed < sortedRenderers.Length)
{
int endIndex = Mathf.Min(processed + spritesPerFrame, sortedRenderers.Length);
for (int i = processed; i < endIndex; i++)
{
var renderer = sortedRenderers[i];
if (renderer.sprite == null) continue;
Mesh mesh = CreateSpriteMesh(renderer);
if (mesh == null) continue;
Material material = renderer.sharedMaterial != null ? renderer.sharedMaterial : GetDefaultSpriteMaterial();
if (material == null) continue;
MaterialPropertyBlock mpb = new MaterialPropertyBlock();
renderer.GetPropertyBlock(mpb);
Color color = renderer.color;
mpb.SetColor("_Color", color);
meshes.Add(mesh);
materials.Add(material);
mpbs.Add(mpb);
}
processed = endIndex;
yield return null; // 每批处理完后让出一帧
}
// 一次性绘制所有Mesh
for (int i = 0; i < meshes.Count; i++)
{
cmd.DrawMesh(meshes[i], Matrix4x4.identity, materials[i], 0, 0, mpbs[i]);
}
// 执行CommandBuffer
Graphics.ExecuteCommandBuffer(cmd);
cmd.Dispose();
// 清理临时创建的Mesh(避免内存泄漏)
foreach (var mesh in meshes)
{
if (mesh != null)
{
UnityEngine.Object.DestroyImmediate(mesh);
}
}
}
/// <summary>
/// 异步从RenderTexture读取像素数据
/// </summary>
private static IEnumerator ReadRenderTextureAsync(RenderTexture renderTexture, Action<Texture2D> onComplete)
{
// ReadPixels是同步操作,但我们可以分块读取来减少卡顿
RenderTexture previousActive = RenderTexture.active;
RenderTexture.active = renderTexture;
Texture2D texture2D = new Texture2D(
renderTexture.width,
renderTexture.height,
TextureFormat.RGBA32,
false
);
// 读取像素(这个操作可能比较耗时)
texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
yield return null; // 让出一帧
texture2D.Apply();
RenderTexture.active = previousActive;
onComplete?.Invoke(texture2D);
}
/// <summary>
/// 启动异步合并协程(需要MonoBehaviour实例)
/// </summary>
/// <param name="monoBehaviour">用于启动协程的MonoBehaviour实例</param>
/// <param name="renderers">要合并的SpriteRenderer数组</param>
/// <param name="onComplete">完成回调</param>
/// <param name="textureWidth">输出纹理宽度(像素),0则自动计算</param>
/// <param name="textureHeight">输出纹理高度(像素),0则自动计算</param>
/// <param name="pixelsPerUnit">Sprite的pixelsPerUnit</param>
/// <param name="spritesPerFrame">每帧处理的Sprite数量</param>
public static void StartMergeAsync(
MonoBehaviour monoBehaviour,
SpriteRenderer[] renderers,
Action<MergeResult> onComplete,
int textureWidth = 0,
int textureHeight = 0,
float pixelsPerUnit = 100f,
int spritesPerFrame = 5)
{
if (monoBehaviour == null)
{
Debug.LogError("MonoBehaviour实例为空,无法启动协程");
onComplete?.Invoke(new MergeResult());
return;
}
monoBehaviour.StartCoroutine(MergeSpriteRenderersAsync(
renderers,
onComplete,
textureWidth,
textureHeight,
pixelsPerUnit,
spritesPerFrame
));
}
/// <summary>
/// 使用MonoCenter启动异步合并协程(推荐)
/// </summary>
public static void StartMergeAsyncWithMonoCenter(
SpriteRenderer[] renderers,
Action<MergeResult> onComplete,
int textureWidth = 0,
int textureHeight = 0,
float pixelsPerUnit = 100f,
int spritesPerFrame = 5)
{
if (MonoCenter.Mono == null)
{
Debug.LogError("MonoCenter未初始化,无法启动协程");
onComplete?.Invoke(new MergeResult());
return;
}
StartMergeAsync(
MonoCenter.Mono,
renderers,
onComplete,
textureWidth,
textureHeight,
pixelsPerUnit,
spritesPerFrame
);
}
#endregion
}
}