/// <summary> /// 为Renderer计算新的lightmapScaleOffset /// </summary> /// <param name="meshUVBounds">模型资源的原始UV包围盒</param> /// <param name="uvBounds">定向到Lightmap上的一块用于指定Renderer的Lightmap纹理区间</param> /// <returns></returns> static Vector4d CalculateUVScaleOffset(Vector4d meshUVBounds, Vector4d uvBounds) { var scaleOffset = new Vector4d(); scaleOffset.x = (uvBounds.z - uvBounds.x) / (meshUVBounds.z - meshUVBounds.x); scaleOffset.y = (uvBounds.w - uvBounds.y) / (meshUVBounds.w - meshUVBounds.y); scaleOffset.z = uvBounds.z - meshUVBounds.z * scaleOffset.x; scaleOffset.w = uvBounds.w - meshUVBounds.w * scaleOffset.y; return(scaleOffset); }
unsafe static void _RepackLightmap() { var gos = Selection.gameObjects; var checkMSG = false; REOPEN: if (gos.Length == 0) { UDebug.LogError("Please select a GameObject in hierarchy to process!"); return; } var hierarchy = UnityUtils.GetHierarchyPath(gos[0].transform); if (!checkMSG) { checkMSG = true; if (!EditorUtility.DisplayDialog(Title, "请确认:处理的是原始场景,如果已经处理过,请重新加载场景。", "直接整", "好吧,重新加载场景")) { UnityEditor.SceneManagement.EditorSceneManager.OpenScene(currentScene); gos = new GameObject[] { GameObject.Find(hierarchy) }; goto REOPEN; } } // 经过测试,这个值确实是输出到了最终lightmap纹理中纹理单位的间隔 var Padding = LightmapEditorSettings.padding; Bounds bounds; var workingPath = CleanTempFolder(); var allRenderers = CollectAllLightmapedRenderers(gos, out bounds).Values.ToList(); // 把搜集到的原始Mesh重新保存成asset格式,便于后面的修改(FBX无法修改) var meshes = new Dictionary <Mesh, Mesh>(); for (int i = 0; i < allRenderers.Count; ++i) { var ri = allRenderers[i]; var srcMesh = ri.mesh; if (!srcMesh.isReadable) { UDebug.LogError("Mesh \"{0}\" is not readable!"); continue; } var mf = ri.renderer.transform.GetComponent <MeshFilter>(); if (mf == null) { continue; } Mesh dstMesh = null; if (!meshes.TryGetValue(srcMesh, out dstMesh)) { var assetPath = String.Format("{0}/{1}_{2}.mesh.asset", workingPath, srcMesh.name, meshes.Count); if (File.Exists(assetPath)) { AssetDatabase.DeleteAsset(assetPath); } AssetDatabase.CreateAsset(UnityEngine.Object.Instantiate <Mesh>(srcMesh), assetPath); dstMesh = AssetDatabase.LoadAssetAtPath <Mesh>(assetPath); meshes.Add(srcMesh, dstMesh); } //覆盖原始模型 mf.sharedMesh = dstMesh; } if (meshes.Count > 0) { var litmapData = LoadAllLightmapData(); if (litmapData == null) { UDebug.LogError("Load source lightmaps failed."); return; } var inputRects = new List <NativeAPI.stbrp_rect>(); var all_atlas_pixels = new Dictionary <int, AtlasPixelsPage>(); var lightmapRectInfoList = new List <LightmapRect>(); using (var tp = new TexturePacker()) { for (int i = 0; i < allRenderers.Count; ++i) { var ri = allRenderers[i]; var r = ri.renderer; var uv2BoundsInfo = GetMeshUV2Bounds(r); int lightmapIndex = uv2BoundsInfo.lightmapIndex; if (lightmapIndex >= 0 && lightmapIndex < litmapData.Count) { var lightmapData = litmapData[r.lightmapIndex]; var meshUVBounds = uv2BoundsInfo.srcUVBounds; var lightmapUVBounds = uv2BoundsInfo.lightmapUVBounds; Real fMinX = lightmapUVBounds.x * lightmapData.width; Real fMaxX = lightmapUVBounds.z * lightmapData.width; Real fMinY = lightmapUVBounds.y * lightmapData.height; Real fMaxY = lightmapUVBounds.w * lightmapData.height; #if LIGHTMAPREPACKER_PRECISION_HIGH int pixMinX = ( int )Math.Max(Math.Floor(lightmapUVBounds.x * lightmapData.width), 0); int pixMaxX = ( int )Math.Min(Math.Ceiling(lightmapUVBounds.z * lightmapData.width), lightmapData.width); int pixMinY = ( int )Math.Max(Math.Floor(lightmapUVBounds.y * lightmapData.height), 0); int pixMaxY = ( int )Math.Min(Math.Ceiling(lightmapUVBounds.w * lightmapData.height), lightmapData.height); #else int pixMinX = ( int )Math.Max(Mathf.Floor(lightmapUVBounds.x * lightmapData.width), 0); int pixMaxX = ( int )Math.Min(Mathf.Ceil(lightmapUVBounds.z * lightmapData.width), lightmapData.width); int pixMinY = ( int )Math.Max(Mathf.Floor(lightmapUVBounds.y * lightmapData.height), 0); int pixMaxY = ( int )Math.Min(Mathf.Ceil(lightmapUVBounds.w * lightmapData.height), lightmapData.height); #endif if (pixMaxX <= pixMinX || pixMaxY <= pixMinY) { UDebug.LogError("Invalid LightmapUV: {0}", UnityUtils.GetHierarchyPath(r.transform)); continue; } // 传入自己的TexturePacker之前,把border大小加上去 var rt = default(NativeAPI.stbrp_rect); rt.w = ( ushort )((pixMaxX - pixMinX) + Padding); rt.h = ( ushort )((pixMaxY - pixMinY) + Padding); var lightmapRect = new LightmapRect(); lightmapRect.renderer = r; lightmapRect.lightmapUVBounds = lightmapUVBounds; lightmapRect.meshUVBounds = meshUVBounds; lightmapRect.lightmapPixelBounds = new Vector4i(pixMinX, pixMinY, pixMaxX, pixMaxY); lightmapRect.lightmapPixelFBounds = new Vector4d(fMinX, fMinY, fMaxX, fMaxY); rt.id = lightmapRectInfoList.Count; inputRects.Add(rt); lightmapRectInfoList.Add(lightmapRect); } } tp.PackRects(inputRects.ToArray()); tp.ForEach( (page, atlasSize, rt) => { var lightmapRect = lightmapRectInfoList[rt.id]; var renderer = lightmapRect.renderer; var lightmapData = litmapData[renderer.lightmapIndex]; AtlasPixelsPage atlas_pixels; if (!all_atlas_pixels.TryGetValue(page, out atlas_pixels)) { atlas_pixels = new AtlasPixelsPage { pixels = new Vector4[atlasSize * atlasSize], size = atlasSize }; all_atlas_pixels.Add(page, atlas_pixels); } // 原始像素包围盒,不包含Border var i_minx = ( int )lightmapRect.lightmapPixelBounds.x; var i_miny = ( int )lightmapRect.lightmapPixelBounds.y; var i_maxx = ( int )lightmapRect.lightmapPixelBounds.z; var i_maxy = ( int )lightmapRect.lightmapPixelBounds.w; #if FIXING_LIGHTMAP_UVBORDER int rtOffset = Padding / 2; if (Padding > 0) { // 扩大边框,多拷贝一些边缘像素消除黑边 i_minx -= Padding / 2; i_miny -= Padding / 2; i_maxx += Padding - Padding / 2; i_maxy += Padding - Padding / 2; } #else int rtOffset = 0; #endif // lightmap像素块copy fixed(Vector4 * _atlas_pixels = atlas_pixels.pixels) { for (int y = i_miny; y < i_maxy; y++) { int dy = y - i_miny + (rt.y - rtOffset); if (dy < 0 || dy >= atlasSize) { continue; } // 纹理坐标需要翻转一下,像素是翻转了的 int _dy = atlasSize - 1 - dy; int _sy = lightmapData.height - 1 - y; int _dy_stride = _dy * atlasSize; int _sy_stride = _sy * lightmapData.width; for (int x = i_minx; x < i_maxx; x++) { int dx = x - i_minx + (rt.x - rtOffset); if (dx < 0 || dx >= atlasSize) { continue; } _atlas_pixels[_dy_stride + dx] = lightmapData.GetPixelClamped(x, _sy); } } } var uvBounds = new Vector4d(); Real errorX = 0; Real errorY = 0; Real errorZ = 0; Real errorW = 0; #if FIXING_LIGHTMAP_UVERROR errorX = lightmapRect.lightmapPixelFBounds.x - lightmapRect.lightmapPixelBounds.x; errorY = lightmapRect.lightmapPixelFBounds.y - lightmapRect.lightmapPixelBounds.y; errorZ = lightmapRect.lightmapPixelFBounds.z - lightmapRect.lightmapPixelBounds.z; errorW = lightmapRect.lightmapPixelFBounds.w - lightmapRect.lightmapPixelBounds.w; #endif uvBounds.x = ((rt.x + errorX) / ( Real )atlasSize); uvBounds.y = ((rt.y + errorY) / ( Real )atlasSize); // 计算在新的lightmap纹理下的纹理坐标包围盒,由于pack之前我们人为加了一个Padding,所以这里计算要减去 uvBounds.z = (((rt.x + rt.w + errorZ) - Padding) / ( Real )atlasSize); uvBounds.w = (((rt.y + rt.h + errorW) - Padding) / ( Real )atlasSize); if (Mathf.Approximately(( float )lightmapRect.meshUVBounds.z, ( float )lightmapRect.meshUVBounds.x) || Mathf.Approximately(( float )lightmapRect.meshUVBounds.w, ( float )lightmapRect.meshUVBounds.y)) { // 无效 renderer.lightmapIndex = -1; renderer.lightmapScaleOffset = new Vector4(1, 1, 0, 0); UDebug.LogError("Invalid LightmapUV's bounds: {0}", UnityUtils.GetHierarchyPath(lightmapRect.renderer.transform)); } else { // 计算新的ScaleOffset,映射到新的lightmap纹理 renderer.lightmapIndex = page; var sf = CalculateUVScaleOffset(lightmapRect.meshUVBounds, uvBounds); renderer.lightmapScaleOffset = new Vector4(( float )sf.x, ( float )sf.y, ( float )sf.z, ( float )sf.w); } } ); if (all_atlas_pixels.Count > 0) { var lightmapOutputPath = workingPath; var lightmaps = new LightmapData[all_atlas_pixels.Count]; var lightmapPathList = new List <KeyValuePair <String, int> >(all_atlas_pixels.Count); int pageIndex = 0; foreach (var pagePixels in all_atlas_pixels) { fixed(Vector4 *_atlas_pixels = pagePixels.Value.pixels) { EditorUtility.DisplayProgressBar(Title, "Saving lightmap atlas...", ( float )pageIndex / ( float )all_atlas_pixels.Count); pageIndex++; int atlasSize = pagePixels.Value.size; var path = String.Format("{0}/lightmap_{1}.exr", lightmapOutputPath, pagePixels.Key); if (File.Exists(path)) { AssetDatabase.DeleteAsset(path); } NativeAPI.SaveEXR(( IntPtr )_atlas_pixels, atlasSize, atlasSize, 4, 0, path); lightmapPathList.Add(new KeyValuePair <String, int>(path, pagePixels.Key)); } } try { AssetDatabase.Refresh(); pageIndex = 0; for (int j = 0; j < lightmapPathList.Count; j++) { var path = lightmapPathList[j].Key; var page = lightmapPathList[j].Value; EditorUtility.DisplayProgressBar(Title, "Apply lightmap atlas...", ( float )pageIndex / ( float )all_atlas_pixels.Count); pageIndex++; var ti = AssetImporter.GetAtPath(path) as TextureImporter; if (ti != null) { if (ti.textureType != TextureImporterType.Lightmap || ti.isReadable || ti.wrapMode != TextureWrapMode.Clamp) { ti.textureType = TextureImporterType.Lightmap; ti.isReadable = false; ti.wrapMode = TextureWrapMode.Clamp; AssetDatabase.ImportAsset(ti.assetPath); } var tex = AssetDatabase.LoadAssetAtPath(path, typeof(Texture2D)) as Texture2D; if (tex != null) { lightmaps[page] = new LightmapData(); lightmaps[page].lightmapColor = tex; } } } } finally { LightmapSettings.lightmaps = lightmaps; } } } } }