Main/Assets/Editor/SplitTerrain/LightMapSpliter.cs

442 lines
18 KiB
C#
Raw Normal View History

2025-01-25 04:38:09 +08:00
using Thousandto.Core.Asset;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Thousandto.Editor.Test
{
/// <summary>
/// 从lightmap中扣除对应Renderer的光照贴图并且与同一网格的
/// 光照贴图合并成一个新的lightmap
/// </summary>
public static class LightMapSpliter
{
public class RendererInfo
{
public string MapName;
public string RootGoName;
//索引
public int Index;
public Renderer Renderer;
public Mesh Mesh;
//截取的lightmap
public Texture2D MyLightmap;
public Vector2 Position;
//原始bound
public Vector4 OldBound;
public Vector4 OldScaledBound;
//裁剪矩形从完整lightmap中裁剪范围
public Rect CutRect;
//拆分lightmap后的新bound
public Vector4 MyLightmapScaleInfo;
}
public static void Start()
{
}
/// <summary>
/// 将gameobject通过名字中的index来分类
/// </summary>
/// <param name="rootGos"></param>
/// <returns></returns>
public static Dictionary<int, List<GameObject>> SortAsIndex(GameObject[] rootGos)
{
Dictionary<int, List<GameObject>> retDict = new Dictionary<int, List<GameObject>>();
for (int i = 0; i < rootGos.Length; ++i)
{
var strArray = rootGos[i].name.Split('_');
if (strArray.Length != 2)
continue;
int index = int.Parse(strArray[1]);
Utils.AddToDict(retDict, index, rootGos[i]);
}
return retDict;
}
/// <summary>
/// 创建新的lightmap并且生成配置文件
/// </summary>
/// <param name="rootGos"></param>
public static void CreateCombinedLightmap(GameObject[] rootGos)
{
RequirePrefabLightmapInfoScript(rootGos);
var sortGos = SortAsIndex(rootGos);
var itor = sortGos.GetEnumerator();
List<string> configInfos = new List<string>();
PackDataInfoList pdList = new PackDataInfoList();
EditorUtility.DisplayProgressBar("新lightmap", "拆分lightmap", 0);
int counter = 0;
while (itor.MoveNext())
{
EditorUtility.DisplayProgressBar("新lightmap", "拆分lightmap", (counter++)/ (float)sortGos.Count);
var gos = itor.Current.Value.ToArray();
var rdInfoList = ScanRendererInfos(gos);
Rect rect;
Rect[] packedRects;
//CombineRendererInfos(rdInfoList, out rect);
CombineRendererInfos2(rdInfoList, out rect, out packedRects);
SetNewLightmapScaleOffset(rdInfoList);
//var tex = CombineTextures(rdInfoList, (int)rect.width, (int)rect.height);
var path = ConfigDefine.GetSavePrefabPath("Lightmap_" + itor.Current.Key + ".exr");
//SaveLightmapTexture(path, tex);
configInfos.Add(string.Format("{0}\t{1}", itor.Current.Key, Path.GetFileNameWithoutExtension(path)));
//构造图集打包信息传递到外部调用EXR工具
PackDataInfo pdInfo = new PackDataInfo();
pdInfo.PackImgWidth = (int)rect.width;
pdInfo.PackImgHeight = (int)rect.height;
pdInfo.SaveFileNameList.Add(Path.GetFullPath(path));
for (int i = 0; i < rdInfoList.Count; ++i)
{
//lightmap原始路径
var lightmapTex = LightMapUtil.GetFullLightmap(rdInfoList[i].Renderer);
var lightmapPath = AssetDatabase.GetAssetPath(lightmapTex);
pdInfo.LightmapPathList.Add(Path.GetFullPath(lightmapPath));
var rect1 = new Rect();
rect1 = rdInfoList[i].CutRect;
//rect1.x = rdInfoList[i].OldScaledBound.x * lightmapTex.width;
//rect1.y = rdInfoList[i].OldScaledBound.y * lightmapTex.height;
//rect1.width = rect2.width;
//rect1.height = rect2.height;
pdInfo.SourceRectList.Add(rect1);
var rect2 = new Rect();
rect2.x = packedRects[i].x * pdInfo.PackImgWidth;
rect2.y = packedRects[i].y * pdInfo.PackImgHeight;
rect2.width = rect1.width;// packedRects[i].width * pdInfo.PackImgWidth;
rect2.height = rect1.height;// packedRects[i].height * pdInfo.PackImgHeight;
pdInfo.PackedRectList.Add(rect2);
}
pdList.AddPackData(pdInfo);
}
Utils.SyncPrefabInEditor(rootGos);
SaveLightmapConfig(configInfos.ToArray());
var tempFile = System.IO.Path.GetTempFileName();
var tempFullPath = Path.Combine(Path.GetTempPath(), tempFile);
pdList.Write(tempFullPath);
//Utils.CallExe("", tempFullPath);
UnityEngine.Debug.Log("Temp path: " + tempFullPath);
}
/// <summary>
/// 给gameobject添加脚本保存Renderer的一些信息给prefab使用
/// </summary>
/// <param name="rootGos"></param>
public static void RequirePrefabLightmapInfoScript(GameObject[] rootGos)
{
for (int i = 0; i < rootGos.Length; ++i)
{
var script = rootGos[i].GetComponent<PrefabLightmapInfo>();
if (script == null)
{
script = rootGos[i].AddComponent<PrefabLightmapInfo>();
}
var prefabIndex = TerrainConfigLoader.GetIndexFromGameObjectName(rootGos[i].name);
//var index = TerrainConfigLoader.GenerateLightmapIndex(prefabIndex);
script.OnBakeFinish();
script.SetMeshIndex(prefabIndex);
}
}
public static void SaveLightmapConfig(string[] infoArray)
{
var path = ConfigDefine.GetConfigPath(TerrainConfigDefine.LIGHTMAP_CONFIG_FILENAME);
File.WriteAllLines(path, infoArray);
}
public static string SaveLightmapTexture(string path, Texture2D tex)
{
var bytes = tex.EncodeToPNG();
File.WriteAllBytes(path, bytes);
AssetDatabase.ImportAsset(path);
TextureImporter ti = (TextureImporter)AssetImporter.GetAtPath(path);
ti.textureType = TextureImporterType.Lightmap;
ti.SaveAndReimport();
return path;
}
public static Texture2D CombineLightmap(GameObject[] rootGos)
{
var rdInfoList = ScanRendererInfos(rootGos);
Rect rect;
Rect[] packedList;
CombineRendererInfos2(rdInfoList, out rect, out packedList);
var tex = CombineTextures(rdInfoList, (int)rect.width, (int)rect.height);
return tex;
}
/// <summary>
/// 收集所有Renderer的信息
/// </summary>
/// <param name="rootGos"></param>
/// <returns></returns>
public static List<RendererInfo> ScanRendererInfos(GameObject[] rootGos)
{
List<RendererInfo> rdInfoListh = new List<RendererInfo>();
for (int i = 0; i < rootGos.Length; ++i)
{
var renderers = rootGos[i].GetComponentsInChildren<Renderer>();
for (int j = 0; j < renderers.Length; ++j)
{
Vector4 bound;
Vector4 scaleBound;
Rect pickRect;
var myLightmap = LightMapUtil.PickLightmap(renderers[j], out bound, out scaleBound, out pickRect);
if (myLightmap != null)
{
RendererInfo rdInfo = new RendererInfo();
rdInfo.MapName = EditorSceneManager.GetActiveScene().name;
rdInfo.RootGoName = rootGos[i].name;
rdInfo.Renderer = renderers[j];
rdInfo.MyLightmap = myLightmap;
rdInfo.OldBound = bound;
rdInfo.OldScaledBound = scaleBound;
rdInfo.CutRect = pickRect;
rdInfoListh.Add(rdInfo);
}
}
}
rdInfoListh.Sort((x1, x2) =>
{
return -(x1.MyLightmap.width * x1.MyLightmap.height - x2.MyLightmap.width * x2.MyLightmap.height);
});
return rdInfoListh;
}
/// <summary>
/// 纹理合并
/// </summary>
/// <param name="rdInfoList"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static Texture2D CombineTextures(List<RendererInfo> rdInfoList, int width, int height)
{
Texture2D tex = LightMapUtil.CreateEmptyTexture(width, height, TextureFormat.ARGB32);
for (int i = 0; i < rdInfoList.Count; ++i)
{
var rdInfo = rdInfoList[i];
var pixels = rdInfoList[i].MyLightmap.GetPixels();
tex.SetPixels((int)rdInfo.Position.x, (int)rdInfo.Position.y, rdInfo.MyLightmap.width, rdInfo.MyLightmap.height, pixels);
}
tex.alphaIsTransparency = true;
tex.Apply();
return tex;
}
//设置新的lightmapScaleOffset
public static void SetNewLightmapScaleOffset(List<RendererInfo> rdInfoList)
{
for (int i = 0; i < rdInfoList.Count; ++i)
{
//重置scaleOffset
var script = rdInfoList[i].Renderer.gameObject.GetComponentInParent<PrefabLightmapInfo>();
if (script != null)
{
script.SetLightmapScaleOffset(rdInfoList[i].Renderer, rdInfoList[i].MyLightmapScaleInfo);
}
}
}
/// <summary>
/// 合并光照贴图,计算位置和范围 lightmapScaleOffset
/// </summary>
/// <param name="rdInfoList"></param>
/// <param name="bound"></param>
public static void CombineRendererInfos(List<RendererInfo> rdInfoList, out Rect bound)
{
bound = new Rect(0, 0, 128, 128);
while (true)
{
bool finish = true;
List<Rect> rectList = new List<Rect>();
rectList.Add(bound);
for (int i = 0; i < rdInfoList.Count; ++i)
{
Vector2 pos = Vector2.zero;
if (InsertRect(rectList, new Rect(0, 0, rdInfoList[i].MyLightmap.width, rdInfoList[i].MyLightmap.height), out pos))
{
rdInfoList[i].Position = pos;
var blockW = rdInfoList[i].MyLightmap.width;
var blockH = rdInfoList[i].MyLightmap.height;
var texWidth = bound.width;
var texHeight = bound.height;
var sourceBounds = rdInfoList[i].OldBound;
var scaleUVX = (blockW) / (texWidth * (sourceBounds.z - sourceBounds.x));
var offsetUVX = pos.x / (float)texWidth - scaleUVX * sourceBounds.x;
var scaleUVY = (blockH) / (texHeight * (sourceBounds.w - sourceBounds.y));
var offsetUVY = pos.y / (float)texHeight - scaleUVY * sourceBounds.y;
rdInfoList[i].MyLightmapScaleInfo = new Vector4(scaleUVX, scaleUVY, offsetUVX, offsetUVY);
}
else
{
bound.width *= 2;
bound.height *= 2;
finish = false;
break;
}
}
if (finish)
break;
}
}
/// <summary>
/// 合并光照贴图,计算位置和范围 lightmapScaleOffset
/// </summary>
/// <param name="rdInfoList"></param>
/// <param name="bound"></param>
public static void CombineRendererInfos2(List<RendererInfo> rdInfoList, out Rect bound, out Rect[] packedRects)
{
packedRects = new Rect[0];
//计算总面积
int areaAll = 0;
for(int i = 0; i < rdInfoList.Count; ++i)
{
areaAll += (rdInfoList[i].MyLightmap.width * rdInfoList[i].MyLightmap.height);
}
//计算最接近这个面积的宽高尺寸
int size = 128;
while(true)
{
if (size * size > areaAll)
break;
size *= 2;
}
bound = new Rect(0, 0, size, size);
if (areaAll == 0)
return;
while (true)
{
List<Rect> rectList = new List<Rect>();
List<Texture2D> texList = new List<Texture2D>();
rectList.Add(bound);
for (int i = 0; i < rdInfoList.Count; ++i)
{
Texture2D tex = new Texture2D(rdInfoList[i].MyLightmap.width, rdInfoList[i].MyLightmap.height);
texList.Add(tex);
}
Texture2D combined = new Texture2D((int)bound.width, (int)bound.height, TextureFormat.ARGB32, false);
packedRects = combined.PackTextures(texList.ToArray(), 0, size);
if (packedRects == null || packedRects.Length == 0 ||
packedRects[0].width * bound.width < texList[0].width || packedRects[0].height * bound.height < texList[0].height)
{
bound.width *= 2;
bound.height *= 2;
size *= 2;
}
else
{
bound.width = combined.width;
bound.height = combined.height;
for (int i = 0; i < rdInfoList.Count; ++i)
{
Vector2 pos = new Vector2(packedRects[i].x, packedRects[i].y);
{
rdInfoList[i].Position = pos;
rdInfoList[i].Position.x *= bound.width;
rdInfoList[i].Position.y *= bound.height;
//为了消除接缝黑边
var ignorPixels = 1f;
var blockW = rdInfoList[i].MyLightmap.width;
var blockH = rdInfoList[i].MyLightmap.height;
var texWidth = bound.width;
var texHeight = bound.height;
var sourceBounds = rdInfoList[i].OldBound;
var scaleUVX = (blockW - ignorPixels) / (texWidth * (sourceBounds.z - sourceBounds.x));
var offsetUVX = (pos.x) / (float)texWidth - scaleUVX * sourceBounds.x;
var scaleUVY = (blockH - ignorPixels) / (texHeight * (sourceBounds.w - sourceBounds.y));
var offsetUVY = (pos.y) / (float)texHeight - scaleUVY * sourceBounds.y;
rdInfoList[i].MyLightmapScaleInfo = new Vector4(scaleUVX, scaleUVY, offsetUVX, offsetUVY);
}
}
break;
}
}
}
/// <summary>
/// 简陋的图集打包算法
/// </summary>
/// <param name="spaceRectList"></param>
/// <param name="rect"></param>
/// <param name="insertPos"></param>
/// <returns></returns>
public static bool InsertRect(List<Rect> spaceRectList, Rect rect, out Vector2 insertPos)
{
bool ret = false;
insertPos = Vector2.zero;
for(int i = 0; i < spaceRectList.Count; ++i)
{
//放到一个矩形中后会产生3个小矩形碎片重新添加到空余矩形列表中
if(spaceRectList[i].width >= rect.width && spaceRectList[i].height >= rect.height)
{
var startPos = spaceRectList[i].position;
var endPos = startPos + new Vector2(rect.width, rect.height);
var moreWidth = spaceRectList[i].width - rect.width;
var moreHeight = spaceRectList[i].height - rect.height;
Rect bottom = new Rect(startPos.x, startPos.y + rect.height, rect.width, moreHeight);
Rect right = new Rect(startPos.x + rect.width, startPos.y, moreWidth, rect.height);
Rect cross = new Rect(startPos.x + rect.width, startPos.y + rect.height, moreWidth, moreHeight);
spaceRectList.RemoveAt(i);
if(bottom.width > 0 && bottom.height > 0) spaceRectList.Add(bottom);
if(right.width > 0 && right.height > 0) spaceRectList.Add(right);
if (cross.width > 0 && cross.height > 0) spaceRectList.Add(cross);
insertPos = startPos;
ret = true;
break;
}
}
spaceRectList.Sort((x1, x2) =>
{
return (int)(x1.width * x1.height - x2.width * x2.height);
});
return ret;
}
}
}