using Thousandto.Core.Asset; using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; namespace Thousandto.Editor.Test { /// /// 从lightmap中扣除对应Renderer的光照贴图,并且与同一网格的 /// 光照贴图合并成一个新的lightmap /// 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() { } /// /// 将gameobject通过名字中的index来分类 /// /// /// public static Dictionary> SortAsIndex(GameObject[] rootGos) { Dictionary> retDict = new Dictionary>(); 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; } /// /// 创建新的lightmap,并且生成配置文件 /// /// public static void CreateCombinedLightmap(GameObject[] rootGos) { RequirePrefabLightmapInfoScript(rootGos); var sortGos = SortAsIndex(rootGos); var itor = sortGos.GetEnumerator(); List configInfos = new List(); 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); } /// /// 给gameobject添加脚本,保存Renderer的一些信息给prefab使用 /// /// public static void RequirePrefabLightmapInfoScript(GameObject[] rootGos) { for (int i = 0; i < rootGos.Length; ++i) { var script = rootGos[i].GetComponent(); if (script == null) { script = rootGos[i].AddComponent(); } 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; } /// /// 收集所有Renderer的信息 /// /// /// public static List ScanRendererInfos(GameObject[] rootGos) { List rdInfoListh = new List(); for (int i = 0; i < rootGos.Length; ++i) { var renderers = rootGos[i].GetComponentsInChildren(); 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; } /// /// 纹理合并 /// /// /// /// /// public static Texture2D CombineTextures(List 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 rdInfoList) { for (int i = 0; i < rdInfoList.Count; ++i) { //重置scaleOffset var script = rdInfoList[i].Renderer.gameObject.GetComponentInParent(); if (script != null) { script.SetLightmapScaleOffset(rdInfoList[i].Renderer, rdInfoList[i].MyLightmapScaleInfo); } } } /// /// 合并光照贴图,计算位置和范围 lightmapScaleOffset /// /// /// public static void CombineRendererInfos(List rdInfoList, out Rect bound) { bound = new Rect(0, 0, 128, 128); while (true) { bool finish = true; List rectList = new List(); 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; } } /// /// 合并光照贴图,计算位置和范围 lightmapScaleOffset /// /// /// public static void CombineRendererInfos2(List 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 rectList = new List(); List texList = new List(); 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; } } } /// /// 简陋的图集打包算法 /// /// /// /// /// public static bool InsertRect(List 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; } } }