#if UNITY_EDITOR // 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 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)] 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 ("unityLib11")] private static extern void SaveTexture(IntPtr tex, string path); [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); [DllImport ("frender", CallingConvention=CallingConvention.Cdecl)] public static extern void SaveCookie(IntPtr 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(); 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; public static int atlasPaddingPixels = 3; public static bool atlasCountPriority = false; public static bool splitByScene = false; 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; static List terrainObjectToActual; static List terrainObjectToHeightMap; static IntPtr[] terrainObjectToHeightMapPtr; 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; static List treeObjectList; static Dictionary cmp_objToLodLevel; static Dictionary cmp_holderObjArea; 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 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 public void DebugLogError(string text) { ftRenderLightmap.DebugLogError(text); } 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 exportVBPos(BinaryWriter f, Transform t, Mesh m, Vector3[] vertices) { for(int i=0;i(); // 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 so = new SerializedObject(obj.GetComponent()); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; 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 = obj.GetComponent(); if (r) { var so = new SerializedObject(r); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; 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 = obj.GetComponent(); if (r) { var so = new SerializedObject(r); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; 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 || (lmgroup!=null && lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.SH)) 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 bool progressBarEnabled = false; static public bool userCanceled = false; //static IEnumerator progressFunc; static EditorWindow activeWindow; static void ProgressBarInit(string startText, EditorWindow window = null) { progressBarText = startText; //progressBarEnabled = true; ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0); } static void ProgressBarShow(string text, float percent) { progressBarText = text; progressBarPercent = percent; ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0); 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 (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(); storages[i].bakedRenderersTerrain = new List(); storages[i].bakedIDsTerrain = new List(); storages[i].bakedScaleOffsetTerrain = new List(); } } } 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; 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.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.ContributeGI) == 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; while(patchRes > 254) patchRes = 254;//patchRes /= 2; 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; 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; 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(tdata.baseMapResolution, tdata.baseMapResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); var tex = new Texture2D(tdata.baseMapResolution, tdata.baseMapResolution, TextureFormat.ARGB32, true, false); RenderTexture.active = tempCam.targetTexture; tempCam.Render(); terr.materialTemplate = oldMat; terr.materialType = oldMatType; obj.transform.position = oldPos; RenderTexture.active = tempCam.targetTexture; tex.ReadPixels(new Rect(0,0,tdata.baseMapResolution, tdata.baseMapResolution), 0, 0, true); tex.Apply(); unlitTerrainMat.mainTexture = tex; Graphics.SetRenderTarget(null); DestroyImmediate(tempCamGO); 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.ContributeGI); 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; treeObjectList.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 = obj.GetComponent(); 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.ContributeGI); 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; 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(); var sharedMesh = GetSharedMesh(mr); if (mr == null || sharedMesh == null) continue; // must have visible mesh var mrEnabled = mr.enabled || r.gameObject.GetComponent() != null; if (!mrEnabled) continue; //if (mf.sharedMesh == null) continue; var so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; 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[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); }); } } static bool FilterObjects(ExportSceneData data, UnityEngine.Object[] objects) { var objToLodLevel = data.objToLodLevel; var groupList = data.groupList; var lmBounds = data.lmBounds; 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 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 = obj.GetComponent(); if (mr as MeshRenderer == null && mr as SkinnedMeshRenderer == 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.ContributeGI) == 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(obj.GetComponent()); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; 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; if (!postPacking) { if (group.sceneLodLevel == -1) { group.sceneLodLevel = lodLevel; } else { 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 (splitByScene) group.sceneName = obj.scene.name; // 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) { data.autoVertexGroup = ScriptableObject.CreateInstance(); data.autoVertexGroup.name = obj.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++; } group = data.autoVertexGroup; storages[sceneToID[obj.scene]].implicitGroupedObjects.Add(obj); storages[sceneToID[obj.scene]].implicitGroups.Add(data.autoVertexGroup); tempStorage.implicitGroupMap[obj] = data.autoVertexGroup; storages[sceneToID[obj.scene]].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) { Debug.LogWarning("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 = 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; if (splitByScene) newGroup.sceneName = holder.scene.name; } 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; 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()); /*if (st.bakedIDs[index] != lmgroup.id) { Debug.LogError("ValidateScaleOffsetImmutability: LMID does not match"); Debug.LogError(st.bakedIDs[index]+" "+lmgroup.id+" "+lmgroup.name); return false; }*/ if (index < 0 || st.bakedScaleOffset.Count <= index) continue; storedScaleOffset = st.bakedScaleOffset[index]; } // approx equality if (!(scaleOffset == storedScaleOffset)) { Debug.LogError("ValidateScaleOffsetImmutability: scale/offset does not match"); return false; } } } return true; } static bool ClearUVPadding(ExportSceneData data, AdjustUVPaddingData adata) { var objsToWrite = data.objsToWrite; var dirtyAssetList = adata.dirtyAssetList; var dirtyObjList = adata.dirtyObjList; 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 so = new SerializedObject(mr); var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue; area *= scaleInLm; if (lmgroup.isImplicit && 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) { width = height = Mathf.Sqrt(area); 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; //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); } // 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 (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 = holder.scene.name + "_LMA" + autoAtlasGroups.Count; newGroup.isImplicit = true; newGroup.sceneLodLevel = lodLevel; if (splitByScene) newGroup.sceneName = holderObjs[i].scene.name; //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 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++) { area += holderObjArea[holderObjs[i]]; } 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; newGroup.containsTerrains = lmgroup.containsTerrains; newGroup.resolution = atlasSizes != null ? atlasSizes[i] : lmgroup.resolution; newGroup.bitmask = 1; 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; 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 (lmgroup.isImplicit) { float bucketArea = SumObjectsArea(holderObjs, bStart, bEnd, data); lmgroup.resolution = ResolutionFromArea(bucketArea); } // Fill some LMGroup data lmgroup.sceneName = holderObjs[bStart].scene.name; 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; const int padding = 1; 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); // 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);//, allowRotate); int atlasCount = xatlas.xatlasGetAtlasCount(atlas); var atlasSizes = new int[atlasCount]; xatlas.xatlasNormalize(atlas, atlasSizes); // 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; Debug.Log("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 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(); var atlasStack = new Stack(); for(int g=0; g(); 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 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(); // 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) { userCanceled = false; ProgressBarInit("Exporting scene - preparing...", window); yield return null; var bakeryRuntimePath = ftLightmaps.GetRuntimePath(); gstorage = AssetDatabase.LoadAssetAtPath(bakeryRuntimePath + "ftGlobalStorage.asset", typeof(ftGlobalStorage)) as ftGlobalStorage; 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 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 lmUVArrays2 = new List(); var lmUVArrays3 = new List(); var lmIndexArrays = new List>(); var lmIndexArrays2 = new List(); var lmLocalToGlobalIndices = new List>(); vbtraceTexPosNormalArray = new List(); vbtraceTexUVArray = new List(); sceneLodsUsed = 0; // Create temp path CreateSceneFolder(); // Init storages try { InitSceneStorage(data); } catch(Exception e) { ExportSceneError("Global storage init"); Debug.LogError("Exception caught: " + e.ToString()); throw; } // Create LMGroup for light probes if (ftRenderLightmap.lightProbeMode == ftRenderLightmap.LightProbeMode.L1 && renderTextures && !atlasOnly && ftRenderLightmap.hasAnyProbes) { var c = CreateLightProbeLMGroup(data); while(c.MoveNext()) yield return null; } if (ftRenderLightmap.hasAnyVolumes) { var c2 = CreateVolumeLMGroup(data); while(c2.MoveNext()) yield return null; } // wip var lmBounds = data.lmBounds; var storages = data.storages; var sceneToID = data.sceneToID; var groupList = data.groupList; var objToLodLevel = data.objToLodLevel; var objToLodLevelVisible = data.objToLodLevelVisible; var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objsToWriteIndices = data.objsToWriteIndices; var objsToWriteNames = data.objsToWriteNames; var objsToWriteUVOverride = data.objsToWriteUVOverride; var objsToWriteScaleOffset = data.objsToWriteScaleOffset; var objsToWriteVerticesUV = data.objsToWriteVerticesUV; var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2; var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW; var objsToWriteVerticesNormalW = data.objsToWriteVerticesNormalW; var objsToWriteVerticesTangentW = data.objsToWriteVerticesTangentW; var holderRect = data.holderRect; terrainObjectList = new List(); terrainObjectToActual = new List(); terrainObjectToHeightMap = new List(); terrainObjectToBounds = new List(); terrainObjectToBoundsUV = new List(); terrainObjectToFlags = new List(); terrainObjectToLMID = new List(); terrainObjectToHeightMips = new List>(); treeObjectList = 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 (!CheckForMultipleSceneStorages(obj, data)) yield break; if (ConvertUnityAreaLight(obj)) continue; ConvertTerrain(obj); } // Regather objects if new were added if (terrainObjectList.Count > 0 || treeObjectList.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; CalculateVertexCountForVertexGroups(data); CreateAutoAtlasLMGroups(data, renderTextures, atlasOnly); TransformVertices(data, tangentSHLights); if (unwrapUVs) { var adata = new AdjustUVPaddingData(); CalculateUVPadding(data, adata); ResetPaddingStorageData(data); StoreNewUVPadding(data, adata); if (!ValidatePaddingImmutability(adata)) yield break; if (CheckUnwrapError()) yield break; // Reimport assets with adjusted padding if (modifyLightmapStorage) { if (!ReimportModifiedAssets(adata)) yield break; TransformModifiedAssets(data, adata, tangentSHLights); } } else if (forceDisableUnwrapUVs) { var adata = new AdjustUVPaddingData(); ResetPaddingStorageData(data); if (!ClearUVPadding(data, adata)) yield break; if (CheckUnwrapError()) yield break; TransformModifiedAssets(data, adata, tangentSHLights); } CalculateHolderUVBounds(data); CalculateAutoAtlasInitResolution(data); if (!PackAtlases(data)) yield break; if (atlasPacker == ftGlobalStorage.AtlasPacker.Default) { NormalizeAutoAtlases(data); JoinAutoAtlases(data); } InitSceneStorage2(data); //TransformVertices(data); // shouldn't be necessary // Update objToWriteGroups because of autoAtlas if (autoAtlas) { for(int i=0; i(); atlasOnlySize = new List(); atlasOnlyID = new List(); atlasOnlyScaleOffset = new List(); var emptyVec4 = new Vector4(1,1,0,0); Rect rc = new Rect(); for(int i=0; i()); atlasOnlyScaleOffset.Add(scaleOffset); atlasOnlySize.Add(lmgroup == null ? 0 : lmgroup.resolution); atlasOnlyID.Add(lmgroup == null ? 0 : lmgroup.id); } yield break; } // Sort LMGroups so vertex groups are never first (because Unity assumes lightmap compression on LM0) for(int i=0; i(); for(int i=0; i(); 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 = 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) { Debug.Log("GI VRAM auto optimization ON: estimated usage " + (int)approxMem + " > " + SystemInfo.graphicsMemorySize); ftRenderLightmap.giLodModeEnabled = true; } else { Debug.Log("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 (exportTerrainAsHeightmap) { for(int i=0; i 0) { terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; for(int i=0; i 0) uvgbGlobalFlags |= UVGBFLAG_TERRAIN; SetUVGBFlags(uvgbGlobalFlags); for(int i=0; i 8192) Debug.LogWarning("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 0) { //terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; /*for(int i=0; i(); for(int i=0; i(); var m = GetSharedMesh(mr); var inds = objsToWriteIndices[i]; // Write LMID, mesh and surface definition int id = exportLMID(flmid, obj, lmgroup); exportMesh(fmesh, m); exportSurfs(fsurf, inds, inds.Length);// m); int lodLevel; if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1; bool isTerrain = (exportTerrainAsHeightmap && obj.name == "__ExportTerrain"); // Write albedo IDs, collect alpha IDs, update LMGroup bounds if (id >= 0) { for(int k=0; k 1) alphaRef = 1; // allow same map instances with different threshold List texIDs; if (!alphaMap.TryGetValue(texPtr, out texIDs)) { alphaMap[texPtr] = texIDs = new List(); lmAlphaList.Add(texPtr); 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 = obj.GetComponent(); 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) 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()); storages[sceneID].bakedIDs.Add(CorrectLMGroupID(id, lmgroup, groupList)); storages[sceneID].bakedScaleOffset.Add(scaleOffset); storages[sceneID].bakedVertexOffset.Add(vertexBake ? (lmgroup.vertexCounter - tformedPos.Length) : -1); storages[sceneID].bakedVertexColorMesh.Add(null); } } objsToWriteScaleOffset.Add(scaleOffset); } } // Generate LOD UVs if (ftRenderLightmap.giLodModeEnabled) { for(int s=0; s lmgroup.resolution) { Debug.LogWarning("Not generating LOD UVs for " + lmgroup.name + ", because there are too many UV islands"); uvrUnload(); continue; } Debug.Log("Min LOD resolution for " + lmgroup.name + " is " + minLodResolution); for(int s=0; s= 0 && (int)u > id*10) { Debug.LogError("Float overflow (GI LOD)"); } lmUVArrays2[i][k * 2] = u; } } } // Write vbTraceTex int numTraceVerts = vbtraceTexUVArray.Count/2; for(int i=0; i 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; ftUVGBufferGen.StartUVGBuffer(res, hasEmissive, bakeWithNormalMaps); for(int i=0; i()) continue; var bakedMesh = GetSharedMeshBaked(obj); ftUVGBufferGen.RenderUVGBuffer(bakedMesh, obj.GetComponent(), objsToWriteScaleOffset[i], obj.transform.localToWorldMatrix, vertexBake, objsToWriteUVOverride[i], bakeWithNormalMaps && !exportTerrainAsHeightmap && obj.name == "__ExportTerrain"); } ftUVGBufferGen.EndUVGBuffer(); var albedo = ftUVGBufferGen.texAlbedo; var emissive = ftUVGBufferGen.texEmissive; var normal = ftUVGBufferGen.texNormal; 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); SaveGBufferMap(albedo.GetNativeTexturePtr(), scenePath + "/uvalbedo_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); //if (g==2) storage.debugTex = emissive; yield return null; if (hasEmissive) { SaveGBufferMap(emissive.GetNativeTexturePtr(), scenePath + "/uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); yield return null; if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); } if (bakeWithNormalMaps) { SaveGBufferMap(normal.GetNativeTexturePtr(), scenePath + "/uvnormal_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(5); yield return null; } } } ProgressBarShow(exportShaderColors ? "Exporting scene - alpha buffer..." : "Exporting scene - UV GBuffer and alpha buffer...", 0.55f); if (userCanceled) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } yield return null; InitShaders(); LoadScene(scenePath); // Force load textures to VRAM var forceRt = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); var forceTex = new Texture2D(1, 1, TextureFormat.ARGB32, false, false); if (!exportShaderColors) { for(int i=0; i 0) { //for(int i=0; i