Main/Assets/Editor/SplitTerrain/LightMapSpliter.cs
2025-01-25 04:38:09 +08:00

442 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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