#if UNITY_EDITOR #define USE_TERRAINS // Disable 'obsolete' warnings #pragma warning disable 0618 using UnityEngine; using UnityEditor; using System; using System.IO; using System.Text; using System.Linq; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityEngine.Rendering; using System.Reflection; public class ftBuildGraphics : ScriptableWizard { const int UVGBFLAG_NORMAL = 1; const int UVGBFLAG_FACENORMAL = 2; const int UVGBFLAG_ALBEDO = 4; const int UVGBFLAG_EMISSIVE = 8; const int UVGBFLAG_POS = 16; const int UVGBFLAG_SMOOTHPOS = 32; const int UVGBFLAG_TANGENT = 64; const int UVGBFLAG_TERRAIN = 128; const int UVGBFLAG_RESERVED = 256; [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void InitShaders(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void LoadScene(string path); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] private static extern void SetAlbedos(int count, IntPtr[] tex); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] private static extern int CopyAlbedos(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] private static extern int CopyHeightmapsFromRAM(int count, TexInput[] tex); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void FreeAlbedoCopies(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] private static extern void SetAlphas(int count, IntPtr[] tex, float[] alphaRefs, int[] alphaChannels, int numLODs, bool flip); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] private static extern void SetAlphasFromRAM(int count, System.IntPtr tex, float[] alphaRefs, int[] alphaChannels, int numLODs, bool flip); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SaveSky(IntPtr tex, float rx, float ry, float rz, float ux, float uy, float uz, float fx, float fy, float fz, string path, bool isLinear, bool doubleLDR, bool rgbm); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SaveCookie(IntPtr tex, string path); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SaveCookieFromRAM(TexInput tex, string path); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int ftRenderUVGBuffer(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SetUVGBFlags(int flags); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SetFixPos(bool enabled); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SetCompression(bool enabled); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int ftGenerateAlphaBuffer(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int SaveGBufferMap(IntPtr tex, string path, bool compressed); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int SaveGBufferMapFromRAM(byte[] tex, int size, string path, bool compressed); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int GetABGErrorCode(); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern int GetUVGBErrorCode(); [DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)] public static extern int uvrLoad(float[] inputUVs, int numVerts, int[] inputIndices, int numIndices); [DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)] public static extern int uvrRepack(float padding, int resolution); [DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)] public static extern int uvrUnload(); public enum TexInputType { Color, FloatColor, HalfColor }; public struct TexInput { public byte[] data; public ushort width, height; }; static Material matCubemapToStripExport; static int voffset, soffset, ioffset; static public string scenePath = ""; static BufferedBinaryWriterFloat fvbfull, fvbtrace, fvbtraceTex, fvbtraceUV0; static BufferedBinaryWriterInt fib; static BinaryWriter fscene, fmesh, flmid, fseamfix, fsurf, fmatid, fmatide, fmatidh, fmatideb, falphaid, fib32, fhmaps; static BinaryWriter[] fib32lod; static BinaryWriter[] falphaidlod; public static ftLightmapsStorage.ImplicitLightmapData tempStorage = new ftLightmapsStorage.ImplicitLightmapData(); public static float texelsPerUnit = 20; public static int minAutoResolution = 16; public static int maxAutoResolution = 4096; public static bool mustBePOT = true; public static bool autoAtlas = true; public static bool unwrapUVs = true; public static bool forceDisableUnwrapUVs = false; public static bool exportShaderColors = true; // albedo and emission (sometimes normal) always come from the engine now //public static int atlasPaddingPixels = ftAdditionalConfig.texelPaddingForDefaultAtlasPacker; public static bool atlasCountPriority = false; public static bool splitByScene = false; public static bool splitByTag = true; public static bool uvPaddingMax = false; public static bool exportTerrainAsHeightmap = true; public static bool exportTerrainTrees = false; public static bool uvgbHeightmap = true; public static bool texelsPerUnitPerMap = false; public static float mainLightmapScale = 1; public static float maskLightmapScale = 1; public static float dirLightmapScale = 1; const float atlasScaleUpValue = 1.01f; const int atlasMaxTries = 100; const float alphaInstanceThreshold = 5.0f / 255.0f; const bool flipAlpha = true; public static string overwriteExtensionCheck = ".hdr"; public static bool overwriteWarning = false; public static bool overwriteWarningSelectedOnly = false; public static bool memoryWarning = false; public static bool modifyLightmapStorage = true; public static bool validateLightmapStorageImmutability = true; public static bool sceneNeedsToBeRebuilt = false; //public static int unityVersionMajor = 0; //public static int unityVersionMinor = 0; static int areaLightCounter = -2; public static int sceneLodsUsed = 0; static GameObject lmgroupHolder; static BakeryLightmapGroup lightProbeLMGroup = null; static BakeryLightmapGroup volumeLMGroup = null; static List terrainObjectList; #if USE_TERRAINS static List terrainObjectToActual; #endif static List terrainObjectToHeightMap; static List terrainObjectToHeightMapRAM; static List terrainObjectToBounds; static List terrainObjectToLMID; static List terrainObjectToBoundsUV; static List terrainObjectToFlags; static List> terrainObjectToHeightMips; //static List> terrainObjectToNormalMips; //static List terrainObjectToNormalMip0; static List temporaryAreaLightMeshList; static List temporaryAreaLightMeshList2; public static List temporaryGameObjects; static Dictionary cmp_objToLodLevel; static Dictionary cmp_holderObjArea; public static Dictionary> lodLevelsVisibleInLodLevel = new Dictionary>(); // defines LOD levels visible in chosen LOD level public static List vbtraceTexPosNormalArray; // global vbTraceTex.bin positions/normals public static List vbtraceTexUVArray; // global vbTraceTex.bin UVs public static float[] vbtraceTexUVArrayLOD; // global vbTraceTex.bin LOD UVs public static List atlasOnlyObj; public static List atlasOnlyScaleOffset; public static List atlasOnlySize; public static List atlasOnlyID; public static List atlasOnlyGroup; public static ftGlobalStorage.AtlasPacker atlasPacker = ftGlobalStorage.AtlasPacker.xatlas; public static bool forceAllAreaLightsSelfshadow = false; public static bool postPacking = true; public static bool holeFilling = false; static int floatOverWarnCount = 0; const int maxFloatOverWarn = 10; static ftGlobalStorage gstorage; static BakeryProjectSettings pstorage; static ExportSceneData _tempData; static public void DebugLogError(string text) { ftRenderLightmap.DebugLogError(text); } class FarSphereRenderData { public RenderTexture[] albedo, depth, normal; public Matrix4x4[] viewProj; public Vector3 pos; public Mesh[] meshes; public Texture2D[] textures; } static Shader farSphereRshader, farSphereRshaderOcc, farSphereSshader, farSphereProjClipShader, farSphereDilateShader; static Material farSphereMat, farSphereMatOcc, farSphereProjClipMat, farSphereDilateMat; static ComputeShader farSphereCSTransform, farSphereCSCull; class AtlasNode { public AtlasNode child0, child1; public Rect rc; public GameObject obj; bool leaf = true; public AtlasNode Insert(GameObject o, Rect rect) { if (!leaf) { var newNode = child0.Insert(o, rect); if (newNode != null) return newNode; return child1.Insert(o, rect); } else { if (obj != null) return null; var fits = (rect.width <= rc.width && rect.height <= rc.height); if (!fits) return null; var fitsExactly = (rect.width == rc.width && rect.height == rc.height); if (fitsExactly) { obj = o; return this; } child0 = new AtlasNode(); child1 = new AtlasNode(); var dw = rc.width - rect.width; var dh = rc.height - rect.height; if (dw > dh) { child0.rc = new Rect(rc.x, rc.y, rect.width, rc.height); child1.rc = new Rect(rc.x + rect.width, rc.y, rc.width - rect.width, rc.height); } else { child0.rc = new Rect(rc.x, rc.y, rc.width, rect.height); child1.rc = new Rect(rc.x, rc.y + rect.height, rc.width, rc.height - rect.height); } leaf = false; return child0.Insert(o, rect); } } public void GetMax(ref float maxx, ref float maxy) { if (obj != null) { if ((rc.x + rc.width) > maxx) maxx = rc.x + rc.width; if ((rc.y + rc.height) > maxy) maxy = rc.y + rc.height; } if (child0 != null) child0.GetMax(ref maxx, ref maxy); if (child1 != null) child1.GetMax(ref maxx, ref maxy); } public void Transform(float offsetx, float offsety, float scalex, float scaley) { rc.x *= scalex; rc.y *= scaley; rc.x += offsetx; rc.y += offsety; rc.width *= scalex; rc.height *= scaley; if (child0 != null) child0.Transform(offsetx, offsety, scalex, scaley); if (child1 != null) child1.Transform(offsetx, offsety, scalex, scaley); } }; static ftBuildGraphics() { //ftRenderLightmap.PatchPath(); //var unityVer = Application.unityVersion.Split('.'); //unityVersionMajor = Int32.Parse(unityVer[0]); //unityVersionMinor = Int32.Parse(unityVer[1]); } static void DebugLogInfo(string info) { if (pstorage == null) pstorage = ftLightmaps.GetProjectSettings(); if ((pstorage.logLevel & (int)BakeryProjectSettings.LogLevel.Info) != 0) Debug.Log(info); } static void DebugLogWarning(string info) { if (pstorage == null) pstorage = ftLightmaps.GetProjectSettings(); if ((pstorage.logLevel & (int)BakeryProjectSettings.LogLevel.Warning) != 0) Debug.LogWarning(info); } static string FilterNonASCII(string s) { if (Encoding.UTF8.GetByteCount(s) == s.Length) return s; var bytes = Encoding.UTF8.GetBytes(s); var newStr = ""; for(int i=0; i 127) { newStr += "_" + (int)bytes[i]; } else { newStr += Convert.ToChar(bytes[i]); } } return newStr; } static void exportVBPos(BinaryWriter f, Transform t, Mesh m, Vector3[] vertices) { for(int i=0;i(); if (mr as MeshRenderer == null && mr as SkinnedMeshRenderer == null) { // possibly multiple renderers on one gameobject? mr = obj.GetComponent() as Renderer; if (mr != null) return mr; mr = obj.GetComponent() as Renderer; if (mr != null) return mr; return null; } return mr; } static BakeryLightmapGroup GetLMGroupFromObjectExplicit(GameObject obj, ExportSceneData data) { lmgroupHolder = null; var lmgroupSelector = obj.GetComponent(); // if object has explicit lmgroup if (lmgroupSelector == null) { // if parents have explicit lmgroup var t2 = obj.transform.parent; while(lmgroupSelector == null && t2 != null) { lmgroupSelector = t2.GetComponent(); t2 = t2.parent; } } BakeryLightmapGroup lmgroup = null; if (lmgroupSelector != null) { lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup; lmgroupHolder = lmgroupSelector.gameObject; var scaleInLm = data.objToScaleInLm[obj]; if (scaleInLm == 0.0f) lmgroup = data.autoVertexGroup; //null; // ignore lightmaps when scaleInLightmap == 0 } return lmgroup; } static BakeryLightmapGroup GetLMGroupFromObject(GameObject obj, ExportSceneData data) { UnityEngine.Object lmgroupObj = null; BakeryLightmapGroup lmgroup = null; lmgroupHolder = null; var lmgroupSelector = obj.GetComponent(); // if object has explicit lmgroup tempStorage.implicitGroupMap.TryGetValue(obj, out lmgroupObj); // or implicit lmgroup = (BakeryLightmapGroup)lmgroupObj; lmgroupHolder = obj; if (lmgroupSelector == null && lmgroup == null) { // if parents have explicit/implicit lmgroup var t2 = obj.transform.parent; while(lmgroupSelector == null && lmgroup == null && t2 != null) { lmgroupSelector = t2.GetComponent(); tempStorage.implicitGroupMap.TryGetValue(t2.gameObject, out lmgroupObj); lmgroup = (BakeryLightmapGroup)lmgroupObj; lmgroupHolder = t2.gameObject; t2 = t2.parent; } } if (lmgroupSelector != null) { lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup; } if (lmgroup != null) { var r = GetValidRenderer(obj); if (r) { var scaleInLm = data.objToScaleInLm[obj]; if (scaleInLm == 0.0f) lmgroup = data.autoVertexGroup; // null; // ignore lightmaps when scaleInLightmap == 0 } } else { lmgroupHolder = null; } return lmgroup; } // use by ftRenderLightmap public static BakeryLightmapGroup GetLMGroupFromObject(GameObject obj) { UnityEngine.Object lmgroupObj = null; BakeryLightmapGroup lmgroup = null; lmgroupHolder = null; var lmgroupSelector = obj.GetComponent(); // if object has explicit lmgroup tempStorage.implicitGroupMap.TryGetValue(obj, out lmgroupObj); // or implicit lmgroup = (BakeryLightmapGroup)lmgroupObj; lmgroupHolder = obj; if (lmgroupSelector == null && lmgroup == null) { // if parents have explicit/implicit lmgroup var t2 = obj.transform.parent; while(lmgroupSelector == null && lmgroup == null && t2 != null) { lmgroupSelector = t2.GetComponent(); tempStorage.implicitGroupMap.TryGetValue(t2.gameObject, out lmgroupObj); lmgroup = (BakeryLightmapGroup)lmgroupObj; lmgroupHolder = t2.gameObject; t2 = t2.parent; } } if (lmgroupSelector != null) { lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup; } if (lmgroup != null) { var r = GetValidRenderer(obj); if (r) { var so = new SerializedObject(r); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; #if UNITY_2019_2_OR_NEWER var _r = r as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif //var scaleInLm = data.objToScaleInLm[obj]; if (scaleInLm == 0.0f) lmgroup = null; // null; // ignore lightmaps when scaleInLightmap == 0 } } else { lmgroupHolder = null; } return lmgroup; } public static void exportVBTraceTexAttribs(List arrPosNormal, List arrUV, Vector3[] vertices, Vector3[] normals, Vector2[] uv2, int lmid, bool vertexBake, GameObject obj) { for(int i=0;i0) { u = Mathf.Clamp(uv2[i].x, 0, 0.99999f); v = Mathf.Clamp(1.0f - uv2[i].y, 0, 0.99999f); } } else { if (uv2.Length>0 && !vertexBake) { u = Mathf.Clamp(uv2[i].x, 0, 0.99999f); v = Mathf.Clamp(uv2[i].y, 0, 0.99999f); } else if (vertexBake) { u = uv2[i].x; v = uv2[i].y - 1.0f; } } float origU = u; if (lmid >= 0) { u += lmid * 10; if ((int)u > lmid*10) { // attempt fixing float overflow u = (lmid*10+1) - (lmid*10+1)*0.0000002f; if ((int)u > lmid*10) { if (floatOverWarnCount < maxFloatOverWarn) { Debug.LogError("Float overflow for " + obj.name + " (U: " + origU + ", LMID: " + lmid + ")"); floatOverWarnCount++; } } } } else { u = lmid * 10 - u; if ((int)u != lmid*10) { u = -u; lmid = -lmid; u = (lmid*10+1) - (lmid*10+1)*0.0000002f; if ((int)u > lmid*10) { if (floatOverWarnCount < maxFloatOverWarn) { Debug.LogError("Float overflow for " + obj.name + " (U: " + origU + ", LMID: " + lmid + ")"); floatOverWarnCount++; } } u = -u; lmid = -lmid; } } arrUV.Add(u); arrUV.Add(v); } } static void exportVBTraceUV0(BufferedBinaryWriterFloat f, Vector2[] uvs, int vertCount) { if (uvs.Length == 0) { for(int i=0;i0) { f.Write(uv2[i].x); f.Write(uv2[i].y); } else { f.Write(0.0f); f.Write(0.0f); } } } // Either I'm dumb, or it's impossible to make generics work with it (only worked in .NET 3.5) class BufferedBinaryWriterFloat { [StructLayout(LayoutKind.Explicit)] public class ReinterpretBuffer { [FieldOffset(0)] public byte[] bytes; [FieldOffset(0)] public float[] elements; } BinaryWriter f; ReinterpretBuffer buffer; int bufferPtr; int bufferSize; int elementSize; public BufferedBinaryWriterFloat(BinaryWriter b, int elemSize = 4, int buffSizeInFloats = 1024*1024) { f = b; buffer = new ReinterpretBuffer(); buffer.bytes = new byte[buffSizeInFloats * elemSize]; bufferPtr = 0; bufferSize = buffSizeInFloats; elementSize = elemSize; } void Flush() { if (bufferPtr == 0) return; f.Write(buffer.bytes, 0, bufferPtr * elementSize); bufferPtr = 0; } public void Write(float x) { buffer.elements[bufferPtr] = x; bufferPtr++; if (bufferPtr == bufferSize) Flush(); } public void Close() { Flush(); f.Close(); } } class BufferedBinaryWriterInt { [StructLayout(LayoutKind.Explicit)] public class ReinterpretBuffer { [FieldOffset(0)] public byte[] bytes; [FieldOffset(0)] public int[] elements; } BinaryWriter f; ReinterpretBuffer buffer; int bufferPtr; int bufferSize; int elementSize; public BufferedBinaryWriterInt(BinaryWriter b, int elemSize = 4, int buffSizeInFloats = 1024*1024) { f = b; buffer = new ReinterpretBuffer(); buffer.bytes = new byte[buffSizeInFloats * elemSize]; bufferPtr = 0; bufferSize = buffSizeInFloats; elementSize = elemSize; } void Flush() { if (bufferPtr == 0) return; f.Write(buffer.bytes, 0, bufferPtr * elementSize); bufferPtr = 0; } public void Write(int x) { buffer.elements[bufferPtr] = x; bufferPtr++; if (bufferPtr == bufferSize) Flush(); } public void Close() { Flush(); f.Close(); } } static void exportVBFull(BufferedBinaryWriterFloat f, Vector3[] vertices, Vector3[] normals, Vector4[] tangents, Vector2[] uv, Vector2[] uv2) { bool hasUV = uv.Length > 0; bool hasUV2 = uv2.Length > 0; for(int i=0;i indicesOpaque, List indicesTransparent, List indicesLMID, int[] indices, bool isFlipped, int offset, int indexOffsetLMID, BinaryWriter falphaid, ushort alphaID) { //var indices = m.GetTriangles(i); var indicesOut = alphaID == 0xFFFF ? indicesOpaque : indicesTransparent; int indexA, indexB, indexC; for(int j=0;j(); if (areaLight == null) { int index = temporaryAreaLightMeshList.IndexOf(obj); if (index >= 0) { areaLight = temporaryAreaLightMeshList2[index]; } } if (areaLight != null) { f.Write(areaLightCounter); areaLight.lmid = areaLightCounter; areaLightCounter--; return areaLightCounter; } else if (lmgroup != null) { f.Write(lmgroup.id); return lmgroup.id; } else { f.Write(0xFFFFFFFF); return -1; } } static Vector2[] GenerateVertexBakeUVs(int voffset, int vlength, int totalVertexCount) { int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)totalVertexCount)); atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize; var uvs = new Vector2[vlength]; float mul = 1.0f / atlasTexSize; float add = mul * 0.5f; for(int i=0; i(); return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMeshBaked(GameObject obj) { var mrSkin = obj.GetComponent(); if (mrSkin != null) { var baked = new Mesh(); mrSkin.BakeMesh(baked); return baked; } var mf = obj.GetComponent(); return (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMesh(GameObject obj) { var mrSkin = obj.GetComponent(); var mf = obj.GetComponent(); return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMeshSkinned(GameObject obj, out bool isSkin) { var mrSkin = obj.GetComponent(); Mesh mesh; if (mrSkin != null) { mesh = new Mesh(); mrSkin.BakeMesh(mesh); isSkin = mrSkin.bones.Length > 0; // blendshape-only don't need scale? } else { isSkin = false; var mf = obj.GetComponent(); if (mf == null) return null; mesh = mf.sharedMesh; } return mesh; } static GameObject TestPackAsSingleSquare(GameObject holder) { var t = holder.transform; var p = t.parent; while(p != null) { if (p.GetComponent() != null) return p.gameObject; p = p.parent; } return holder; } static bool ModelUVsOverlap(ModelImporter importer, ftGlobalStorage store) { if (importer.generateSecondaryUV) return true; var path = importer.assetPath; /*for(int i=0; i= 0 && index < store.uvOverlapAssetList.Count) { if (store.uvOverlapAssetList[index] == 0) { return false; } else { return true; } } var newAsset = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject; ftModelPostProcessor.CheckUVOverlap(newAsset, path); /*for(int i=0; i= 0) { if (store.uvOverlapAssetList[index] == 0) { return false; } else { return true; } } return true; } static bool NeedsTangents(BakeryLightmapGroup lmgroup, bool tangentSHLights) { // RNM requires tangents if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.RNM || (lmgroup!=null && lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.RNM)) return true; // SH requires tangents only if there is a SH skylight if (!tangentSHLights) return false; if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.SH || ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.MonoSH || (lmgroup!=null && (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.SH || lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.MonoSH))) return true; return false; } static long GetTime() { return System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond; } static public string progressBarText; static public float progressBarPercent = 0; static public bool userCanceled = false; //static IEnumerator progressFunc; static EditorWindow activeWindow; static void ProgressBarInit(string startText, EditorWindow window = null) { progressBarText = startText; if (ftRenderLightmap.showProgressBar) ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0, false); } static void ProgressBarShow(string text, float percent, bool onTop) { progressBarText = text; progressBarPercent = percent; if (ftRenderLightmap.showProgressBar) ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0, onTop); userCanceled = ftRenderLightmap.simpleProgressBarCancelled(); } public static void FreeTemporaryAreaLightMeshes() { if (temporaryAreaLightMeshList != null) { for(int i=0; i(); //if (mr != null) DestroyImmediate(mr); //var mf = temporaryAreaLightMeshList[i].GetComponent(); //if (mf != null) DestroyImmediate(mf); DestroyImmediate(temporaryAreaLightMeshList[i]); } } temporaryAreaLightMeshList = null; } } public static void ProgressBarEnd(bool removeTempObjects)// bool isError = true) { if (removeTempObjects) { if (terrainObjectList != null) { for(int i=0; i groupList) { id = id < 0 ? -1 : id; if (lmgroup != null && lmgroup.parentName != null && lmgroup.parentName.Length > 0 && lmgroup.parentName != "|") { for(int g=0; g(); if (storages[i] == null) { storages[i] = gg.AddComponent(); } if (modifyLightmapStorage) { /* storages[i].bakedRenderers = new List(); storages[i].bakedIDs = new List(); storages[i].bakedScaleOffset = new List(); storages[i].bakedVertexOffset = new List(); storages[i].bakedVertexColorMesh = new List(); storages[i].bakedRenderersTerrain = new List(); storages[i].bakedIDsTerrain = new List(); storages[i].bakedScaleOffsetTerrain = new List(); */ storages[i].hasEmissive = new List(); storages[i].lmGroupLODResFlags = null; storages[i].lmGroupMinLOD = null; storages[i].lmGroupLODMatrix = null; storages[i].nonBakedRenderers = new List(); } if (first) { data.firstNonNullStorage = i; first = false; } storages[i].implicitGroups = new List(); storages[i].implicitGroupedObjects = new List(); sceneToID[scene] = i; } //var go = GameObject.Find("!ftraceLightmaps"); //data.settingsStorage = go.GetComponent(); } static void InitSceneStorage2(ExportSceneData data) { var storages = data.storages; for(int i=0; i(); storages[i].bakedIDs = new List(); storages[i].bakedScaleOffset = new List(); storages[i].bakedVertexOffset = new List(); storages[i].bakedVertexColorMesh = new List(); #if USE_TERRAINS storages[i].bakedRenderersTerrain = new List(); storages[i].bakedIDsTerrain = new List(); storages[i].bakedScaleOffsetTerrain = new List(); #endif } } } // Used to pass maps to alphaBufferGen/frender via RAM (non-DX11) public static TexInput InputDataFromTex(Texture tex, TexInputType ttype = TexInputType.Color) { RenderTextureFormat rtFormat = RenderTextureFormat.ARGB32; TextureFormat texFormat = TextureFormat.RGBA32; if (ttype == TexInputType.FloatColor) { rtFormat = RenderTextureFormat.ARGBFloat; texFormat = TextureFormat.RGBAFloat; } else if (ttype == TexInputType.HalfColor) { rtFormat = RenderTextureFormat.ARGBHalf; texFormat = TextureFormat.RGBAHalf; } var rt = new RenderTexture(tex.width, tex.height, 0, rtFormat); var readableTex = new Texture2D(tex.width, tex.height, texFormat, false); Graphics.Blit(tex, rt); Graphics.SetRenderTarget(rt); readableTex.ReadPixels(new Rect(0,0,tex.width,tex.height), 0, 0, false); readableTex.Apply(); var bytes = readableTex.GetRawTextureData(); var a = new TexInput(); a.data = bytes; a.width = (ushort)tex.width; a.height = (ushort)tex.height; DestroyImmediate(readableTex); rt.Release(); return a; } public static byte[] InputBytesFromTex(Texture tex, TexInputType ttype = TexInputType.Color) { RenderTextureFormat rtFormat = RenderTextureFormat.ARGB32; TextureFormat texFormat = TextureFormat.RGBA32; byte[] header = ftDDS.ddsHeaderRGBA8; if (ttype == TexInputType.FloatColor) { rtFormat = RenderTextureFormat.ARGBFloat; texFormat = TextureFormat.RGBAFloat; header = ftDDS.ddsHeaderFloat4; } else if (ttype == TexInputType.HalfColor) { rtFormat = RenderTextureFormat.ARGBHalf; texFormat = TextureFormat.RGBAHalf; header = ftDDS.ddsHeaderHalf4; } var rt = new RenderTexture(tex.width, tex.height, 0, rtFormat); var readableTex = new Texture2D(tex.width, tex.height, texFormat, false); Graphics.Blit(tex, rt); Graphics.SetRenderTarget(rt); readableTex.ReadPixels(new Rect(0,0,tex.width,tex.height), 0, 0, false); readableTex.Apply(); var bytes = readableTex.GetRawTextureData(); var fbytes = new byte[128 + bytes.Length]; System.Buffer.BlockCopy(header, 0, fbytes, 0, 128); System.Buffer.BlockCopy(BitConverter.GetBytes(tex.height), 0, fbytes, 12, 4); System.Buffer.BlockCopy(BitConverter.GetBytes(tex.width), 0, fbytes, 16, 4); System.Buffer.BlockCopy(bytes, 0, fbytes, 128, bytes.Length); DestroyImmediate(readableTex); rt.Release(); return fbytes; } public static TexInput InputDataFromCubemap(Texture tex, Matrix4x4 mtx, bool isLinear, bool isDoubleLDR, TexInputType ttype = TexInputType.Color) { RenderTextureFormat rtFormat = RenderTextureFormat.ARGB32; TextureFormat texFormat = TextureFormat.RGBA32; if (ttype == TexInputType.FloatColor) { rtFormat = RenderTextureFormat.ARGBFloat; texFormat = TextureFormat.RGBAFloat; } else if (ttype == TexInputType.HalfColor) { rtFormat = RenderTextureFormat.ARGBHalf; texFormat = TextureFormat.RGBAHalf; } int outWidth = System.Math.Min(tex.width, 512); int outHeight = System.Math.Min(tex.height, 512); var rt = new RenderTexture(outWidth, outHeight * 6, 0, rtFormat); var readableTex = new Texture2D(outWidth, outHeight * 6, texFormat, false); if (matCubemapToStripExport == null) matCubemapToStripExport = new Material(Shader.Find("Hidden/ftCubemap2StripExport")); matCubemapToStripExport.SetMatrix("transform", mtx); matCubemapToStripExport.SetVector("isLinear_isDoubleLDR", new Vector4(isLinear ? 1 : 0, isDoubleLDR ? 1 : 0, 0, 0)); Graphics.Blit(tex, rt, matCubemapToStripExport); Graphics.SetRenderTarget(rt); readableTex.ReadPixels(new Rect(0,0,outWidth, outHeight * 6), 0, 0, false); readableTex.Apply(); var bytes = readableTex.GetRawTextureData(); var a = new TexInput(); a.data = bytes; a.width = (ushort)outWidth; a.height = (ushort)(outHeight * 6); DestroyImmediate(readableTex); rt.Release(); return a; } static IEnumerator CreateLightProbeLMGroup(ExportSceneData data) { var storages = data.storages; var sceneToID = data.sceneToID; var lmBounds = data.lmBounds; var groupList = data.groupList; var probes = LightmapSettings.lightProbes; if (probes == null) { DebugLogError("No probes in LightingDataAsset"); yield break; } var positions = probes.positions; int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)probes.count)); atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize; var uvpos = new float[atlasTexSize * atlasTexSize * 4]; var uvnormal = new byte[atlasTexSize * atlasTexSize * 4]; for(int i=0; i(); lightProbeLMGroup.name = "probes"; lightProbeLMGroup.probes = true; lightProbeLMGroup.isImplicit = true; lightProbeLMGroup.resolution = 256; lightProbeLMGroup.bitmask = 1; lightProbeLMGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex; lightProbeLMGroup.id = data.lmid; lightProbeLMGroup.totalVertexCount = probes.count; lightProbeLMGroup.vertexCounter = 0; lightProbeLMGroup.renderDirMode = BakeryLightmapGroup.RenderDirMode.ProbeSH; lightProbeLMGroup.renderMode = (ftRenderLightmap.instance.userRenderMode == ftRenderLightmap.RenderMode.Subtractive && ftRenderLightmap.useUnityForOcclsusionProbes) ? BakeryLightmapGroup.RenderMode.Indirect : BakeryLightmapGroup.RenderMode.Auto; groupList.Add(lightProbeLMGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(10000,10000,10000))); data.lmid++; storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroups.Add(lightProbeLMGroup); storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroupedObjects.Add(null); } static IEnumerator CreateVolumeLMGroup(ExportSceneData data) { ftRenderLightmap.hasAnyVolumes = false; var vols = ftRenderLightmap.FindBakeableVolumes(); if (vols.Length == 0) yield break; ftRenderLightmap.hasAnyVolumes = true; var storages = data.storages; var sceneToID = data.sceneToID; var lmBounds = data.lmBounds; var groupList = data.groupList; int numTotalProbes = 0; for(int v=0; v(); volumeLMGroup.name = "volumes"; volumeLMGroup.probes = true; volumeLMGroup.fixPos3D = true; volumeLMGroup.voxelSize = halfVoxelSize * 2; // incorrect... should be different for every probe volumeLMGroup.isImplicit = true; volumeLMGroup.resolution = 256; volumeLMGroup.bitmask = 1; volumeLMGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex; volumeLMGroup.id = data.lmid; volumeLMGroup.totalVertexCount = numTotalProbes; volumeLMGroup.vertexCounter = 0; volumeLMGroup.renderMode = (BakeryLightmapGroup.RenderMode)pstorage.volumeRenderMode; volumeLMGroup.renderDirMode = BakeryLightmapGroup.RenderDirMode.ProbeSH; groupList.Add(volumeLMGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(10000,10000,10000))); data.lmid++; storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroups.Add(volumeLMGroup); storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroupedObjects.Add(null); } static void CollectExplicitLMGroups(ExportSceneData data) { var groupList = data.groupList; var lmBounds = data.lmBounds; // Find explicit LMGroups // (Also init lmBounds and LMID) var groupSelectors = new List(FindObjectsOfType(typeof(BakeryLightmapGroupSelector)) as BakeryLightmapGroupSelector[]); for(int i=0; i(); if (terr == null) return; if (!terr.enabled) return; if (!obj.activeInHierarchy) return; if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) return; // skip temp objects if (obj.tag == "EditorOnly") return; // skip temp objects if ((GameObjectUtility.GetStaticEditorFlags(obj) & StaticEditorFlags.LightmapStatic) == 0) return; // skip dynamic var so = new SerializedObject(terr); var scaleInLmTerr = so.FindProperty("m_ScaleInLightmap").floatValue; var terrParent = new GameObject(); SceneManager.MoveGameObjectToScene(terrParent, obj.scene); terrParent.transform.parent = obj.transform;//.parent; var expGroup = obj.GetComponent(); if (expGroup != null) { var expGroup2 = terrParent.AddComponent(); expGroup2.lmgroupAsset = expGroup.lmgroupAsset; expGroup2.instanceResolutionOverride = expGroup.instanceResolutionOverride; expGroup2.instanceResolution = expGroup.instanceResolution; } terrParent.name = "__ExportTerrainParent"; terrainObjectList.Add(terrParent); terrainObjectToActual.Add(terr); var tdata = terr.terrainData; int res = tdata.heightmapResolution; var heightmap = tdata.GetHeights(0, 0, res, res); var uvscale = new Vector2(1,1) / (res-1); var uvoffset = new Vector2(0,0); var gposOffset = obj.transform.position; float scaleX = tdata.size.x / (res-1); float scaleY = tdata.size.y; float scaleZ = tdata.size.z / (res-1); int patchRes = res; #if UNITY_2017_3_OR_NEWER // supports 32-bit indices #else while(patchRes > 254) patchRes = 254;//patchRes /= 2; #endif int numVerts = patchRes * patchRes; int numPatches = (int)Mathf.Ceil(res / (float)patchRes); // Gen terrain texture var oldMat = terr.materialTemplate; var oldMatType = terr.materialType; var oldPos = obj.transform.position; #if UNITY_2018_3_OR_NEWER var oldInstanced = terr.drawInstanced; #endif var unlitTerrainMat = new Material(Shader.Find("Hidden/ftUnlitTerrain")); //unlitTerrainMat = AssetDatabase.LoadAssetAtPath("Assets/Bakery/ftUnlitTerrain.mat", typeof(Material)) as Material; terr.materialTemplate = unlitTerrainMat; terr.materialType = Terrain.MaterialType.Custom; #if UNITY_2018_3_OR_NEWER terr.drawInstanced = false; #endif int baseMapResolution = tdata.baseMapResolution; #if UNITY_2019_3_OR_NEWER int holesResolution = tdata.holesResolution; if (holesResolution > baseMapResolution) baseMapResolution = holesResolution; #endif obj.transform.position = new Vector3(-10000, -10000, -10000); // let's hope it's not the worst idea var tempCamGO = new GameObject(); tempCamGO.transform.parent = obj.transform; tempCamGO.transform.localPosition = new Vector3(tdata.size.x * 0.5f, scaleY + 1, tdata.size.z * 0.5f); tempCamGO.transform.eulerAngles = new Vector3(90,0,0); var tempCam = tempCamGO.AddComponent(); tempCam.orthographic = true; tempCam.orthographicSize = Mathf.Max(tdata.size.x, tdata.size.z) * 0.5f; tempCam.aspect = Mathf.Max(tdata.size.x, tdata.size.z) / Mathf.Min(tdata.size.x, tdata.size.z); tempCam.enabled = false; tempCam.clearFlags = CameraClearFlags.SolidColor; tempCam.backgroundColor = new Color(0,0,0,0); tempCam.targetTexture = new RenderTexture(baseMapResolution, baseMapResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); var tex = new Texture2D(baseMapResolution, baseMapResolution, TextureFormat.ARGB32, true, false); RenderTexture.active = tempCam.targetTexture; tempCam.Render(); terr.materialTemplate = oldMat; terr.materialType = oldMatType; #if UNITY_2018_3_OR_NEWER terr.drawInstanced = oldInstanced; #endif obj.transform.position = oldPos; RenderTexture.active = tempCam.targetTexture; tex.ReadPixels(new Rect(0,0,baseMapResolution, baseMapResolution), 0, 0, true); tex.Apply(); unlitTerrainMat.mainTexture = tex; Graphics.SetRenderTarget(null); DestroyImmediate(tempCamGO); bool isDX11 = SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11; if (exportTerrainAsHeightmap) { var hmap = new BinaryWriter(File.Open(scenePath + "/height" + terrainObjectToHeightMap.Count + ".dds", FileMode.Create)); if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("height" + terrainObjectToHeightMap.Count + ".dds"); hmap.Write(ftDDS.ddsHeaderR32F); var bytes = new byte[res * res * 4]; // Normalize heights float maxHeight = 0; float height; for(int y=0; y maxHeight) maxHeight = height; } } maxHeight = Mathf.Max(maxHeight, 0.0001f); float invMaxHeight = 1.0f / maxHeight; for(int y=0; y()); //terrainObjectToNormalMips.Add(new List()); if (mipRes > 0) { floats = new float[mipRes * mipRes]; //normals = new Vector3[mipRes * mipRes]; for(int y=0; y h10 ? h00 : h10; height = height > h01 ? height : h01; height = height > h11 ? height : h11; floats[y*mipRes+x] = height;*/ float maxVal = 0; for(int yy=0; yy<3; yy++) { for(int xx=0; xx<3; xx++) { float val = heightmap[y*2+yy, x*2+xx]; if (val > maxVal) maxVal = val; } } floats[y*mipRes+x] = maxVal; //n00 = normalsPrev[y*2 * res + x*2]; //n10 = normalsPrev[y*2 * res + x*2+1]; //n01 = normalsPrev[(y*2+1) * res + x*2]; //n11 = normalsPrev[(y*2+1) * res + x*2+1]; //normals[y*mipRes+x] = (n00 + n10 + n01 + n11); } } System.Buffer.BlockCopy(floats, 0, bytes, 0, mipRes*mipRes*4); hmap.Write(bytes, 0, mipRes*mipRes*4); float[] storedMip = new float[mipRes*mipRes]; System.Buffer.BlockCopy(floats, 0, storedMip, 0, mipRes*mipRes*4); terrainObjectToHeightMips[terrIndex].Add(storedMip); //terrainObjectToNormalMips[terrIndex].Add(normals); mipCount++; mipRes /= 2; } // Next mips are regular max() of 4 texels while(mipRes > 1) { if (floatsPrev == null) { floatsPrev = floats; floats = new float[mipRes * mipRes]; } //normalsPrev = normals; //normals = new Vector3[mipRes * mipRes]; for(int y=0; y h10 ? h00 : h10; height = height > h01 ? height : h01; height = height > h11 ? height : h11; floats[y*mipRes+x] = height; //n00 = normalsPrev[y*2 * mipRes*2 + x*2]; //n10 = normalsPrev[y*2 * mipRes*2 + x*2+1]; //n01 = normalsPrev[(y*2+1) * mipRes*2 + x*2]; //n11 = normalsPrev[(y*2+1) * mipRes*2 + x*2+1]; //normals[y*mipRes+x] = (n00 + n10 + n01 + n11); } } System.Buffer.BlockCopy(floats, 0, bytes, 0, mipRes*mipRes*4); hmap.Write(bytes, 0, mipRes*mipRes*4); float[] storedMip = new float[mipRes*mipRes]; System.Buffer.BlockCopy(floats, 0, storedMip, 0, mipRes*mipRes*4); terrainObjectToHeightMips[terrIndex].Add(storedMip); //terrainObjectToNormalMips[terrIndex].Add(normals); mipCount++; mipRes /= 2; floatsTmp = floatsPrev; floatsPrev = floats; floats = floatsTmp; } hmap.BaseStream.Seek(12, SeekOrigin.Begin); hmap.Write(res); hmap.Write(res); hmap.BaseStream.Seek(28, SeekOrigin.Begin); hmap.Write(mipCount); hmap.Close(); // Create dummy plane for packing/albedo/emissive purposes var mesh = new Mesh(); mesh.vertices = new Vector3[] { gposOffset, gposOffset + new Vector3(tdata.size.x, 0, 0), gposOffset + new Vector3(tdata.size.x, 0, tdata.size.z), gposOffset + new Vector3(0, 0, tdata.size.z) }; mesh.triangles = new int[]{0,1,2, 2,3,0}; mesh.normals = new Vector3[]{Vector3.up, Vector3.up, Vector3.up, Vector3.up}; mesh.uv = new Vector2[]{new Vector2(0,0), new Vector2(1,0), new Vector2(1,1), new Vector2(0,1)}; mesh.uv2 = mesh.uv; var terrGO = new GameObject(); terrGO.name = "__ExportTerrain"; GameObjectUtility.SetStaticEditorFlags(terrGO, StaticEditorFlags.LightmapStatic); terrGO.transform.parent = terrParent.transform; var mf = terrGO.AddComponent(); var mr = terrGO.AddComponent(); mf.sharedMesh = mesh; #if UNITY_2019_3_OR_NEWER // using standard materialTemplates in 2019.3 doesn't work mr.sharedMaterial = unlitTerrainMat; #else mr.sharedMaterial = (terr.materialTemplate == null) ? unlitTerrainMat : terr.materialTemplate; #endif terrGO.transform.position = obj.transform.position; var so2 = new SerializedObject(mr); so2.FindProperty("m_ScaleInLightmap").floatValue = scaleInLmTerr; so2.ApplyModifiedProperties(); //terrainObjectList.Add(terrGO); //terrainObjectToActual.Add(terr); } else { for (int patchX=0; patchX(); var mr = terrGO.AddComponent(); mf.sharedMesh = mesh; #if UNITY_2019_3_OR_NEWER // using standard materialTemplates in 2019.3 doesn't work mr.sharedMaterial = unlitTerrainMat; #else mr.sharedMaterial = (terr.materialTemplate == null) ? unlitTerrainMat : terr.materialTemplate; #endif var so2 = new SerializedObject(mr); so2.FindProperty("m_ScaleInLightmap").floatValue = scaleInLmTerr; so2.ApplyModifiedProperties(); mr.shadowCastingMode = terr.castShadows ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off; terrainObjectList.Add(terrGO); terrainObjectToActual.Add(terr); } } } if (exportTerrainTrees && terr.drawTreesAndFoliage) { var trees = tdata.treeInstances; for (int t = 0; t < trees.Length; t++) { Vector3 pos = Vector3.Scale(trees[t].position, tdata.size) + obj.transform.position; var treeProt = tdata.treePrototypes[trees[t].prototypeIndex]; var prefab = treeProt.prefab; var newObj = GameObject.Instantiate(prefab, pos, Quaternion.AngleAxis(trees[t].rotation, Vector3.up)) as GameObject; newObj.name = "__Export" + newObj.name; temporaryGameObjects.Add(newObj); var lodGroup = newObj.GetComponent(); if (lodGroup == null) { var renderers = newObj.GetComponentsInChildren(); for(int r=0; r(); if (areaLightMesh != null) { var areaLight = obj.GetComponent(); var mr = GetValidRenderer(obj); var mf = obj.GetComponent(); if (!forceAllAreaLightsSelfshadow) { if (!areaLightMesh.selfShadow) return true; // no selfshadow - ignore mesh export } if (areaLight != null && ftLightMeshInspector.IsArea(areaLight) && (mr == null || mf == null)) { var areaObj = new GameObject(); mf = areaObj.AddComponent(); mf.sharedMesh = BuildAreaLightMesh(areaLight); mr = areaObj.AddComponent(); var props = new MaterialPropertyBlock(); props.SetColor("_Color", areaLightMesh.color); props.SetFloat("intensity", areaLightMesh.intensity); if (areaLightMesh.texture != null) props.SetTexture("_MainTex", areaLightMesh.texture); mr.SetPropertyBlock(props); GameObjectUtility.SetStaticEditorFlags(areaObj, StaticEditorFlags.LightmapStatic); temporaryAreaLightMeshList.Add(areaObj); temporaryAreaLightMeshList2.Add(areaLightMesh); var xformSrc = obj.transform; var xformDest = areaObj.transform; xformDest.position = xformSrc.position; xformDest.rotation = xformSrc.rotation; var srcMtx = xformSrc.localToWorldMatrix; xformDest.localScale = new Vector3(srcMtx.GetColumn(0).magnitude, srcMtx.GetColumn(1).magnitude, srcMtx.GetColumn(2).magnitude); return true; // mesh created } } return false; // not Light Mesh } static void MapObjectsToSceneLODs(ExportSceneData data, UnityEngine.Object[] objects) { var objToLodLevel = data.objToLodLevel; var objToLodLevelVisible = data.objToLodLevelVisible; lodLevelsVisibleInLodLevel = new Dictionary>(); const int maxSceneLodLevels = 100; var sceneLodUsed = new int[maxSceneLodLevels]; for(int i=0; i[lodGroups.Length]; var localLodLevelsInLodGroup = new List[lodGroups.Length]; int lcounter = -1; foreach(LODGroup lodgroup in lodGroups) { lcounter++; if (!lodgroup.enabled) continue; var obj = lodgroup.gameObject; if (obj == null) continue; if (!obj.activeInHierarchy) continue; var path = AssetDatabase.GetAssetPath(obj); if (path != "") continue; // must belond to scene if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects if (obj.tag == "EditorOnly") continue; // skip temp objects var lods = lodgroup.GetLODs(); if (lods.Length == 0) continue; for(int i=0; i() != null; if (!mrEnabled) continue; //if (mf.sharedMesh == null) continue; var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; // (before FilterObjects) #if UNITY_2019_2_OR_NEWER var _r = mr as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif if (scaleInLm == 0) continue; lightmappedLOD = true; break; } if (!lightmappedLOD) continue; var lodDist = i == 0 ? 0 : (int)Mathf.Clamp((1.0f-lods[i-1].screenRelativeTransitionHeight) * (maxSceneLodLevels-1), 0, maxSceneLodLevels-1); if (sceneLodUsed[lodDist] < 0) { sceneLodUsed[lodDist] = sceneLodsUsed; sceneLodsUsed++; } int newLodLevel = sceneLodUsed[lodDist]; if (lodLevelsInLodGroup[lcounter] == null) { lodLevelsInLodGroup[lcounter] = new List(); localLodLevelsInLodGroup[lcounter] = new List(); } if (lodLevelsInLodGroup[lcounter].IndexOf(newLodLevel) < 0) { lodLevelsInLodGroup[lcounter].Add(newLodLevel); localLodLevelsInLodGroup[lcounter].Add(i); } for(int j=0; j visList; if (!objToLodLevelVisible.TryGetValue(r.gameObject, out visList)) objToLodLevelVisible[r.gameObject] = visList = new List(); visList.Add(newLodLevel); } } } } // Sort scene LOD levels int counter = 0; var unsortedLodToSortedLod = new int[maxSceneLodLevels]; for(int i=0; i= 0) { unsortedLodToSortedLod[unsorted] = counter; sceneLodUsed[i] = counter; counter++; } } var keys = new GameObject[objToLodLevel.Count]; counter = 0; foreach(var pair in objToLodLevel) { keys[counter] = pair.Key; counter++; } foreach(var key in keys) { int unsorted = objToLodLevel[key]; objToLodLevel[key] = unsortedLodToSortedLod[unsorted]; var visList = objToLodLevelVisible[key]; for(int j=0; j 1) { var lodRenderers = lods[localLevel].renderers; for(int k=0; k visList; if (!lodLevelsVisibleInLodLevel.TryGetValue(affectedLOD, out visList)) lodLevelsVisibleInLodLevel[affectedLOD] = visList = new List(); if (visList.IndexOf(objOwnLOD) < 0) visList.Add(objOwnLOD); } } /*foreach(var pair in lodLevelsVisibleInLodLevel) { string str = "LOD " + pair.Key + " sees: "; for(int i=0; i[sceneLodsUsed]; data.indicesTransparentLOD = new List[sceneLodsUsed]; for(int i=0; i(); data.indicesTransparentLOD[i] = new List(); } // Sort objects by scene-wide LOD level if (sceneLodsUsed > 0) { Array.Sort(objects, delegate(UnityEngine.Object a, UnityEngine.Object b) { if (a == null || b == null) return 0; int lodLevelA = -1; int lodLevelB = -1; if (!objToLodLevel.TryGetValue((GameObject)a, out lodLevelA)) lodLevelA = -1; if (!objToLodLevel.TryGetValue((GameObject)b, out lodLevelB)) lodLevelB = -1; return lodLevelA.CompareTo(lodLevelB); }); } } public static int IsInsideSector(Transform tform, Transform sectorTform, Bounds b, BakerySector s) { var parent = tform; while(parent != null) { if (parent == sectorTform) return 1; // belongs to this sector parent = parent.parent; } return 0; // far } static bool ClipFarSphere(FarSphereRenderData clipee, FarSphereRenderData clipper) { if (farSphereProjClipShader == null) { farSphereProjClipShader = Shader.Find("Hidden/ftFarSphereProjClip"); if (farSphereProjClipShader == null) { Debug.LogError("Can't find ftFarSphereProjClip shader"); return false; } } if (farSphereProjClipMat == null) { farSphereProjClipMat = new Material(farSphereProjClipShader); } // Project clipper depth to clipee // If projected pixel is on screen and is closer to the projector // draw empty pixel // else // discard var cmd = new CommandBuffer(); Shader.SetGlobalFloat("_InvCubeSize", 1.0f / ftAdditionalConfig.sectorFarSphereResolution); for(int i=0; i<6; i++) { Shader.SetGlobalTexture("_CurDepth", clipee.depth[i]); Shader.SetGlobalMatrix("_CurInvViewProj", Matrix4x4.Inverse(clipee.viewProj[i])); Shader.SetGlobalVector("_CurPos", clipee.pos); Shader.SetGlobalTexture("_CurNormal", clipee.normal[i]); for(int j=0; j<6; j++) { Shader.SetGlobalTexture("_ProjDepth", clipper.depth[j]); Shader.SetGlobalMatrix("_ProjViewProj", clipper.viewProj[j]); Shader.SetGlobalVector("_ProjPos", clipper.pos); //Graphics.Blit(null, clipee.albedo[i], farSphereProjClipMat); // can flip cmd.Clear(); cmd.SetRenderTarget(new RenderTargetIdentifier(clipee.albedo[i]), 0, CubemapFace.Unknown, 0); cmd.DrawProcedural(Matrix4x4.identity, farSphereProjClipMat, 0, MeshTopology.Triangles, 6, 1, null); Graphics.ExecuteCommandBuffer(cmd); } } return true; } static FarSphereRenderData GenerateFarSphereData(ExportSceneData data, Vector3 capturePoint, int objCount, bool genProjMatrix) { // Constants const int cubeRes = ftAdditionalConfig.sectorFarSphereResolution; var rot = new Vector3[6]; rot[0] = new Vector3(0, -90, 0); rot[1] = new Vector3(0, 90, 0); rot[2] = new Vector3(90, 0, 0); rot[3] = new Vector3(-90, 0, 0); rot[4] = new Vector3(0, 180, 180); rot[5] = new Vector3(0, 0, 180); var wnormal = new Vector3[6]; wnormal[0] = -Vector3.right; wnormal[1] = Vector3.right; wnormal[2] = -Vector3.up; wnormal[3] = Vector3.up; wnormal[4] = -Vector3.forward; wnormal[5] = Vector3.forward; var objsToWrite = data.objsToWrite; //var sector = ftRenderLightmap.curSector; // Load shaders if (farSphereRshader == null) { farSphereRshader = Shader.Find("Hidden/ftFarSphereRender"); if (farSphereRshader == null) { Debug.LogError("Can't find ftFarSphereRender shader"); return null; } } if (farSphereRshaderOcc == null) { farSphereRshaderOcc = Shader.Find("Hidden/ftFarSphereRenderOccluder"); if (farSphereRshaderOcc == null) { Debug.LogError("Can't find ftFarSphereRenderOccluder shader"); return null; } } if (farSphereMatOcc == null) { farSphereMatOcc = new Material(farSphereRshaderOcc); } // Create RTs FarSphereRenderData outData = new FarSphereRenderData(); outData.albedo = new RenderTexture[6]; outData.depth = new RenderTexture[6]; outData.normal = new RenderTexture[6]; outData.viewProj = new Matrix4x4[6]; outData.pos = capturePoint; for(int i=0; i<6; i++) { outData.albedo[i] = new RenderTexture(cubeRes, cubeRes, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); outData.albedo[i].wrapMode = TextureWrapMode.Clamp; outData.albedo[i].Create(); outData.depth[i] = new RenderTexture(cubeRes, cubeRes, 32, RenderTextureFormat.Depth, RenderTextureReadWrite.Linear); outData.depth[i].filterMode = FilterMode.Point; outData.depth[i].wrapMode = TextureWrapMode.Clamp; outData.depth[i].Create(); outData.normal[i] = new RenderTexture(cubeRes, cubeRes, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); outData.normal[i].filterMode = FilterMode.Point; outData.normal[i].wrapMode = TextureWrapMode.Clamp; outData.normal[i].Create(); } bool useCamera = true; List outsideRenderers = null; //#if UNITY_2019_1_OR_NEWER // if (GraphicsSettings.renderPipelineAsset != null) // { useCamera = false; outsideRenderers = data.outsideRenderers; if (farSphereMat == null) { farSphereMat = new Material(farSphereRshader); } // } //#endif GameObject tempCamGO = null; Camera tempCam = null; Transform camTform = null; Matrix4x4 uProj; if (useCamera) { // Create temp camera tempCamGO = new GameObject(); tempCamGO.name = "BakerySectorFarCamera"; camTform = tempCamGO.transform; camTform.localPosition = capturePoint;// sectorCenter; tempCam = tempCamGO.AddComponent(); tempCam.orthographic = false; tempCam.aspect = 1.0f; tempCam.fieldOfView = 90.0f; tempCam.enabled = false; tempCam.clearFlags = CameraClearFlags.Nothing; tempCam.backgroundColor = new Color(0,0,0,0); tempCam.SetReplacementShader(farSphereRshader, "RenderType"); tempCam.nearClipPlane = 0.1f; tempCam.farClipPlane = 1000.0f; tempCam.renderingPath = RenderingPath.Forward; uProj = tempCam.projectionMatrix; } else { uProj = Matrix4x4.Perspective(90, 1, 0.1f, 1000.0f); } var proj = GL.GetGPUProjectionMatrix(uProj, true); /*// Set culling matrices for the render (cull things inside the sector + near distance) if (sector.tforms.Count == 0) { Shader.SetGlobalFloat("cullMatricesCount", 0); } else { var cullMatrices = new Matrix4x4[sector.tforms.Count]; for(int i=0; i().sharedMesh; var worldMatrix = objsToWrite[o].transform.localToWorldMatrix; for(int s=0; s().sharedMesh; var worldMatrix = outsideRenderers[o].transform.localToWorldMatrix; var mats = outsideRenderers[o].sharedMaterials; for(int s=0; s s && mats[s] != null && mats[s].HasProperty("_MainTex")) { var mat = mats[s]; Texture tex = null; if (mat.HasProperty("_MainTex")) { tex = mat.mainTexture; } else if (mat.HasProperty("_BaseColorMap")) { // HDRP tex = mat.GetTexture("_BaseColorMap"); } else if (mat.HasProperty("_BaseMap")) { // URP tex = mat.GetTexture("_BaseMap"); } farSphereMat.SetTexture("_MainTex", tex); pass = 0; // opaque if (tex != null) { var matTag = mat.GetTag("RenderType", true); if (matTag == "TransparentCutout" || matTag == "Transparent" || matTag == "TreeLeaf") { pass = 2; // transparent cutout } } farSphereMat.SetPass(pass); } for(int j=0; j capturePoints) { if (fdatas.Length == 0) return true; // Constants const int cubeRes = ftAdditionalConfig.sectorFarSphereResolution; const int threadWidth = 16; const int vertWidth = cubeRes; const int dispatchWidth = vertWidth / threadWidth; const int triCount = (vertWidth-1)*(vertWidth-1)*2; int dispatchIndexGroups = (int)Mathf.Ceil(triCount / (float)(threadWidth * threadWidth)); var wnormal = new Vector3[6]; wnormal[0] = -Vector3.right; wnormal[1] = Vector3.right; wnormal[2] = -Vector3.up; wnormal[3] = Vector3.up; wnormal[4] = -Vector3.forward; wnormal[5] = Vector3.forward; var objsToWrite = data.objsToWrite; var objsToWriteNames = data.objsToWriteNames; var objsToWriteLightmapped = data.objsToWriteLightmapped; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objsToWriteVerticesUV = data.objsToWriteVerticesUV; var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2; var objsToWriteIndices = data.objsToWriteIndices; var objToScaleInLm = data.objToScaleInLm; if (data.autoVertexGroup == null) CreateAutoVertexGroup(data, data.objsToWrite.Count > 0 ? data.objsToWrite[0] : null); var storages = data.storages; var sceneToID = data.sceneToID; if (farSphereSshader == null) { farSphereSshader = Shader.Find("Hidden/ftFarSphere"); if (farSphereSshader == null) { Debug.LogError("Can't find ftFarSphere shader"); return false; } } var bakeryEditorPath = ftLightmaps.GetEditorPath(); if (farSphereCSTransform == null) { farSphereCSTransform = AssetDatabase.LoadAssetAtPath(bakeryEditorPath + "shaderSrc/ftTransformFarSphere.compute", typeof(ComputeShader)) as ComputeShader; if (farSphereCSTransform == null) { Debug.LogError("Can't find ftTransformFarSphere"); return false; } } if (farSphereCSCull == null) { farSphereCSCull = AssetDatabase.LoadAssetAtPath(bakeryEditorPath + "shaderSrc/ftCullFarSphere.compute", typeof(ComputeShader)) as ComputeShader; if (farSphereCSCull == null) { Debug.LogError("Can't find ftCullFarSphere"); return false; } } // Create temp buffers var rwBuff = new ComputeBuffer(vertWidth * vertWidth, 12); // vertices in/out var indBuff = new ComputeBuffer(triCount, 3*4); // indices in var appendBuff = new ComputeBuffer(triCount, 3*4, ComputeBufferType.Append); // indices out var countBuff = new ComputeBuffer(1, 4, ComputeBufferType.Raw); // out index count var countArray = new int[1]; var uvBuff = new ComputeBuffer(vertWidth * vertWidth, 8); // vertex UVs // Create shared input index buffer var tris = new int[(vertWidth-1) * (vertWidth-1) * 2 * 3]; int indCount = 0; for(int y=0; y(); var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap"); scaleInLm.floatValue = 0; so.ApplyModifiedProperties(); mr.sharedMaterial = mat; GameObjectUtility.SetStaticEditorFlags(faceMesh, StaticEditorFlags.LightmapStatic); var mf = faceMesh.AddComponent(); var mesh = new Mesh(); mesh.vertices = verts; mesh.uv = uv; mesh.triangles = arr; totalTris += arr.Length; mesh.RecalculateNormals(); mf.sharedMesh = mesh; fdata.meshes[i] = mesh; objsToWrite.Add(faceMesh); objsToWriteNames.Add(""); objsToWriteLightmapped.Add(false); objsToWriteGroup.Add(data.autoVertexGroup); objsToWriteHolder.Add(faceMesh); objsToWriteVerticesUV.Add(uv); objsToWriteVerticesUV2.Add(uv); var inds = new int[1][]; inds[0] = arr; objsToWriteIndices.Add(inds); objToScaleInLm[faceMesh] = 0; if (sceneST == null) sceneST = storages[sceneToID[faceMesh.scene]]; sceneST.implicitGroupedObjects.Add(faceMesh); sceneST.implicitGroups.Add(data.autoVertexGroup); tempStorage.implicitGroupMap[faceMesh] = data.autoVertexGroup; if (modifyLightmapStorage) sceneST.nonBakedRenderers.Add(mr); fdata.albedo[i].Release(); fdata.depth[i].Release(); fdata.normal[i].Release(); } } Debug.Log("Total out-of-sector tris: " + totalTris); tempTex.Release(); rwBuff.Release(); indBuff.Release(); appendBuff.Release(); countBuff.Release(); uvBuff.Release(); return true; } static bool LoadSectorCapture(ExportSceneData data, BakerySectorCapture capture, Transform sectorTform) { if (capture.meshes == null) { Debug.LogError("No meshes in capture"); return false; } if (capture.positions == null) { Debug.LogError("No positions in capture"); return false; } if (capture.textures == null) { Debug.LogError("No textures in capture"); return false; } if (farSphereSshader == null) { farSphereSshader = Shader.Find("Hidden/ftFarSphere"); if (farSphereSshader == null) { Debug.LogError("Can't find ftFarSphere shader"); return false; } } var objsToWrite = data.objsToWrite; var objsToWriteNames = data.objsToWriteNames; var objsToWriteLightmapped = data.objsToWriteLightmapped; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objsToWriteVerticesUV = data.objsToWriteVerticesUV; var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2; var objsToWriteIndices = data.objsToWriteIndices; var objToScaleInLm = data.objToScaleInLm; if (data.autoVertexGroup == null) CreateAutoVertexGroup(data, data.objsToWrite.Count > 0 ? data.objsToWrite[0] : null); var storages = data.storages; var sceneToID = data.sceneToID; var parent = new GameObject(); parent.name = "fp"; var parentTform = parent.transform; parentTform.position = capture.sectorPos; parentTform.rotation = capture.sectorRot; temporaryGameObjects.Add(parent); ftLightmapsStorage sceneST = null; for(int i=0; i(); var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap"); scaleInLm.floatValue = 0; so.ApplyModifiedProperties(); mr.sharedMaterial = mat; GameObjectUtility.SetStaticEditorFlags(faceMesh, StaticEditorFlags.LightmapStatic); var mf = faceMesh.AddComponent(); mf.sharedMesh = capture.meshes[i]; objsToWrite.Add(faceMesh); objsToWriteNames.Add(""); objsToWriteLightmapped.Add(false); objsToWriteGroup.Add(data.autoVertexGroup); objsToWriteHolder.Add(faceMesh); var uv = mf.sharedMesh.uv; objsToWriteVerticesUV.Add(uv); objsToWriteVerticesUV2.Add(uv); var inds = new int[1][]; inds[0] = mf.sharedMesh.triangles; objsToWriteIndices.Add(inds); objToScaleInLm[faceMesh] = 0; if (sceneST == null) sceneST = storages[sceneToID[faceMesh.scene]]; sceneST.implicitGroupedObjects.Add(faceMesh); sceneST.implicitGroups.Add(data.autoVertexGroup); tempStorage.implicitGroupMap[faceMesh] = data.autoVertexGroup; if (modifyLightmapStorage) sceneST.nonBakedRenderers.Add(mr); } parentTform.rotation = sectorTform.rotation; parentTform.position = sectorTform.position; return true; } static void CreateAutoVertexGroup(ExportSceneData data, GameObject anyObj) { var groupList = data.groupList; var lmBounds = data.lmBounds; data.autoVertexGroup = ScriptableObject.CreateInstance(); data.autoVertexGroup.name = (anyObj == null ? "scene" : FilterNonASCII(anyObj.scene.name)) + "_VLM"; data.autoVertexGroup.isImplicit = true; data.autoVertexGroup.resolution = 256; data.autoVertexGroup.bitmask = 1; data.autoVertexGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex; data.autoVertexGroup.id = data.lmid; groupList.Add(data.autoVertexGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; } static int GetLightmapTag(GameObject obj, ExportSceneData data) { var objToBakeTag = data.objToBakeTag; if (objToBakeTag == null) objToBakeTag = data.objToBakeTag = new Dictionary(); int tag; if (objToBakeTag.TryGetValue(obj, out tag)) return tag; tag = -1; var mr = obj.GetComponent(); if (mr != null) { var so = new SerializedObject(mr); var param = so.FindProperty("m_LightmapParameters").objectReferenceValue; if (param != null) { var param2 = param as LightmapParameters; if (param2 != null) { tag = param2.bakedLightmapTag; } } } objToBakeTag[obj] = tag; return tag; } static bool FilterObjects(ExportSceneData data, UnityEngine.Object[] objects) { var objToLodLevel = data.objToLodLevel; var storages = data.storages; var sceneToID = data.sceneToID; var objsToWrite = data.objsToWrite; var objsToWriteNames = data.objsToWriteNames; var objsToWriteLightmapped = data.objsToWriteLightmapped; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objsToWriteVerticesUV = data.objsToWriteVerticesUV; var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2; var objsToWriteIndices = data.objsToWriteIndices; var objToScaleInLm = data.objToScaleInLm; List outsideRenderers = null; outsideRenderers = data.outsideRenderers; /*#if UNITY_2019_1_OR_NEWER if (GraphicsSettings.renderPipelineAsset != null) { outsideRenderers = data.outsideRenderers; } #endif*/ Transform sectorTform = null; if (ftRenderLightmap.fullSectorRender) { sectorTform = ftRenderLightmap.curSector.transform; } var prop = new MaterialPropertyBlock(); foreach(GameObject obj in objects) { if (obj == null) continue; if (!obj.activeInHierarchy) continue; var path = AssetDatabase.GetAssetPath(obj); if (path != "") continue; // must belond to scene if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects if (obj.tag == "EditorOnly") continue; // skip temp objects var areaLight = obj.GetComponent(); if (areaLight == null) { int areaIndex = temporaryAreaLightMeshList.IndexOf(obj); if (areaIndex >= 0) areaLight = temporaryAreaLightMeshList2[areaIndex]; } if (areaLight != null) { if (!forceAllAreaLightsSelfshadow) { if (!areaLight.selfShadow) continue; } } var mr = GetValidRenderer(obj); if (mr == null) { // must be MR or SMR continue; } var sharedMesh = GetSharedMesh(mr); if (sharedMesh == null) continue; // must have visible mesh // Remove previous lightmap #if UNITY_2018_1_OR_NEWER if (mr.HasPropertyBlock()) { // Reset shader props mr.GetPropertyBlock(prop); prop.SetFloat("bakeryLightmapMode", 0); mr.SetPropertyBlock(prop); } #else mr.GetPropertyBlock(prop); if (!prop.isEmpty) { prop.SetFloat("bakeryLightmapMode", 0); mr.SetPropertyBlock(prop); } #endif if (((GameObjectUtility.GetStaticEditorFlags(obj) & StaticEditorFlags.LightmapStatic) == 0) && areaLight==null) { mr.lightmapIndex = 0xFFFF; continue; // skip dynamic } var mrEnabled = mr.enabled || obj.GetComponent() != null; if (!mrEnabled && areaLight == null) continue; var so = new SerializedObject(mr);//obj.GetComponent()); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; #if UNITY_2019_2_OR_NEWER var _r = mr as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif if (ftRenderLightmap.fullSectorRender) { int status = IsInsideSector(obj.transform, sectorTform, mr.bounds, ftRenderLightmap.curSector); if (status == 0) { if (outsideRenderers != null) outsideRenderers.Add(mr); continue; } if (status == 2) scaleInLm = 0; } objToScaleInLm[obj] = scaleInLm; BakeryLightmapGroup group = null; if (scaleInLm > 0) { group = GetLMGroupFromObjectExplicit(obj, data); if (group != null) { // Set LOD level for explicit group int lodLevel; if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1; var packer = group.atlasPacker == BakeryLightmapGroup.AtlasPacker.Auto ? atlasPacker : (ftGlobalStorage.AtlasPacker)group.atlasPacker; if (!postPacking || packer != ftGlobalStorage.AtlasPacker.xatlas) { if (group.sceneLodLevel == -1) { group.sceneLodLevel = lodLevel; } else { if (lodLevel > 0) { if (!ExportSceneValidationMessage("Multiple LOD levels in " + group.name + ", this is only supported when xatlas is set as the atlas packer and post-packing is enabled.")) return false; } } } if (exportTerrainAsHeightmap && !group.isImplicit && obj.name == "__ExportTerrain") { if (!ExportSceneValidationMessage("Terrain Optimization is enabled and terrains are inside a lightmap group. This is not currently supported. Try disabling Terrain Optimization (" + group.name + ", " + group.isImplicit + ").")) return false; } // Set for explicit group if (splitByScene) group.sceneName = obj.scene.name; if (splitByTag) group.tag = GetLightmapTag(obj, data); // New explicit Pack Atlas holder selection if (!group.isImplicit && group.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { lmgroupHolder = obj; // by default pack each object lmgroupHolder = TestPackAsSingleSquare(lmgroupHolder); var prefabParent = PrefabUtility.GetPrefabParent(obj) as GameObject; if (prefabParent != null) { var ptype = PrefabUtility.GetPrefabType(prefabParent); if (ptype == PrefabType.ModelPrefab) { // but if object is a part of prefab/model var sharedMesh2 = GetSharedMesh(obj); var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sharedMesh2)) as ModelImporter; if (importer != null && !ModelUVsOverlap(importer, gstorage)) { // or actually just non-overlapping model, // then pack it as a whole // find topmost asset parent var t = prefabParent.transform; while(t.parent != null) t = t.parent; var assetTopMost = t.gameObject; // find topmost scene instance parent var g = obj; while(PrefabUtility.GetPrefabParent(g) as GameObject != assetTopMost && g.transform.parent != null) { g = g.transform.parent.gameObject; } lmgroupHolder = g; } } } } } } else { if (data.autoVertexGroup == null) CreateAutoVertexGroup(data, obj); group = data.autoVertexGroup; tempStorage.implicitGroupMap[obj] = data.autoVertexGroup; if (modifyLightmapStorage) { var st = storages[sceneToID[obj.scene]]; st.implicitGroupedObjects.Add(obj); st.implicitGroups.Add(data.autoVertexGroup); st.nonBakedRenderers.Add(mr); } } bool vertexBake = (group != null && group.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex); // must have UVs or be arealight or vertexbaked var uv = sharedMesh.uv; var uv2 = sharedMesh.uv2; if (uv.Length == 0 && uv2.Length == 0 && areaLight==null && !vertexBake) continue; var usedUVs = uv2.Length == 0 ? uv : uv2; //bool validUVs = true; for(int v=0; v 1.0001f || usedUVs[v].y < -0.0001f || usedUVs[v].y > 1.0001f) { DebugLogWarning("Mesh " + sharedMesh.name + " on object " + obj.name + " possibly has incorrect UVs (UV2: " + (uv2.Length == 0 ? "no" : "yes")+", U: " + usedUVs[v].x + ", V: " + usedUVs[v].y + ")"); //validUVs = false; break; } } //if (!validUVs) continue; if (vertexBake) { group.totalVertexCount = 0; group.vertexCounter = 0; } objsToWrite.Add(obj); objsToWriteNames.Add(obj.name); objsToWriteLightmapped.Add((scaleInLm > 0 && areaLight == null) ? true : false); objsToWriteGroup.Add(group); objsToWriteHolder.Add(lmgroupHolder); objsToWriteVerticesUV.Add(uv); objsToWriteVerticesUV2.Add(uv2); var inds = new int[sharedMesh.subMeshCount][]; for(int n=0; n 0) { newGroup = autoAtlasGroups[0]; } else { newGroup = ScriptableObject.CreateInstance(); // Make sure first lightmap is always LM0, not LM1, if probes are used int lmNum = storages[sceneToID[holder.scene]].implicitGroups.Count; if (ftRenderLightmap.lightProbeMode == ftRenderLightmap.LightProbeMode.L1 && ftRenderLightmap.hasAnyProbes && renderTextures && !atlasOnly) lmNum--; newGroup.name = FilterNonASCII(holder.scene.name) + "_LM" + autoAtlasGroups.Count;//lmNum; newGroup.isImplicit = true; newGroup.resolution = 256; newGroup.bitmask = 1; newGroup.area = 0; newGroup.mode = autoAtlas ? BakeryLightmapGroup.ftLMGroupMode.PackAtlas : BakeryLightmapGroup.ftLMGroupMode.OriginalUV; newGroup.id = data.lmid; groupList.Add(newGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; if (autoAtlas) { autoAtlasGroups.Add(newGroup); var rootNode = new AtlasNode(); rootNode.rc = new Rect(0, 0, 1, 1); autoAtlasGroupRootNodes.Add(rootNode); } } storages[sceneToID[holder.scene]].implicitGroupedObjects.Add(holder); storages[sceneToID[holder.scene]].implicitGroups.Add(newGroup); //Debug.LogError("Add "+(storages[sceneToID[holder.scene]].implicitGroups.Count-1)+" "+newGroup.name); tempStorage.implicitGroupMap[holder] = newGroup; // Set for implicit group if (splitByScene) newGroup.sceneName = holder.scene.name; if (splitByTag) newGroup.tag = GetLightmapTag(holder, data); } if (!tempStorage.implicitGroupMap.ContainsKey(holder)) { // happens with modifyLightmapStorage == false var gholders = storages[sceneToID[holder.scene]].implicitGroupedObjects; var grs = storages[sceneToID[holder.scene]].implicitGroups; for(int g=0; g= 0) { startIndex = onlyID; endIndex = onlyID; } // Transform vertices to world space for(int i=startIndex; i<=endIndex; i++) { var obj = objsToWrite[i]; var lmgroup = objsToWriteGroup[i]; bool isSkin; var m = GetSharedMeshSkinned(obj, out isSkin); var vertices = m.vertices; var tform = obj.transform; while(objsToWriteVerticesPosW.Count <= i) { objsToWriteVerticesPosW.Add(null); objsToWriteVerticesNormalW.Add(null); } objsToWriteVerticesPosW[i] = new Vector3[vertices.Length]; if (isSkin) { var lossyScale = tform.lossyScale; var inverseWorldScale = new Vector3(1.0f/lossyScale.x, 1.0f/lossyScale.y, 1.0f/lossyScale.z); for(int t=0; t(); var objsWithExplicitGroupPadding = new List(); var objsWithExplicitGroupPaddingWidth = new List(); for(int i=0; i()); //var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; var scaleInLm = data.objToScaleInLm[objsToWrite[i]]; if (scaleInLm == 0) continue; // don't reunwrap objects with scale in lightmap == 0 area *= scaleInLm; float width = Mathf.Sqrt(area); float twidth = 1; if (lmgroup.isImplicit) { twidth = width * texelsPerUnit; } else { float currentArea; if (!explicitGroupTotalArea.TryGetValue(lmgroup.id, out currentArea)) currentArea = 0; explicitGroupTotalArea[lmgroup.id] = currentArea + area; var holder = objsToWriteHolder[i]; BakeryLightmapGroupSelector comp = null; if (holder != null) comp = holder.GetComponent(); if (comp != null && comp.instanceResolutionOverride) { // Explicit holder size twidth = width * comp.instanceResolution; } else { // Texel size in atlas - can't calculate at this point objsWithExplicitGroupPadding.Add(i); objsWithExplicitGroupPaddingWidth.Add(width); continue; } } float requiredPadding = 4 * (1024.0f / (twidth * smallestMapScale)); int requiredPaddingClamped = (int)Mathf.Clamp(requiredPadding, 1, 256); int existingPadding = 0; meshToPaddingMap.TryGetValue(m, out existingPadding); meshToPaddingMap[m] = Math.Max(requiredPaddingClamped, existingPadding); // select largest padding among instances List arr; if (!meshToObjIDs.TryGetValue(m, out arr)) { meshToObjIDs[m] = arr = new List(); } if (!arr.Contains(i)) arr.Add(i); } for(int j=0; j arr; if (!meshToObjIDs.TryGetValue(m, out arr)) { meshToObjIDs[m] = arr = new List(); } if (!arr.Contains(i)) arr.Add(i); } } static void ResetPaddingStorageData(ExportSceneData data) { var storages = data.storages; // Reset scene padding backup for(int s=0; s(); str.modifiedAssets = new List(); } } static void StoreNewUVPadding(ExportSceneData data, AdjustUVPaddingData adata) { var meshToPaddingMap = adata.meshToPaddingMap; var meshToObjIDs = adata.meshToObjIDs; var dirtyAssetList = adata.dirtyAssetList; var dirtyObjList = adata.dirtyObjList; var storages = data.storages; foreach(var pair in meshToPaddingMap) { var m = pair.Key; var requiredPaddingClamped = pair.Value; var assetPath = AssetDatabase.GetAssetPath(m); var ids = meshToObjIDs[m]; //for(int s=0; s= 0) ind = objStorage.modifiedAssets[mstoreIndex].meshName.IndexOf(mname); if (ind < 0) { if (mstoreIndex < 0) { // add new record to globalstorage objStorage.modifiedAssetPathList.Add(assetPath); var newStruct = new ftGlobalStorage.AdjustedMesh(); newStruct.meshName = new List(); newStruct.padding = new List(); objStorage.modifiedAssets.Add(newStruct); mstoreIndex = objStorage.modifiedAssets.Count - 1; } var nameList = objStorage.modifiedAssets[mstoreIndex].meshName; var paddingList = objStorage.modifiedAssets[mstoreIndex].padding; var unwrapperList = objStorage.modifiedAssets[mstoreIndex].unwrapper; if (unwrapperList == null) { var s = objStorage.modifiedAssets[mstoreIndex]; unwrapperList = s.unwrapper = new List(); objStorage.modifiedAssets[mstoreIndex] = s; } while(nameList.Count > unwrapperList.Count) unwrapperList.Add(0); // fix legacy nameList.Add(mname); paddingList.Add(requiredPaddingClamped); unwrapperList.Add((int)ftRenderLightmap.unwrapper); if (!dirtyAssetList.Contains(assetPath)) dirtyAssetList.Add(assetPath); for(int xx=0; xx(); objStorage.modifiedAssets[mstoreIndex] = s; } while(nameList.Count > unwrapperList.Count) unwrapperList.Add(0); // fix legacy // modify existing record var oldValue = paddingList[ind]; var oldUnwrapperValue = (ftGlobalStorage.Unwrapper)unwrapperList[ind]; bool shouldModify = oldValue != requiredPaddingClamped; if (uvPaddingMax) { shouldModify = oldValue < requiredPaddingClamped; } if (oldUnwrapperValue != ftRenderLightmap.unwrapper) shouldModify = true; if (shouldModify) { if (!dirtyAssetList.Contains(assetPath)) dirtyAssetList.Add(assetPath); for(int xx=0; xx 0) { sceneNeedsToBeRebuilt = true; return false; } } return true; } static bool ValidateScaleOffsetImmutability(ExportSceneData data) { if (validateLightmapStorageImmutability) { var holderRect = data.holderRect; var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var storages = data.storages; var sceneToID = data.sceneToID; var emptyVec4 = new Vector4(1,1,0,0); Rect rc = new Rect(); for(int i=0; i(); var vpos = objsToWriteVerticesPosW[i]; var vuv = objsToWriteVerticesUV2[i];//m.uv2; var inds = objsToWriteIndices[i]; //if (vuv.Length == 0 || obj.GetComponent()!=null) vuv = objsToWriteVerticesUV[i];//m.uv; // area lights or objects without UV2 export UV1 instead if (vuv.Length == 0 || obj.GetComponent()!=null || temporaryAreaLightMeshList.Contains(obj)) vuv = objsToWriteVerticesUV[i];//m.uv; // area lights or objects without UV2 export UV1 instead Vector2 uv1 = Vector2.zero; Vector2 uv2 = Vector2.zero; Vector2 uv3 = Vector2.zero; int lodLevel; if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1; for(int k=0;k 0) { uv1 = vuv[indexA]; uv2 = vuv[indexB]; uv3 = vuv[indexC]; } /*var uv31 = new Vector3(uv1.x, uv1.y, 0); var uv32 = new Vector3(uv2.x, uv2.y, 0); var uv33 = new Vector3(uv3.x, uv3.y, 0); areaUV += Vector3.Cross(uv32 - uv31, uv33 - uv31).magnitude;*/ if (uv1.x < uvBounds.x) uvBounds.x = uv1.x; if (uv1.y < uvBounds.y) uvBounds.y = uv1.y; if (uv1.x > uvBounds.z) uvBounds.z = uv1.x; if (uv1.y > uvBounds.w) uvBounds.w = uv1.y; if (uv2.x < uvBounds.x) uvBounds.x = uv2.x; if (uv2.y < uvBounds.y) uvBounds.y = uv2.y; if (uv2.x > uvBounds.z) uvBounds.z = uv2.x; if (uv2.y > uvBounds.w) uvBounds.w = uv2.y; if (uv3.x < uvBounds.x) uvBounds.x = uv3.x; if (uv3.y < uvBounds.y) uvBounds.y = uv3.y; if (uv3.x > uvBounds.z) uvBounds.z = uv3.x; if (uv3.y > uvBounds.w) uvBounds.w = uv3.y; } // uv layouts always have empty spaces //area /= areaUV; var scaleInLm = data.objToScaleInLm[obj]; if (!pstorage.alternativeScaleInLightmap) area *= scaleInLm; if ((lmgroup.isImplicit || lmgroup.autoResolution) && lodLevel == -1) { lmgroup.area += area; // accumulate LMGroup area // only use base scene values, no LODs, to properly initialize autoatlas size } if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { // Accumulate per-holder area and UV bounds float existingArea; Vector4 existingBounds; holderObjUVBounds.TryGetValue(holderObj, out existingBounds); if (!holderObjArea.TryGetValue(holderObj, out existingArea)) { existingArea = 0; existingBounds = uvBounds; List holderList; if (!groupToHolderObjects.TryGetValue(lmgroup, out holderList)) { groupToHolderObjects[lmgroup] = holderList = new List(); } holderList.Add(holderObj); } holderObjArea[holderObj] = existingArea + area; existingBounds.x = existingBounds.x < uvBounds.x ? existingBounds.x : uvBounds.x; existingBounds.y = existingBounds.y < uvBounds.y ? existingBounds.y : uvBounds.y; existingBounds.z = existingBounds.z > uvBounds.z ? existingBounds.z : uvBounds.z; existingBounds.w = existingBounds.w > uvBounds.w ? existingBounds.w : uvBounds.w; holderObjUVBounds[holderObj] = existingBounds; } } } } static int ResolutionFromArea(float area) { int resolution = (int)(Mathf.Sqrt(area) * texelsPerUnit); if (mustBePOT) { if (atlasCountPriority) { resolution = Mathf.NextPowerOfTwo(resolution); } else { resolution = Mathf.ClosestPowerOfTwo(resolution); } } resolution = Math.Max(resolution, minAutoResolution); resolution = Math.Min(resolution, maxAutoResolution); return resolution; } static void CalculateAutoAtlasInitResolution(ExportSceneData data) { var groupList = data.groupList; // Calculate implicit lightmap resolution for(int i=0; i holderObjs, ExportSceneData data) { var holderObjArea = data.holderObjArea; var holderObjUVBounds = data.holderObjUVBounds; // Divide holders area to get from world space to -> UV space float areaMult = 1.0f; if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { // ...by maximum lightmap area given texel size (autoAtlas) //areaMult = 1.0f / lightmapMaxArea; // don't modify } else { // ... by maximum holder area (normalize) float lmgroupArea = 0; for(int i=0; i holderObjs, ExportSceneData data, PackData pdata) { var objToLodLevel = data.objToLodLevel; var holderObjArea = data.holderObjArea; var remainingAreaPerLodLevel = pdata.remainingAreaPerLodLevel; for(int i=0; i(); if (comp != null && comp.instanceResolutionOverride) areaA = comp.instanceResolution * 10000; comp = b.GetComponent(); if (comp != null && comp.instanceResolutionOverride) areaB = comp.instanceResolution * 10000; return areaB.CompareTo(areaA); } static void ApplyAreaToUVBounds(float area, Vector4 uvbounds, out float width, out float height) { if (pstorage.alternativeScaleInLightmap) area *= 2; width = height = Mathf.Sqrt(area); if (pstorage.alternativeScaleInLightmap) width = height = Mathf.Max(1.0f, Mathf.Floor(height * 0.5f)); float uwidth = uvbounds.z - uvbounds.x; float uheight = uvbounds.w - uvbounds.y; if (uwidth == 0 && uheight == 0) { width = height = 0; } else { float uvratio = uheight / uwidth; if (uvratio <= 1.0f) { width /= uvratio; //height *= uvratio; } else { height *= uvratio; //width /= uvratio; } } } static bool Pack(BakeryLightmapGroup lmgroup, List holderObjs, ExportSceneData data, PackData pdata) { var holderObjArea = data.holderObjArea; var holderObjUVBounds = data.holderObjUVBounds; var holderRect = data.holderRect; var objToLodLevel = data.objToLodLevel; var groupList = data.groupList; var lmBounds = data.lmBounds; var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; var remainingAreaPerLodLevel = pdata.remainingAreaPerLodLevel; int atlasPaddingPixels = pstorage.texelPaddingForDefaultAtlasPacker; //Debug.LogError("repack: "+repackScale); pdata.repack = false; AtlasNode rootNode; if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas && autoAtlasGroupRootNodes != null && autoAtlasGroupRootNodes.Count > 0) { rootNode = autoAtlasGroupRootNodes[0]; } else { rootNode = new AtlasNode(); } rootNode.rc = new Rect(0, 0, 1, 1); for(int i=0; i(); if (comp != null && comp.instanceResolutionOverride) { // Explicit holder size pdata.hasResOverrides = true; width = height = comp.instanceResolution / (float)lmgroup.resolution; } else { // Automatic: width and height = sqrt(area) transformed by UV AABB aspect ratio ApplyAreaToUVBounds(area, uvbounds, out width, out height); if (pstorage.alternativeScaleInLightmap) { var mr = GetValidRenderer(holderObjs[i]); if (mr != null) { var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; #if UNITY_2019_2_OR_NEWER var _r = mr as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif width *= scaleInLm; height *= scaleInLm; } } } // Clamp to full lightmap size float twidth = width; float theight = height; if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { twidth = (width * texelsPerUnit) / lmgroup.resolution; theight = (height * texelsPerUnit) / lmgroup.resolution; //if (i==0) Debug.LogError(texelsPerUnit+" "+twidth); } //float unclampedTwidth = twidth; //float unclampedTheight = twidth; if (comp != null && comp.instanceResolutionOverride) { } else { twidth *= pdata.repackScale; theight *= pdata.repackScale; } twidth = twidth > 1 ? 1 : twidth; theight = theight > 1 ? 1 : theight; twidth = Mathf.Max(twidth, 1.0f / lmgroup.resolution); theight = Mathf.Max(theight, 1.0f / lmgroup.resolution); var rect = new Rect(0, 0, twidth, theight); if (float.IsNaN(twidth) || float.IsNaN(theight)) { ExportSceneError("NaN UVs detected for " + holderObjs[i].name+" "+rect.width+" "+rect.height+" "+width+" "+height+" "+lmgroup.resolution+" "+area+" "+(uvbounds.z - uvbounds.x)+" "+(uvbounds.w - uvbounds.y)); return false; } // Try inserting this rect // Break autoatlas if lod level changes // Optionally break autoatlas if scene changes AtlasNode node = null; int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[i], out lodLevel)) lodLevel = -1; bool splitAtlas = false; if (splitByScene) { if (holderObjs[i].scene.name != lmgroup.sceneName) { splitAtlas = true; } } if (splitByTag) { if (GetLightmapTag(holderObjs[i], data) != lmgroup.tag) { splitAtlas = true; } } /*if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { bool ba = holderObjs[i].name == "__ExportTerrainParent"; if (ba) lmgroup.containsTerrains = true; if (i > 0) { bool bb = holderObjs[i-1].name == "__ExportTerrainParent"; if (ba != bb) { splitAtlas = true; } } }*/ if (!splitAtlas) { if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { if (lodLevel == lmgroup.sceneLodLevel) { node = rootNode.Insert(holderObjs[i], rect); } } else { node = rootNode.Insert(holderObjs[i], rect); } } /*if (node!=null) { Debug.Log(holderObjs[i].name+" goes straight into "+lmgroup.name); }*/ if (node == null) { if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { // Can't fit - try other autoAtlas lightmaps BakeryLightmapGroup newGroup = null; var holder = holderObjs[i]; int goodGroup = -1; for(int g=1; g 1 ? 1 : twidth; theight = theight > 1 ? 1 : theight; rect = new Rect(0, 0, twidth, theight); node = autoAtlasGroupRootNodes[g].Insert(holder, rect); if (node != null) { //Debug.Log(holder.name+" fits into "+autoAtlasGroups[g].name); newGroup = autoAtlasGroups[g]; goodGroup = g; break; } } // Can't fit - create new lightmap (autoAtlas) if (goodGroup < 0) { newGroup = ScriptableObject.CreateInstance(); newGroup.name = FilterNonASCII(holder.scene.name) + "_LMA" + autoAtlasGroups.Count; newGroup.isImplicit = true; newGroup.sceneLodLevel = lodLevel; if (splitByScene) newGroup.sceneName = holderObjs[i].scene.name; if (splitByTag) newGroup.tag = GetLightmapTag(holderObjs[i], data); //Debug.Log(holder.name+" creates "+newGroup.name); /*if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { newGroup.containsTerrains = holderObjs[i].name == "__ExportTerrainParent"; }*/ newGroup.resolution = (int)(Mathf.Sqrt(remainingAreaPerLodLevel[lodLevel]) * texelsPerUnit); if (mustBePOT) { if (atlasCountPriority) { newGroup.resolution = Mathf.NextPowerOfTwo(newGroup.resolution); } else { newGroup.resolution = Mathf.ClosestPowerOfTwo(newGroup.resolution); } } newGroup.resolution = Math.Max(newGroup.resolution, minAutoResolution); newGroup.resolution = Math.Min(newGroup.resolution, maxAutoResolution); newGroup.bitmask = 1; newGroup.area = 0; newGroup.mode = BakeryLightmapGroup.ftLMGroupMode.PackAtlas; newGroup.id = data.lmid; groupList.Add(newGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; autoAtlasGroups.Add(newGroup); var rootNode2 = new AtlasNode(); rootNode2.rc = new Rect(0, 0, 1, 1); autoAtlasGroupRootNodes.Add(rootNode2); twidth = (width * texelsPerUnit) / newGroup.resolution; theight = (height * texelsPerUnit) / newGroup.resolution; //unclampedTwidth = twidth; //unclampedTheight = twidth; twidth = twidth > 1 ? 1 : twidth; theight = theight > 1 ? 1 : theight; rect = new Rect(0, 0, twidth, theight); node = rootNode2.Insert(holder, rect); } // Modify implicit group storage MoveObjectToImplicitGroup(holder, newGroup, data); /* var scn = holder.scene; tempStorage.implicitGroupMap[holder] = newGroup; for(int k=0; k atlas UV float padding = ((float)atlasPaddingPixels) / lmgroup.resolution; var paddedRc = new Rect(node.rc.x + padding, node.rc.y + padding, node.rc.width - padding * 2, node.rc.height - padding * 2); paddedRc.x -= uvbounds.x * (paddedRc.width / (uvbounds.z - uvbounds.x)); paddedRc.y -= uvbounds.y * (paddedRc.height / (uvbounds.w - uvbounds.y)); paddedRc.width /= uvbounds.z - uvbounds.x; paddedRc.height /= uvbounds.w - uvbounds.y; holderRect[holderObjs[i]] = paddedRc; } //float areaReduction = (twidth*theight) / (unclampedTwidth*unclampedTheight); remainingAreaPerLodLevel[lodLevel] -= area;// * areaReduction; } if (!lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { if (pdata.finalRepack && pdata.repack) { pdata.continueRepack = true; return true; } if (pdata.finalRepack) { pdata.continueRepack = false; return true; } if (!pdata.repack && !pdata.repackStage2) { //if (repackTries > 0) break; // shrinked down just now - don't scale up pdata.repackStage2 = true; // scale up now pdata.repack = true; pdata.repackScale *= atlasScaleUpValue;///= 0.75f; pdata.repackTries = 0; //Debug.LogError("Scale up, set " +repackScale); } else if (pdata.repackStage2) { pdata.repackTries++; if (pdata.repackTries == atlasMaxTries) { pdata.continueRepack = false; return true; } pdata.repack = true; pdata.repackScale *= atlasScaleUpValue;///= 0.75f; //Debug.LogError("Scale up cont, set " +repackScale); } } return true; } static void MoveObjectToImplicitGroup(GameObject holder, BakeryLightmapGroup newGroup, ExportSceneData data) { var storages = data.storages; var sceneToID = data.sceneToID; // Modify implicit group storage var scn = holder.scene; tempStorage.implicitGroupMap[holder] = newGroup; for(int k=0; k GetAtlasBucketRanges(List holderObjs, ExportSceneData data, bool onlyUserSplits) { var objToLodLevel = data.objToLodLevel; var ranges = new List(); int start = 0; int end = 0; if (holderObjs.Count > 0) { var sceneName = holderObjs[0].scene.name; int tag = -1; if (splitByTag) tag = GetLightmapTag(holderObjs[0], data); int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[0], out lodLevel)) lodLevel = -1; //bool isTerrain = holderObjs[0].name == "__ExportTerrainParent"; for(int i=0; i holderObjs, int start, int end, ExportSceneData data) { var holderObjArea = data.holderObjArea; float area = 0; for(int i=start; i<=end; i++) { float a = holderObjArea[holderObjs[i]]; var mr = holderObjs[i].GetComponent(); if (mr != null) { var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; #if UNITY_2019_2_OR_NEWER var _r = mr as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif if (pstorage.alternativeScaleInLightmap) { a *= scaleInLm * scaleInLm; } else { a *= scaleInLm; } } area += a; } return area; } static BakeryLightmapGroup AllocateAutoAtlas(int count, BakeryLightmapGroup lmgroup, ExportSceneData data, int[] atlasSizes = null) { var lmBounds = data.lmBounds; var groupList = data.groupList; var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; BakeryLightmapGroup newGroup = null; for(int i=0; i(); newGroup.name = lmgroup.sceneName + "_LMA" + autoAtlasGroups.Count; newGroup.isImplicit = true; newGroup.sceneLodLevel = lmgroup.sceneLodLevel; if (splitByScene) newGroup.sceneName = lmgroup.sceneName; if (splitByTag) newGroup.tag = lmgroup.tag; newGroup.containsTerrains = lmgroup.containsTerrains; newGroup.resolution = atlasSizes != null ? atlasSizes[i] : lmgroup.resolution; newGroup.bitmask = lmgroup.bitmask; newGroup.area = 0; newGroup.mode = lmgroup.mode;// BakeryLightmapGroup.ftLMGroupMode.PackAtlas; newGroup.renderMode = lmgroup.renderMode; newGroup.renderDirMode = lmgroup.renderDirMode; newGroup.atlasPacker = lmgroup.atlasPacker; newGroup.computeSSS = lmgroup.computeSSS; newGroup.sssSamples = lmgroup.sssSamples; newGroup.sssDensity = lmgroup.sssDensity; newGroup.sssColor = lmgroup.sssColor * lmgroup.sssScale; newGroup.fakeShadowBias = lmgroup.fakeShadowBias; newGroup.transparentSelfShadow = lmgroup.transparentSelfShadow; newGroup.flipNormal = lmgroup.flipNormal; newGroup.id = data.lmid; groupList.Add(newGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; autoAtlasGroups.Add(newGroup); var rootNode2 = new AtlasNode(); rootNode2.rc = new Rect(0, 0, 1, 1); autoAtlasGroupRootNodes.Add(rootNode2); } return newGroup; } static bool PackWithXatlas(BakeryLightmapGroup lmgroup, List holderObjs, ExportSceneData data, PackData pdata) { var holderObjArea = data.holderObjArea; var holderObjUVBounds = data.holderObjUVBounds; var holderRect = data.holderRect; var autoAtlasGroups = data.autoAtlasGroups; var objToLodLevel = data.objToLodLevel; var objsToWriteHolder = data.objsToWriteHolder; var objsToWrite = data.objsToWrite; bool warned = false; // Split objects into "buckets" by scene, terrains, LODs, etc // Objects are already pre-sorted, so we need only ranges int bStart = 0; int bEnd = holderObjs.Count-1; int bucketCount = 2; List buckets = null; if (lmgroup.isImplicit) { buckets = GetAtlasBucketRanges(holderObjs, data, postPacking); bucketCount = buckets.Count; } var holderAutoIndex = new int[holderObjs.Count]; for(int bucket=0; bucket 0) { // Start new bucket lmgroup = AllocateAutoAtlas(1, lmgroup, data); } int firstAutoAtlasIndex = autoAtlasGroups.Count - 1; if (autoAtlasGroups.Count > 0 && autoAtlasGroups[0] == lmgroup) { // new bucket always uses the original LMGroup // if the original LMGroup is implicit, it'll be moved to firstAutoAtlasIndex // but in case of the actual autoAtlas, the original LMGroup should use atlas index 0 firstAutoAtlasIndex = 0; } if (lmgroup.isImplicit) { float bucketArea = SumObjectsArea(holderObjs, bStart, bEnd, data); lmgroup.resolution = ResolutionFromArea(bucketArea); } // Fill some LMGroup data lmgroup.sceneName = holderObjs[bStart].scene.name; if (splitByTag) { lmgroup.tag = GetLightmapTag(holderObjs[bStart], data); } int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1; lmgroup.sceneLodLevel = lodLevel; /*if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { lmgroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent"; }*/ var atlas = xatlas.xatlasCreateAtlas(); const int attempts = 4096; int padding = pstorage.texelPaddingForXatlasAtlasPacker; const bool allowRotate = false; float packTexelsPerUnit = lmgroup.isImplicit ? 1.0f : 0.0f; // multiple atlaseses vs single atlas int packResolution = lmgroup.resolution; int maxChartSize = 0;//packResolution; bool bruteForce = true; // high quality int vertCount = 4; int indexCount = 6; Vector2[] uv = null; int[] indices = null; if (!holeFilling) { uv = new Vector2[4]; indices = new int[6]; indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 2; indices[4] = 3; indices[5] = 0; } var uvBuffer = new Vector2[4]; var xrefBuffer = new int[4]; var indexBuffer = new int[6]; for(int i=bStart; i<=bEnd; i++) { if (!warned) { var comp = holderObjs[i].GetComponent(); if (comp != null && comp.instanceResolutionOverride) { if (!ExportSceneValidationMessage("When using xatlas as atlas packer, 'Override resolution' option is not supported for LMGroups.\nOption is used on: " + holderObjs[i].name)) { xatlas.xatlasClear(atlas); return false; } warned = true; } } var area = holderObjArea[holderObjs[i]]; var uvbounds = holderObjUVBounds[holderObjs[i]]; // Automatic: width and height = sqrt(area) transformed by UV AABB aspect ratio float width, height; ApplyAreaToUVBounds(area, uvbounds, out width, out height); if (pstorage.alternativeScaleInLightmap) { var mr = holderObjs[i].GetComponent(); if (mr != null) { var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; #if UNITY_2019_2_OR_NEWER var _r = mr as MeshRenderer; if (pstorage.takeReceiveGIIntoAccount && _r != null && _r.receiveGI == ReceiveGI.LightProbes) scaleInLm = 0; #endif width *= scaleInLm; height *= scaleInLm; } } // Clamp to full lightmap size float twidth = width; float theight = height; if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas) { twidth = (width * texelsPerUnit);// / lmgroup.resolution; theight = (height * texelsPerUnit);// / lmgroup.resolution; } if (!holeFilling) { uv[0] = new Vector2(0,0); uv[1] = new Vector2(twidth,0); uv[2] = new Vector2(twidth,theight); uv[3] = new Vector2(0,theight); } else { List indexList = null; List uvList = null; vertCount = indexCount = 0; int numMeshes = 0; var ubounds = holderObjUVBounds[holderObjs[i]]; var holder = holderObjs[i]; for(int o=0; o(); uvList = new List(); for(int j=0; j 0) { for(int j=0; j 1) { uv = uvList.ToArray(); indices = indexList.ToArray(); } } var handleUV = GCHandle.Alloc(uv, GCHandleType.Pinned); int err = 0; try { var pointerUV = handleUV.AddrOfPinnedObject(); err = xatlas.xatlasAddUVMesh(atlas, vertCount, pointerUV, indexCount, indices, allowRotate); } finally { if (handleUV.IsAllocated) handleUV.Free(); } if (err == 1) { Debug.LogError("xatlas::AddMesh: indices are out of range"); xatlas.xatlasClear(atlas); return false; } else if (err == 2) { Debug.LogError("xatlas::AddMesh: index count is incorrect"); xatlas.xatlasClear(atlas); return false; } else if (err != 0) { Debug.LogError("xatlas::AddMesh: unknown error"); xatlas.xatlasClear(atlas); return false; } } //xatlas.xatlasParametrize(atlas); xatlas.xatlasPack(atlas, attempts, packTexelsPerUnit, packResolution, maxChartSize, padding, bruteForce, pstorage.alignToTextureBlocksWithXatlas);//, allowRotate); int atlasCount = xatlas.xatlasGetAtlasCount(atlas); var atlasSizes = new int[atlasCount]; xatlas.xatlasNormalize(atlas, atlasSizes, pstorage.alternativeScaleInLightmap); // Create additional lightmaps AllocateAutoAtlas(atlasCount-1, lmgroup, data, atlasSizes); // Move objects into new atlases if (lmgroup.isImplicit) { for(int i=0; i<=bSize; i++) { int atlasIndex = xatlas.xatlasGetAtlasIndex(atlas, i, 0); // Modify implicit group storage var holder = holderObjs[bStart + i]; var newGroup = autoAtlasGroups[firstAutoAtlasIndex + atlasIndex]; MoveObjectToImplicitGroup(holderObjs[bStart + i], newGroup, data); holderAutoIndex[bStart + i] = firstAutoAtlasIndex + atlasIndex; } } for(int i=0; i<=bSize; i++) { // Get data from xatlas int newVertCount = xatlas.xatlasGetVertexCount(atlas, i); uvBuffer = new Vector2[newVertCount]; xrefBuffer = new int[newVertCount]; int newIndexCount = xatlas.xatlasGetIndexCount(atlas, i); indexBuffer = new int[newIndexCount]; if (holeFilling) { uvBuffer = new Vector2[newVertCount]; xrefBuffer = new int[newVertCount]; indexBuffer = new int[newIndexCount]; } var handleT = GCHandle.Alloc(uvBuffer, GCHandleType.Pinned); var handleX = GCHandle.Alloc(xrefBuffer, GCHandleType.Pinned); var handleI = GCHandle.Alloc(indexBuffer, GCHandleType.Pinned); try { var pointerT = handleT.AddrOfPinnedObject(); var pointerX = handleX.AddrOfPinnedObject(); var pointerI = handleI.AddrOfPinnedObject(); xatlas.xatlasGetData(atlas, i, pointerT, pointerX, pointerI); } finally { if (handleT.IsAllocated) handleT.Free(); if (handleX.IsAllocated) handleX.Free(); if (handleI.IsAllocated) handleI.Free(); } float minU = float.MaxValue; float minV = float.MaxValue; float maxU = -float.MaxValue; float maxV = -float.MaxValue; for(int j=0; j maxU) maxU = uvBuffer[j].x; if (uvBuffer[j].y > maxV) maxV = uvBuffer[j].y; } // Generate final rectangle to transform local UV -> atlas UV float upadding = 0; var uvbounds = holderObjUVBounds[holderObjs[bStart + i]]; var paddedRc = new Rect(minU + upadding, minV + upadding, (maxU-minU) - upadding * 2, (maxV-minV) - upadding * 2); paddedRc.x -= uvbounds.x * (paddedRc.width / (uvbounds.z - uvbounds.x)); paddedRc.y -= uvbounds.y * (paddedRc.height / (uvbounds.w - uvbounds.y)); paddedRc.width /= uvbounds.z - uvbounds.x; paddedRc.height /= uvbounds.w - uvbounds.y; holderRect[holderObjs[bStart + i]] = paddedRc; } xatlas.xatlasClear(atlas); } if (postPacking) { buckets = GetAtlasBucketRanges(holderObjs, data, false); bucketCount = buckets.Count; DebugLogInfo("Bucket count for " + lmgroup.name +": " + (bucketCount/2)); if (lmgroup.isImplicit) { // Post-packing for auto-atlased groups var autoLMBuckets = new List[autoAtlasGroups.Count]; for(int bucket=0; bucket(); if (!autoLMBuckets[autoLM].Contains(bucket)) autoLMBuckets[autoLM].Add(bucket); } } int origGroupCount = autoAtlasGroups.Count; for(int i=0; i 1) { // Split for(int j=1; j "+newGroup.name + " (" + newGroup.id+", "+newGroup.parentID+")"); for(int k=bStart; k<=bEnd; k++) { int autoLM = holderAutoIndex[k]; if (autoLM == i) { MoveObjectToImplicitGroup(holderObjs[k], newGroup, data); holderAutoIndex[k] = -1; // mark as moved } } } } } for(int i=0; i 0) { // Post-packing for explicit groups // Single LMGroup -> LMGroup*buckets // Setup first bucket bStart = buckets[0]; bEnd = buckets[1]; int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1; lmgroup.sceneLodLevel = lodLevel; /*if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { lmgroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent"; }*/ //Debug.LogError(lmgroup.name+": "+ lmgroup.sceneLodLevel+" because of " + holderObjs[bStart].name); // Skip first bucket for(int bucket=2; bucket holderObjs, ExportSceneData data, PackData pdata) { var holderRect = data.holderRect; if (!lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas && !pdata.hasResOverrides) { float maxx = 0; float maxy = 0; for(int i=0; i maxx) maxx = rect.x + rect.width; if ((rect.y + rect.height) > maxy) maxy = rect.y + rect.height; } float maxDimension = maxx > maxy ? maxx : maxy; float normalizeScale = 1.0f / maxDimension; for(int i=0; i(); for(int g=0; g 0) continue; var rootNode = autoAtlasGroupRootNodes[g]; float maxx = 0; float maxy = 0; rootNode.GetMax(ref maxx, ref maxy); float maxDimension = maxx > maxy ? maxx : maxy; float normalizeScale = 1.0f / maxDimension; stack.Clear(); stack.Push(rootNode); while(stack.Count > 0) { var node = stack.Pop(); if (node.obj != null) { var rect = holderRect[node.obj]; holderRect[node.obj] = new Rect(rect.x * normalizeScale, rect.y * normalizeScale, rect.width * normalizeScale, rect.height * normalizeScale); } if (node.child0 != null) stack.Push(node.child0); if (node.child1 != null) stack.Push(node.child1); } if (maxDimension < 0.5f) { lmgroup.resolution /= 2; // shrink the lightmap after normalization if it was too empty lmgroup.resolution = Math.Max(lmgroup.resolution, minAutoResolution); } } } static void JoinAutoAtlases(ExportSceneData data) { var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; var groupList = data.groupList; var lmBounds = data.lmBounds; var holderRect = data.holderRect; var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var stack = new Stack(); // Join autoatlases var autoAtlasCategories = new List(); bool joined = false; for(int g=0; g 0) continue; string cat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) cat = autoAtlasGroups[g].sceneName + cat; if (splitByTag) cat = autoAtlasGroups[g].tag + "/" + cat; if (!autoAtlasCategories.Contains(cat)) autoAtlasCategories.Add(cat); } for(int alod=0; alod(); var atlasStack = new Stack(); for(int g=0; g 0) continue; var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat; if (splitByTag) thisCat = autoAtlasGroups[g].tag + "/" + thisCat; if (thisCat != cat) continue; if (autoAtlasGroups[g].resolution == maxAutoResolution) continue; if (!autoAtlasSizes.Contains(autoAtlasGroups[g].resolution)) autoAtlasSizes.Add(autoAtlasGroups[g].resolution); } autoAtlasSizes.Sort(); for(int s=0; s 0) continue; var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat; if (splitByTag) thisCat = autoAtlasGroups[g].tag + "/" + thisCat; if (thisCat != cat) continue; if (autoAtlasGroups[g].resolution != asize) continue; atlasStack.Push(autoAtlasGroups[g]); if (atlasStack.Count == 4) { var newGroup = ScriptableObject.CreateInstance(); newGroup.name = autoAtlasGroups[g].name; newGroup.isImplicit = true; newGroup.sceneLodLevel = autoAtlasGroups[g].sceneLodLevel; newGroup.sceneName = autoAtlasGroups[g].sceneName; newGroup.resolution = asize * 2; newGroup.bitmask = autoAtlasGroups[g].bitmask; newGroup.mode = BakeryLightmapGroup.ftLMGroupMode.PackAtlas; newGroup.id = data.lmid; groupList.Add(newGroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; autoAtlasGroups.Add(newGroup); var rootNode2 = new AtlasNode(); rootNode2.rc = new Rect(0, 0, 1, 1); autoAtlasGroupRootNodes.Add(rootNode2); // Top rootNode2.child0 = new AtlasNode(); rootNode2.child0.rc = new Rect(0, 0, 1, 0.5f); // Bottom rootNode2.child1 = new AtlasNode(); rootNode2.child1.rc = new Rect(0, 0.5f, 1, 0.5f); for(int gg=0; gg<4; gg++) { var subgroup = atlasStack.Pop(); var id = autoAtlasGroups.IndexOf(subgroup); var subgroupRootNode = autoAtlasGroupRootNodes[id]; float ox, oy, sx, sy; if (gg == 0) { // Left top rootNode2.child0.child0 = subgroupRootNode; //rootNode2.child0.child0.Transform(0, 0, 0.5f, 0.5f); //offsetScale = rootNode2.child0.child0.rc; ox = 0; oy = 0; sx = 0.5f; sy = 0.5f; } else if (gg == 1) { // Right top rootNode2.child0.child1 = subgroupRootNode; //rootNode2.child0.child1.Transform(0.5f, 0, 0.5f, 0.5f); //offsetScale = rootNode2.child0.child1.rc; ox = 0.5f; oy = 0; sx = 0.5f; sy = 0.5f; } else if (gg == 2) { // Left bottom rootNode2.child1.child0 = subgroupRootNode; //rootNode2.child1.child0.Transform(0, 0.5f, 0.5f, 0.5f); //offsetScale = rootNode2.child1.child0.rc; ox = 0; oy = 0.5f; sx = 0.5f; sy = 0.5f; } else { // Right bottom rootNode2.child1.child1 = subgroupRootNode; //rootNode2.child1.child1.Transform(0.5f, 0.5f, 0.5f, 0.5f); //offsetScale = rootNode2.child1.child1.rc; ox = 0.5f; oy = 0.5f; sx = 0.5f; sy = 0.5f; } autoAtlasGroups.RemoveAt(id); autoAtlasGroupRootNodes.RemoveAt(id); id = groupList.IndexOf(subgroup); groupList.RemoveAt(id); lmBounds.RemoveAt(id); for(int x=id; x 0) { var node = stack.Pop(); if (node.obj != null) { var rect = holderRect[node.obj]; holderRect[node.obj] = new Rect(rect.x * sx + ox, rect.y * sy + oy, rect.width * sx, rect.height * sy); MoveObjectToImplicitGroup(node.obj, newGroup, data); /* tempStorage.implicitGroupMap[node.obj] = newGroup; for(int k=0; k sceneToID = new Dictionary(); public Dictionary sceneHasStorage = new Dictionary(); // Object properties public Dictionary objToLodLevel = new Dictionary(); // defines atlas LOD level public Dictionary> objToLodLevelVisible = new Dictionary>(); // defines LOD levels where this object is visible public Dictionary objToScaleInLm = new Dictionary(); public Dictionary objToBakeTag; public List objsToWrite = new List(); public List objsToWriteLightmapped = new List(); public List objsToWriteGroup = new List(); public List objsToWriteHolder = new List(); public List objsToWriteScaleOffset = new List(); public List objsToWriteUVOverride = new List(); public List objsToWriteNames = new List(); public List objsToWriteVerticesPosW = new List(); public List objsToWriteVerticesNormalW = new List(); public List objsToWriteVerticesTangentW = new List(); public List objsToWriteVerticesUV = new List(); public List objsToWriteVerticesUV2 = new List(); public List objsToWriteIndices = new List(); public List objsToWriteHasMetaAlpha = new List(); public List outsideRenderers = new List(); // for sector+SRP only // Auto-atlasing public List autoAtlasGroups = new List(); public List autoAtlasGroupRootNodes = new List(); public BakeryLightmapGroup autoVertexGroup; // Data to collect for atlas packing public Dictionary holderObjArea = new Dictionary(); // LMGroup holder area, accumulated from all children public Dictionary holderObjUVBounds = new Dictionary(); // LMGroup holder 2D UV AABB public Dictionary> groupToHolderObjects = new Dictionary>(); // LMGroup -> holders map public Dictionary holderRect = new Dictionary(); // Per-LMGroup data public List groupList = new List(); public List lmBounds = new List(); // list of bounding boxes around LMGroups for testing lights // Geometry data public List[] indicesOpaqueLOD = null; public List[] indicesTransparentLOD = null; public int lmid = 0; // LMID counter public ExportSceneData(int sceneCount) { storages = new ftLightmapsStorage[sceneCount]; } } class AdjustUVPaddingData { public List dirtyObjList = new List(); public List dirtyAssetList = new List(); public Dictionary> meshToObjIDs = new Dictionary>(); public Dictionary meshToPaddingMap = new Dictionary(); } class PackData { public Dictionary remainingAreaPerLodLevel = new Dictionary(); public bool repack = true; public bool repackStage2 = false; public bool finalRepack = false; public float repackScale = 1; public int repackTries = 0; public bool hasResOverrides = false; public bool continueRepack = false; } static public IEnumerator ExportScene(EditorWindow window, bool renderTextures = true, bool atlasOnly = false, BakerySectorCapture sectorCaptureAsset = null) { lmgroupHolder = null; // important to properly flush previous scale-in-lightmaps userCanceled = false; ProgressBarInit("Exporting scene - preparing...", window); yield return null; var bakeryRuntimePath = ftLightmaps.GetRuntimePath(); gstorage = AssetDatabase.LoadAssetAtPath(bakeryRuntimePath + "ftGlobalStorage.asset", typeof(ftGlobalStorage)) as ftGlobalStorage; pstorage = ftLightmaps.GetProjectSettings(); bool isDX11 = SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11; bool nonDX11 = !isDX11; bool _unwrapUVs = unwrapUVs; bool _forceDisableUnwrapUVs = forceDisableUnwrapUVs; if (ftRenderLightmap.fullSectorRender && !ftRenderLightmap.curSector.allowUVPaddingAdjustment) { _unwrapUVs = false; _forceDisableUnwrapUVs = false; } var time = GetTime(); var ms = time; var startMsU = ms; double totalTime = GetTime(); double vbTimeRead = 0; double vbTimeWrite = 0; double vbTimeWriteFull = 0; double vbTimeWriteT = 0; double vbTimeWriteT2 = 0; double vbTimeWriteT3 = 0; double ibTime = 0; var sceneCount = SceneManager.sceneCount; var indicesOpaque = new List(); var indicesTransparent = new List(); var data = new ExportSceneData(sceneCount); bool tangentSHLights = CheckForTangentSHLights(); // Per-LMGroup data var lmAlbedoList = new List(); // list of albedo texture for UV GBuffer rendering var lmAlbedoListTex = new List(); var lmAlphaList = new List(); // list of alpha textures for alpha buffer generation var lmAlphaListRAM = new List(); // non-DX11 array var lmAlphaListTex = new List(); var lmAlphaRefList = new List(); // list of alpha texture refs var lmAlphaChannelList = new List(); // list of alpha channels // lod-related var lmVOffset = new List(); var lmUVArrays = new List>(); var lmIndexArrays = new List>(); var lmLocalToGlobalIndices = new List>(); vbtraceTexPosNormalArray = new List(); vbtraceTexUVArray = new List(); sceneLodsUsed = 0; // Create temp path CreateSceneFolder(); // Disable preview of any sectors if (sectorCaptureAsset == null) { var allSectors = FindObjectsOfType(typeof(BakerySector)) as BakerySector[]; for(int i=0; i(); #if USE_TERRAINS terrainObjectToActual = new List(); #endif terrainObjectToHeightMap = new List(); terrainObjectToHeightMapRAM = new List(); terrainObjectToBounds = new List(); terrainObjectToBoundsUV = new List(); terrainObjectToFlags = new List(); terrainObjectToLMID = new List(); terrainObjectToHeightMips = new List>(); temporaryGameObjects = new List(); temporaryAreaLightMeshList = new List(); temporaryAreaLightMeshList2 = new List(); var objects = Resources.FindObjectsOfTypeAll(typeof(GameObject)); //var objects = UnityEngine.Object.FindObjectsOfTypeAll(typeof(GameObject)); try { ms = GetTime(); //if (!onlyUVdata) //{ time = ms; // Get manually created LMGroups CollectExplicitLMGroups(data); // Object conversion loop / also validate for multiple scene storages for(int objNum = 0; objNum < objects.Length; objNum++) { GameObject obj = (GameObject)objects[objNum]; if (obj == null) continue; if (!obj.activeInHierarchy) continue; var path = AssetDatabase.GetAssetPath(obj); if (path != "") continue; // must belong to scene if (!CheckForMultipleSceneStorages(obj, data)) yield break; if (ConvertUnityAreaLight(obj)) continue; #if USE_TERRAINS ConvertTerrain(obj); #endif } // Regather objects if new were added if (terrainObjectList.Count > 0 || temporaryGameObjects.Count > 0 || temporaryAreaLightMeshList.Count > 0) { //objects = UnityEngine.Object.FindObjectsOfTypeAll(typeof(GameObject)); objects = Resources.FindObjectsOfTypeAll(typeof(GameObject)); } tempStorage.implicitGroupMap = new Dictionary(); // implicit holder -> LMGroup map. used by GetLMGroupFromObject // Find LODGroups -> LODs -> scene-wide LOD distances // Map objects to scene-wide LOD levels MapObjectsToSceneLODs(data, objects); ftModelPostProcessor.Init(); // Filter objects, convert to property arrays if (!FilterObjects(data, objects)) yield break; if (ftRenderLightmap.fullSectorRender) { var sector = ftRenderLightmap.curSector; if (sector.captureMode == BakerySector.CaptureMode.CaptureInPlace || (sectorCaptureAsset != null && sectorCaptureAsset.write)) { int objCount = data.objsToWrite.Count; var cpoints = sector.cpoints; // Render albedo/alpha/depth for each sector point var fdata = new FarSphereRenderData[cpoints.Count]; for(int i=0; i(); sectorCaptureAsset.positions = new List(); sectorCaptureAsset.textures = new List(); for(int i=0; i(); atlasOnlySize = new List(); atlasOnlyID = new List(); atlasOnlyGroup = new List(); atlasOnlyScaleOffset = new List(); var emptyVec4 = new Vector4(1,1,0,0); Rect rc = new Rect(); for(int i=0; i() != null) continue; var holderObj = objsToWriteHolder[i]; if (holderObj != null) { if (!holderRect.TryGetValue(holderObj, out rc)) { holderObj = null; } } var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y); atlasOnlyObj.Add(GetValidRenderer(objsToWrite[i])); atlasOnlyScaleOffset.Add(scaleOffset); atlasOnlySize.Add(lmgroup == null ? 0 : lmgroup.resolution); int id = lmgroup == null ? 0 : lmgroup.id; if (lmgroup != null) { var nm = lmgroup.parentName; if (nm != null && nm.Length > 0 && nm != "|") { // dependant sub-lightmaps for(int j=0; j(); for(int i=0; i= 0) { for(int j=0; j(); for(int o=0; o 0 && ftRenderLightmap.verbose) { ProgressBarEnd(false); if (!EditorUtility.DisplayDialog("Lightmap overwrite", "These lightmaps will be overwritten:\n\n" + existingFilenames, "Overwrite", "Cancel")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } } ftRenderLightmap.giLodModeEnabled = false;//ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.ForceOn; ulong approxMem = 0; if (groupList.Count > 100 && ftRenderLightmap.verbose) { ProgressBarEnd(false); if (!EditorUtility.DisplayDialog("Lightmap count check", groupList.Count + " lightmaps are going to be rendered. Continue?", "Continue", "Cancel")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } if (memoryWarning || ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.Auto) { for(int i=0; i SystemInfo.graphicsMemorySize) { DebugLogInfo("GI VRAM auto optimization ON: estimated usage " + (int)approxMem + " > " + SystemInfo.graphicsMemorySize); ftRenderLightmap.giLodModeEnabled = true; } else { DebugLogInfo("GI VRAM auto optimization OFF: estimated usage " + (int)approxMem + " < " + SystemInfo.graphicsMemorySize); } } // Generate terrain geometry with detail enough for given size for UVGBuffer purposes fhmaps = new BinaryWriter(File.Open(scenePath + "/heightmaps.bin", FileMode.Create)); if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("heightmaps.bin"); #if USE_TERRAINS if (exportTerrainAsHeightmap) { for(int i=0; i 0) { for(int i=0; i 0) uvgbGlobalFlags |= UVGBFLAG_TERRAIN; #endif SetUVGBFlags(uvgbGlobalFlags); for(int i=0; i 8192) DebugLogWarning("Warning: vertex lightmap group " + lmgroup.name + " uses resolution of " + atlasTexSize); atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize; flms.Write(-atlasTexSize); } else { flms.Write(lmgroup.resolution); } //Debug.LogError(lmgroup.name+": " + lmgroup.resolution); } flms.Close(); flmlod.Close(); flmuvgb.Close(); voffset = ioffset = soffset = 0; // vertex/index/surface write // Per-surface alpha texture IDs var alphaIDs = new List(); int albedoCounter = 0; var albedoMap = new Dictionary(); // albedo ptr -> ID map int alphaCounter = 0; var alphaMap = new Dictionary>(); // alpha ptr -> ID map var dummyTexList = new List(); // list of single-color 1px textures var dummyPixelArray = new Color[1]; if (ftRenderLightmap.checkOverlaps) { var quad = GameObject.CreatePrimitive(PrimitiveType.Quad); var plane = GameObject.CreatePrimitive(PrimitiveType.Plane); var qmesh = quad.GetComponent().sharedMesh; var pmesh = plane.GetComponent().sharedMesh; DestroyImmediate(quad); DestroyImmediate(plane); bool canCheck = ftModelPostProcessor.InitOverlapCheck(); if (!canCheck) { DebugLogError("Can't load ftOverlapTest.shader"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } for(int g=0; g 1.0001f || usedUVs[v].y < -0.0001f || usedUVs[v].y > 1.0001f) { validUVs = false; break; } } if (!validUVs && ftRenderLightmap.verbose) { string objPath = obj.name; var prt = obj.transform.parent; while(prt != null) { objPath = prt.name + "\\" + objPath; prt = prt.parent; } ftRenderLightmap.simpleProgressBarEnd(); if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " UVs are out of 0-1 bounds", "Continue", "Stop")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } int overlap = ftModelPostProcessor.DoOverlapCheck(obj, false); if (overlap != 0 && ftRenderLightmap.verbose) { //storage.debugRT = ftModelPostProcessor.rt; string objPath = obj.name; var prt = obj.transform.parent; while(prt != null) { objPath = prt.name + "\\" + objPath; prt = prt.parent; } if (overlap < 0) { ftRenderLightmap.simpleProgressBarEnd(); if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " has no UV2", "Continue", "Stop")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } else { ftRenderLightmap.simpleProgressBarEnd(); if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " has overlapping UVs", "Continue", "Stop")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } } } } ftModelPostProcessor.EndOverlapCheck(); } // Prepare progressbar int progressNumObjects = 0; foreach(GameObject obj in objects) { if (obj == null) continue; if (!obj.activeInHierarchy) continue; progressNumObjects++; } // Open files to write fscene = new BinaryWriter(File.Open(scenePath + "/objects.bin", FileMode.Create)); fmesh = new BinaryWriter(File.Open(scenePath + "/mesh.bin", FileMode.Create)); flmid = new BinaryWriter(File.Open(scenePath + "/lmid.bin", FileMode.Create)); fseamfix = new BinaryWriter(File.Open(scenePath + "/seamfix.bin", FileMode.Create)); fsurf = new BinaryWriter(File.Open(scenePath + "/surf.bin", FileMode.Create)); fmatid = new BinaryWriter(File.Open(scenePath + "/matid.bin", FileMode.Create)); fmatide = new BinaryWriter(File.Open(scenePath + "/emissiveid.bin", FileMode.Create)); fmatideb = new BinaryWriter(File.Open(scenePath + "/emissivemul.bin", FileMode.Create)); fmatidh = new BinaryWriter(File.Open(scenePath + "/heightmapid.bin", FileMode.Create)); falphaid = new BinaryWriter(File.Open(scenePath + "/alphaid.bin", FileMode.Create)); fvbfull = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbfull.bin", FileMode.Create)) ); fvbtrace = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtrace.bin", FileMode.Create)) ); fvbtraceTex = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtraceTex.bin", FileMode.Create)) ); fvbtraceUV0 = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtraceUV0.bin", FileMode.Create)) ); fib = new BufferedBinaryWriterInt( new BinaryWriter(File.Open(scenePath + "/ib.bin", FileMode.Create)) ); fib32 = new BinaryWriter(File.Open(scenePath + "/ib32.bin", FileMode.Create)); fib32lod = new BinaryWriter[sceneLodsUsed]; for(int i=0; i(); for(int i=0; i= 0) { for(int k=0; k 1) alphaRef = 1; // Using alpha texture directly // allow same map instances with different threshold List texIDs; if (!alphaMap.TryGetValue(texPtr, out texIDs)) { alphaMap[texPtr] = texIDs = new List(); lmAlphaList.Add(texPtr); if (nonDX11) lmAlphaListRAM.Add(InputDataFromTex(tex)); lmAlphaListTex.Add(tex); lmAlphaRefList.Add(alphaRef); lmAlphaChannelList.Add(alphaChannel); texIDs.Add(alphaCounter); texID = alphaCounter; alphaCounter++; //Debug.Log("Alpha " + texID+": " + tex.name+" "+alphaRef); alphaID = (ushort)texID; } else { int matchingInstance = -1; for(int instance=0; instance()); lmLocalToGlobalIndices.Add(new List()); lmVOffset.Add(0); } var mmr = GetValidRenderer(obj); var castsShadows = mmr.shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off; if (exportTerrainAsHeightmap && obj.name == "__ExportTerrain") castsShadows = false; // prevent exporting placeholder quads to ftrace time = GetTime(); for(int k=0;k k) { if (mats[k] != null) { var matTag = mats[k].GetTag("RenderType", true); if (matTag == "Transparent" || matTag == "TreeLeaf") { if (mats[k].HasProperty("_Color")) { if (mats[k].color.a < 0.5f) { if (!mats[k].HasProperty("BAKERY_META_ALPHA_ENABLE")) { submeshCastsShadows = false; } } } } } } } // Generate tracing index buffer, write alpha IDs per triangle if (submeshCastsShadows) { var alphaID = alphaIDs[(alphaIDs.Count - m.subMeshCount) + k]; if (lodLevel < 0) { // Export persistent IB var indicesOpaqueArray = indicesOpaque; var indicesTransparentArray = indicesTransparent; var falphaidFile = falphaid; exportIB32(indicesOpaqueArray, indicesTransparentArray, id>=0 ? lmIndexArrays[id] : null, inds[k], isFlipped, currentVoffset, id>=0 ? lmVOffset[id] : 0, falphaidFile, alphaID); } else { // Export LOD IBs var visList = objToLodLevelVisible[obj]; for(int vlod=0; vlod=0 ? lmIndexArrays[id] : null, inds[k], isFlipped, currentVoffset, id>=0 ? lmVOffset[id] : 0, falphaidFile, alphaID); } } } ioffset += indexCount; } ibTime += GetTime() - time; if (id >= 0) { var vcount = objsToWriteVerticesPosW[i].Length;//m.vertexCount; var remapArray = lmLocalToGlobalIndices[id]; var addition = lmVOffset[id]; for(int k=0; k(); if (areaLight == null) { var areaIndex = temporaryAreaLightMeshList.IndexOf(obj); if (areaIndex >= 0) areaLight = temporaryAreaLightMeshList2[areaIndex]; } //var areaLight = if (areaLight != null) id = areaLight.lmid; var vertexBake = lmgroup != null ? (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) : false; //var castsShadows = obj.GetComponent().shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off; var holderObj = objsToWriteHolder[i]; if (holderObj != null) { if (!holderRect.TryGetValue(holderObj, out rc)) { holderObj = null; } } time = GetTime(); //var vertices = m.vertices; //var normals = m.normals; //var tangents = m.tangents; var uv = objsToWriteVerticesUV[i];//m.uv; var uv2 = objsToWriteVerticesUV2[i];//m.uv2; if (uv2.Length == 0 && !vertexBake) uv2 = uv;//m.uv; vbTimeRead += GetTime() - time; var inds = objsToWriteIndices[i]; var time2 = GetTime(); time = time2; // Transform UVs var tformedPos = objsToWriteVerticesPosW[i];// new Vector3[vertices.Length]; var tformedNormals = objsToWriteVerticesNormalW[i];// new Vector3[normals.Length]; Vector4[] tformedTangents = null; if (NeedsTangents(lmgroup, tangentSHLights)) { tformedTangents = objsToWriteVerticesTangentW[i]; } Vector2[] tformedUV2; if (areaLight == null && !vertexBake) { tformedUV2 = holderObj == null ? uv2 : new Vector2[tformedPos.Length]; for(int t=0; t= 0) { while(lmUVArrays.Count <= id) { lmUVArrays.Add(new List()); } var lmUVArray = lmUVArrays[id]; for(int k=0; k groupList[g].id && str.hasEmissive[groupList[g].id]; bool vertexBake = groupList[g].mode == BakeryLightmapGroup.ftLMGroupMode.Vertex; int res = groupList[g].resolution; if (vertexBake) { if (groupList[g].totalVertexCount == 0) { DebugLogError("Vertex lightmap group " + groupList[g].name + " has 0 static vertices. Make sure objects inside the group don't all have Scale In Lightmap == 0."); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)groupList[g].totalVertexCount)); atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize; res = atlasTexSize; } var bakeWithNormalMaps = (groupList[g].renderDirMode == BakeryLightmapGroup.RenderDirMode.BakedNormalMaps) ? true : (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.BakedNormalMaps); if (groupList[g].probes) bakeWithNormalMaps = false; bool hasMetaAlpha = false; ftUVGBufferGen.StartUVGBuffer(res, hasEmissive, bakeWithNormalMaps); for(int i=0; i()) continue; var bakedMesh = GetSharedMeshBaked(obj); ftUVGBufferGen.RenderUVGBuffer(bakedMesh, GetValidRenderer(obj), objsToWriteScaleOffset[i], obj.transform, vertexBake, objsToWriteUVOverride[i], bakeWithNormalMaps && !exportTerrainAsHeightmap && obj.name == "__ExportTerrain", objsToWriteHasMetaAlpha[i]); if (objsToWriteHasMetaAlpha[i]) hasMetaAlpha = true; } ftUVGBufferGen.EndUVGBuffer(); var albedo = ftUVGBufferGen.texAlbedo; var emissive = ftUVGBufferGen.texEmissive; var normal = ftUVGBufferGen.texNormal; var alpha = ftUVGBufferGen.texAlpha; if (hasEmissive) { //albedo = ftUVGBufferGen.GetAlbedoWithoutEmissive(ftUVGBufferGen.texAlbedo, ftUVGBufferGen.texEmissive); //if ((unityVersionMajor == 2017 && unityVersionMinor < 2) || unityVersionMajor < 2017) //{ #if UNITY_2017_2_OR_NEWER #else // Unity before 2017.2: emissive packed to RGBM // Unity after 2017.2: linear emissive emissive = ftUVGBufferGen.DecodeFromRGBM(emissive); #endif //} if (ftRenderLightmap.hackEmissiveBoost != 1.0f) { ftUVGBufferGen.Multiply(emissive, ftRenderLightmap.hackEmissiveBoost); } //if (!vertexBake) ftUVGBufferGen.Dilate(emissive); } if (!vertexBake) ftUVGBufferGen.Dilate(albedo); if (isDX11) { SaveGBufferMap(albedo.GetNativeTexturePtr(), scenePath + "/uvalbedo_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); yield return null; } else { var bytes = InputBytesFromTex(albedo); SaveGBufferMapFromRAM(bytes, bytes.Length, scenePath + "/uvalbedo_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); } DestroyImmediate(albedo); //if (g==2) storage.debugTex = emissive; if (hasEmissive) { if (isDX11) { SaveGBufferMap(emissive.GetNativeTexturePtr(), scenePath + "/uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); yield return null; } else { var bytes = InputBytesFromTex(emissive, TexInputType.HalfColor); SaveGBufferMapFromRAM(bytes, bytes.Length, scenePath + "/uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); } if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); DestroyImmediate(emissive); } if (bakeWithNormalMaps) { if (isDX11) { SaveGBufferMap(normal.GetNativeTexturePtr(), scenePath + "/uvnormal_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); yield return null; } else { var bytes = InputBytesFromTex(normal); SaveGBufferMapFromRAM(bytes, bytes.Length, scenePath + "/uvnormal_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); } DestroyImmediate(normal); } if (hasMetaAlpha) { for(int i=0; i 0) { if (isDX11) { var terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; for(int i=0; i