#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<GameObject> terrainObjectList; static List<Terrain> terrainObjectToActual; static List<Texture> terrainObjectToHeightMap; static IntPtr[] terrainObjectToHeightMapPtr; static List<float> terrainObjectToBounds; static List<int> terrainObjectToLMID; static List<float> terrainObjectToBoundsUV; static List<int> terrainObjectToFlags; static List<List<float[]>> terrainObjectToHeightMips; //static List<List<Vector3[]>> terrainObjectToNormalMips; //static List<Vector3[]> terrainObjectToNormalMip0; static List<GameObject> temporaryAreaLightMeshList; static List<BakeryLightMesh> temporaryAreaLightMeshList2; static List<GameObject> treeObjectList; static Dictionary<GameObject,int> cmp_objToLodLevel; static Dictionary<GameObject, float> cmp_holderObjArea; public static List<float> vbtraceTexPosNormalArray; // global vbTraceTex.bin positions/normals public static List<float> vbtraceTexUVArray; // global vbTraceTex.bin UVs public static float[] vbtraceTexUVArrayLOD; // global vbTraceTex.bin LOD UVs public static List<Renderer> atlasOnlyObj; public static List<Vector4> atlasOnlyScaleOffset; public static List<int> atlasOnlySize; public static List<int> 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<vertices.Length;i++) { Vector3 pos = vertices[i];//t.(vertices[i]); f.Write(pos.x); f.Write(pos.y); f.Write(pos.z); } } static void exportVBTrace(BufferedBinaryWriterFloat f, Mesh m, Vector3[] vertices, Vector3[] normals) { for(int i=0;i<vertices.Length;i++) { Vector3 pos = vertices[i];//t.(vertices[i]); f.Write(pos.x); f.Write(pos.y); f.Write(pos.z); Vector3 normal = normals[i];//t.TransformDirection(normals[i]); f.Write(normal.x); f.Write(normal.y); f.Write(normal.z); } } static BakeryLightmapGroup GetLMGroupFromObjectExplicit(GameObject obj, ExportSceneData data) { lmgroupHolder = null; var lmgroupSelector = obj.GetComponent<BakeryLightmapGroupSelector>(); // 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<BakeryLightmapGroupSelector>(); t2 = t2.parent; } } BakeryLightmapGroup lmgroup = null; if (lmgroupSelector != null) { lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup; lmgroupHolder = lmgroupSelector.gameObject; var so = new SerializedObject(obj.GetComponent<Renderer>()); 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<BakeryLightmapGroupSelector>(); // 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<BakeryLightmapGroupSelector>(); 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<Renderer>(); 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<BakeryLightmapGroupSelector>(); // 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<BakeryLightmapGroupSelector>(); 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<Renderer>(); 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<float> arrPosNormal, List<float> arrUV, Vector3[] vertices, Vector3[] normals, Vector2[] uv2, int lmid, bool vertexBake, GameObject obj) { for(int i=0;i<vertices.Length;i++) { Vector3 pos = vertices[i];//t.(vertices[i]); arrPosNormal.Add(pos.x); arrPosNormal.Add(pos.y); arrPosNormal.Add(pos.z); Vector3 normal = normals[i];//t.TransformDirection(normals[i]); arrPosNormal.Add(normal.x); arrPosNormal.Add(normal.y); arrPosNormal.Add(normal.z); float u = 0; float v = 0; if (lmid < 0) { //u = lmid * 10; if (uv2.Length>0) { 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;i<vertCount;i++) { f.Write(0.0f); f.Write(0.0f); } } else { for(int i=0;i<vertCount;i++) { f.Write(uvs[i].x); f.Write(uvs[i].y); } } } static void exportVBBasic(BinaryWriter f, Transform t, Mesh m, Vector3[] vertices, Vector3[] normals, Vector2[] uv2) { for(int i=0;i<vertices.Length;i++) { Vector3 pos = vertices[i];//t.(vertices[i]); f.Write(pos.x); f.Write(pos.y); f.Write(pos.z); Vector3 normal = normals[i];//t.TransformDirection(normals[i]); f.Write(normal.x); f.Write(normal.y); f.Write(normal.z); if (uv2.Length>0) { 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<vertices.Length;i++) { Vector3 pos = vertices[i];//t.(vertices[i]); f.Write(pos.x); f.Write(pos.y); f.Write(pos.z); Vector3 normal = normals[i];//t.TransformDirection(normals[i]); f.Write(normal.x); f.Write(normal.y); f.Write(normal.z); if (tangents == null) { f.Write(0.0f); f.Write(0.0f); f.Write(0.0f); f.Write(0.0f); } else { Vector4 tangent = tangents[i]; f.Write(tangent.x); f.Write(tangent.y); f.Write(tangent.z); f.Write(tangent.w); } if (hasUV) { f.Write(uv[i].x); f.Write(uv[i].y); } else { f.Write(0.0f); f.Write(0.0f); } if (hasUV2) { f.Write(uv2[i].x); f.Write(uv2[i].y); } else { f.Write(0.0f); f.Write(0.0f); } } } static int exportIB(BufferedBinaryWriterInt f, int[] indices, bool isFlipped, bool is32Bit, int offset, BinaryWriter falphaid, ushort alphaID) { //var indices = m.GetTriangles(i); for(int j=0;j<indices.Length;j+=3) { if (!isFlipped) { if (is32Bit) { f.Write(indices[j] + offset); f.Write(indices[j+1] + offset); f.Write(indices[j+2] + offset); } else { f.Write(indices[j]); f.Write(indices[j+1]); f.Write(indices[j+2]); } } else { if (is32Bit) { f.Write(indices[j+2] + offset); f.Write(indices[j+1] + offset); f.Write(indices[j] + offset); } else { f.Write(indices[j+2]); f.Write(indices[j+1]); f.Write(indices[j]); } } if (falphaid!=null) falphaid.Write(alphaID); } return indices.Length; } // returns mesh area if requested static void exportIB32(List<int> indicesOpaque, List<int> indicesTransparent, List<int> 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<indices.Length;j+=3) { if (!isFlipped) { indexA = indices[j]; indexB = indices[j + 1]; indexC = indices[j + 2]; } else { indexA = indices[j + 2]; indexB = indices[j + 1]; indexC = indices[j]; } indicesOut.Add(indexA + offset); indicesOut.Add(indexB + offset); indicesOut.Add(indexC + offset); if (indicesLMID != null) { indicesLMID.Add(indexA + indexOffsetLMID); indicesLMID.Add(indexB + indexOffsetLMID); indicesLMID.Add(indexC + indexOffsetLMID); } if (alphaID != 0xFFFF) falphaid.Write(alphaID); } } static void exportSurfs(BinaryWriter f, int[][] indices, int subMeshCount)// Mesh m) { int offset = ioffset; for(int i=0;i<subMeshCount;i++) { int size = indices[i].Length;//m.GetTriangles(i).Length; f.Write(offset); f.Write(size); offset += size;// * 2; } soffset += subMeshCount; } static void exportMesh(BinaryWriter f, Mesh m) { f.Write(voffset); f.Write(soffset); f.Write((ushort)m.subMeshCount); f.Write((ushort)0); } static int exportLMID(BinaryWriter f, GameObject obj, BakeryLightmapGroup lmgroup) { var areaLight = obj.GetComponent<BakeryLightMesh>(); 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<vlength; i++) { int x = (i + voffset) % atlasTexSize; int y = (i + voffset) / atlasTexSize; uvs[i] = new Vector2(x * mul + add, y * mul + add);// - 1.0f); } return uvs; } public static Mesh GetSharedMesh(Renderer mr) { if (mr == null) return null; var mrSkin = mr as SkinnedMeshRenderer; var mf = mr.gameObject.GetComponent<MeshFilter>(); return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMeshBaked(GameObject obj) { var mrSkin = obj.GetComponent<SkinnedMeshRenderer>(); if (mrSkin != null) { var baked = new Mesh(); mrSkin.BakeMesh(baked); return baked; } var mf = obj.GetComponent<MeshFilter>(); return (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMesh(GameObject obj) { var mrSkin = obj.GetComponent<SkinnedMeshRenderer>(); var mf = obj.GetComponent<MeshFilter>(); return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null); } public static Mesh GetSharedMeshSkinned(GameObject obj, out bool isSkin) { var mrSkin = obj.GetComponent<SkinnedMeshRenderer>(); 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<MeshFilter>(); 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<BakeryPackAsSingleSquare>() != 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<storages.Length; i++) { var store = storages[i]; if (store == null) continue; var index = store.assetList.IndexOf(path); if (index < 0) continue; if (store.uvOverlapAssetList[index] == 0) { return false; } else { return true; } }*/ var index = store.assetList.IndexOf(path); if (index >= 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<storages.Length; i++) { var store = storages[i]; var index = store.assetList.IndexOf(path); if (index < 0) continue; if (store.uvOverlapAssetList[index] == 0) { return false; } else { return true; } }*/ index = store.assetList.IndexOf(path); if (index >= 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<temporaryAreaLightMeshList.Count; i++) { if (temporaryAreaLightMeshList[i] != null) { //var mr = temporaryAreaLightMeshList[i].GetComponent<Renderer>(); //if (mr != null) DestroyImmediate(mr); //var mf = temporaryAreaLightMeshList[i].GetComponent<MeshFilter>(); //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<terrainObjectList.Count; i++) { if (terrainObjectList[i] != null) DestroyImmediate(terrainObjectList[i]); } terrainObjectList = null; } if (treeObjectList != null) { for(int i=0; i<treeObjectList.Count; i++) { if (treeObjectList[i] != null) DestroyImmediate(treeObjectList[i]); } treeObjectList = null; } //if (isError) { FreeTemporaryAreaLightMeshes(); } } //progressBarEnabled = false; ftRenderLightmap.simpleProgressBarEnd(); } void OnInspectorUpdate() { Repaint(); } static void CloseAllFiles() { if (fscene != null) fscene.Close(); if (fmesh != null) fmesh.Close(); if (flmid != null) flmid.Close(); if (fseamfix != null) fseamfix.Close(); if (fsurf != null) fsurf.Close(); if (fmatid != null) fmatid.Close(); if (fmatide != null) fmatide.Close(); if (fmatideb != null) fmatideb.Close(); if (fmatidh != null) fmatidh.Close(); if (falphaid != null) falphaid.Close(); if (fvbfull != null) fvbfull.Close(); if (fvbtrace != null) fvbtrace.Close(); if (fvbtraceTex != null) fvbtraceTex.Close(); if (fvbtraceUV0 != null) fvbtraceUV0.Close(); if (fib != null) fib.Close(); if (fib32 != null) fib32.Close(); if (fhmaps != null) fhmaps.Close(); if (fib32lod != null) { for(int i=0; i<fib32lod.Length; i++) fib32lod[i].Close(); } if (falphaidlod != null) { for(int i=0; i<falphaidlod.Length; i++) falphaidlod[i].Close(); } fvbfull = fvbtrace = fvbtraceTex = fvbtraceUV0 = null; fib = null; fscene = fmesh = flmid = fsurf = fmatid = fmatide = fmatideb = falphaid = fib32 = fseamfix = fmatidh = fhmaps = null; fib32lod = falphaidlod = null; } static bool CheckUnwrapError() { if (ftModelPostProcessor.unwrapError) { DebugLogError("Unity failed unwrapping some models. See console for details. Last failed model: " + ftModelPostProcessor.lastUnwrapErrorAsset+"\nModel will now be reverted to original state."); int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(ftModelPostProcessor.lastUnwrapErrorAsset); if (mstoreIndex < 0) { Debug.LogError("Failed to find failed asset?"); } else { gstorage.ClearAssetModifications(mstoreIndex); } ftModelPostProcessor.unwrapError = false; CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); return true; } return false; } static Mesh BuildAreaLightMesh(Light areaLight) { var mesh = new Mesh(); var verts = new Vector3[4]; Vector2 areaSize = ftLightMeshInspector.GetAreaLightSize(areaLight); verts[0] = new Vector3(-0.5f * areaSize.x, -0.5f * areaSize.y, 0); verts[1] = new Vector3(0.5f * areaSize.x, -0.5f * areaSize.y, 0); verts[2] = new Vector3(0.5f * areaSize.x, 0.5f * areaSize.y, 0); verts[3] = new Vector3(-0.5f * areaSize.x, 0.5f * areaSize.y, 0); var uvs = new Vector2[4]; uvs[0] = new Vector2(0, 0); uvs[1] = new Vector2(1, 0); uvs[2] = new Vector2(1, 1); uvs[3] = new Vector2(0, 1); var indices = new int[6]; indices[0] = 0; indices[1] = 1; indices[2] = 2; indices[3] = 0; indices[4] = 2; indices[5] = 3; var normals = new Vector3[4]; var n = Vector3.forward;// -areaLight.transform.forward; // transformation will be applied later for(int i=0; i<4; i++) normals[i] = n; mesh.vertices = verts; mesh.triangles = indices; mesh.normals = normals; mesh.uv = uvs; return mesh; } static bool CheckForTangentSHLights() { var All2 = FindObjectsOfType(typeof(BakerySkyLight)); for(int i=0; i<All2.Length; i++) { var obj = All2[i] as BakerySkyLight; if (!obj.enabled) continue; if (!obj.gameObject.activeInHierarchy) continue; if (obj.tangentSH) { return true; } } return false; } public static void CreateSceneFolder() { if (scenePath == "" || !Directory.Exists(scenePath)) { // Default scene path is TEMP/frender var tempDir = System.Environment.GetEnvironmentVariable("TEMP", System.EnvironmentVariableTarget.Process); scenePath = tempDir + "\\frender"; if (!Directory.Exists(scenePath)) Directory.CreateDirectory(scenePath); } } static int CorrectLMGroupID(int id, BakeryLightmapGroup lmgroup, List<BakeryLightmapGroup> groupList) { id = id < 0 ? -1 : id; if (lmgroup != null && lmgroup.parentName != null && lmgroup.parentName.Length > 0 && lmgroup.parentName != "|") { for(int g=0; g<groupList.Count; g++) { if (groupList[g].name == lmgroup.parentName) { id = g; break; } } } return id; } static void InitSceneStorage(ExportSceneData data) { var storages = data.storages; var sceneToID = data.sceneToID; bool first = true; for(int i=0; i<storages.Length; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; var gg = ftLightmaps.FindInScene("!ftraceLightmaps", scene); storages[i] = gg.GetComponent<ftLightmapsStorage>(); if (modifyLightmapStorage) { /* storages[i].bakedRenderers = new List<Renderer>(); storages[i].bakedIDs = new List<int>(); storages[i].bakedScaleOffset = new List<Vector4>(); storages[i].bakedVertexOffset = new List<int>(); storages[i].bakedVertexColorMesh = new List<Mesh>(); storages[i].bakedRenderersTerrain = new List<Terrain>(); storages[i].bakedIDsTerrain = new List<int>(); storages[i].bakedScaleOffsetTerrain = new List<Vector4>(); */ storages[i].hasEmissive = new List<bool>(); storages[i].lmGroupLODResFlags = null; storages[i].lmGroupMinLOD = null; storages[i].lmGroupLODMatrix = null; storages[i].nonBakedRenderers = new List<Renderer>(); } if (first) { data.firstNonNullStorage = i; first = false; } storages[i].implicitGroups = new List<UnityEngine.Object>(); storages[i].implicitGroupedObjects = new List<GameObject>(); sceneToID[scene] = i; } //var go = GameObject.Find("!ftraceLightmaps"); //data.settingsStorage = go.GetComponent<ftLightmapsStorage>(); } static void InitSceneStorage2(ExportSceneData data) { var storages = data.storages; for(int i=0; i<storages.Length; i++) { var scene = SceneManager.GetSceneAt(i); if (!scene.isLoaded) continue; if (modifyLightmapStorage) { storages[i].bakedRenderers = new List<Renderer>(); storages[i].bakedIDs = new List<int>(); storages[i].bakedScaleOffset = new List<Vector4>(); storages[i].bakedVertexOffset = new List<int>(); storages[i].bakedVertexColorMesh = new List<Mesh>(); storages[i].bakedRenderersTerrain = new List<Terrain>(); storages[i].bakedIDsTerrain = new List<int>(); storages[i].bakedScaleOffsetTerrain = new List<Vector4>(); } } } 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<probes.count; i++) { int x = i % atlasTexSize; int y = i / atlasTexSize; int index = y * atlasTexSize + x; uvpos[index * 4] = positions[i].x; uvpos[index * 4 + 1] = positions[i].y; uvpos[index * 4 + 2] = positions[i].z; uvpos[index * 4 + 3] = 1.0f; uvnormal[index * 4 + 1] = 255; uvnormal[index * 4 + 3] = 255; } var posFile = new byte[128 + uvpos.Length * 4]; System.Buffer.BlockCopy(ftDDS.ddsHeaderFloat4, 0, posFile, 0, 128); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 12, 4); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 16, 4); System.Buffer.BlockCopy(uvpos, 0, posFile, 128, uvpos.Length * 4); SaveGBufferMapFromRAM(posFile, posFile.Length, scenePath + "/uvpos_probes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(8); yield return null; var posNormal = new byte[128 + uvnormal.Length]; System.Buffer.BlockCopy(ftDDS.ddsHeaderRGBA8, 0, posNormal, 0, 128); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 12, 4); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 16, 4); System.Buffer.BlockCopy(uvnormal, 0, posNormal, 128, uvnormal.Length); SaveGBufferMapFromRAM(posNormal, posNormal.Length, scenePath + "/uvnormal_probes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(8); yield return null; lightProbeLMGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>(); 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<vols.Length; v++) { numTotalProbes += vols[v].resolutionX * vols[v].resolutionY * vols[v].resolutionZ; } var positions = new Vector3[numTotalProbes]; int i = 0; Vector3 halfVoxelSize = Vector3.one; for(int v=0; v<vols.Length; v++) { var vol = vols[v]; int rx = vol.resolutionX; int ry = vol.resolutionY; int rz = vol.resolutionZ; var bmin = vol.bounds.min; var bmax = vol.bounds.max; halfVoxelSize = bmax - bmin; halfVoxelSize = new Vector3(halfVoxelSize.x/rx, halfVoxelSize.y/ry, halfVoxelSize.z/rz) * 0.5f; float lx, ly, lz; for(int z=0; z<rz; z++) { lz = Mathf.Lerp(bmin.z, bmax.z, z/(float)rz) + halfVoxelSize.z; for(int y=0; y<ry; y++) { ly = Mathf.Lerp(bmin.y, bmax.y, y/(float)ry) + halfVoxelSize.y; for(int x=0; x<rx; x++) { lx = Mathf.Lerp(bmin.x, bmax.x, x/(float)rx) + halfVoxelSize.x; positions[i] = new Vector3(lx, ly, lz); i++; } } } } int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)numTotalProbes)); 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(i=0; i<numTotalProbes; i++) { int x = i % atlasTexSize; int y = i / atlasTexSize; int index = y * atlasTexSize + x; uvpos[index * 4] = positions[i].x; uvpos[index * 4 + 1] = positions[i].y; uvpos[index * 4 + 2] = positions[i].z; uvpos[index * 4 + 3] = 1.0f; uvnormal[index * 4 + 1] = 255; uvnormal[index * 4 + 3] = 255; } var posFile = new byte[128 + uvpos.Length * 4]; System.Buffer.BlockCopy(ftDDS.ddsHeaderFloat4, 0, posFile, 0, 128); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 12, 4); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 16, 4); System.Buffer.BlockCopy(uvpos, 0, posFile, 128, uvpos.Length * 4); SaveGBufferMapFromRAM(posFile, posFile.Length, scenePath + "/uvpos_volumes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(8); yield return null; var posNormal = new byte[128 + uvnormal.Length]; System.Buffer.BlockCopy(ftDDS.ddsHeaderRGBA8, 0, posNormal, 0, 128); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 12, 4); System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 16, 4); System.Buffer.BlockCopy(uvnormal, 0, posNormal, 128, uvnormal.Length); SaveGBufferMapFromRAM(posNormal, posNormal.Length, scenePath + "/uvnormal_volumes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer); GL.IssuePluginEvent(8); yield return null; volumeLMGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>(); 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<BakeryLightmapGroupSelector>(FindObjectsOfType(typeof(BakeryLightmapGroupSelector)) as BakeryLightmapGroupSelector[]); for(int i=0; i<groupSelectors.Count; i++) { var lmgroup = groupSelectors[i].lmgroupAsset as BakeryLightmapGroup; if (lmgroup == null) continue; if (!groupList.Contains(lmgroup)) { lmgroup.id = data.lmid; lmgroup.sceneLodLevel = -1; lmgroup.sceneName = ""; groupList.Add(lmgroup); lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0))); data.lmid++; } EditorUtility.SetDirty(lmgroup); } } static bool CheckForMultipleSceneStorages(GameObject obj, ExportSceneData data) { var sceneHasStorage = data.sceneHasStorage; // Check for storage count in each scene if (obj.name == "!ftraceLightmaps") { if (!sceneHasStorage.ContainsKey(obj.scene)) { sceneHasStorage[obj.scene] = true; } else { return ExportSceneValidationMessage("Scene " + obj.scene.name + " has multiple lightmap storage objects. This is not currently supported. Make sure you don't bake a scene with already baked prefabs."); } } return true; } static void ConvertTerrain(GameObject obj) { var terr = obj.GetComponent<Terrain>(); 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<BakeryLightmapGroupSelector>(); if (expGroup != null) { var expGroup2 = terrParent.AddComponent<BakeryLightmapGroupSelector>(); 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<Camera>(); 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<res; y++) { for(int x=0; x<res; x++) { height = heightmap[x,y]; if (height > maxHeight) maxHeight = height; } } maxHeight = Mathf.Max(maxHeight, 0.0001f); float invMaxHeight = 1.0f / maxHeight; for(int y=0; y<res; y++) { for(int x=0; x<res; x++) { heightmap[x,y] *= invMaxHeight; } } float aabbHeight = maxHeight * scaleY; //Debug.Log("aabbHeight: " + aabbHeight); // First mip is the real heightmap System.Buffer.BlockCopy(heightmap, 0, bytes, 0, bytes.Length); hmap.Write(bytes); var htex = new Texture2D(res, res, TextureFormat.RFloat, false, true); htex.LoadRawTextureData(bytes); htex.Apply(); terrainObjectToHeightMap.Add(htex); // min terrainObjectToBounds.Add(obj.transform.position.x); terrainObjectToBounds.Add(obj.transform.position.y); terrainObjectToBounds.Add(obj.transform.position.z); // max terrainObjectToBounds.Add(obj.transform.position.x + tdata.size.x); terrainObjectToBounds.Add(obj.transform.position.y + aabbHeight); terrainObjectToBounds.Add(obj.transform.position.z + tdata.size.z); // filled later terrainObjectToLMID.Add(0); terrainObjectToBoundsUV.Add(0); terrainObjectToBoundsUV.Add(0); terrainObjectToBoundsUV.Add(0); terrainObjectToBoundsUV.Add(0); terrainObjectToFlags.Add(terr.castShadows ? 1 : 0); // Second mip is max() of 3x3 corners float[] floats = null; float[] floatsPrev = null; float[] floatsTmp; int mipCount = 1; int mipRes = res / 2; //int prevRes = res; float h00, h10, h01, h11; /*Vector3 n00, n10, n01, n11; Vector3[] normals = null; Vector3[] normalsPrev = null; var origNormals = new Vector3[res * res]; for(int y=0; y<res; y++) { for(int x=0; x<res; x++) { origNormals[y*res+x] = data.GetInterpolatedNormal(x / (float)res, y / (float)res); } }*/ int terrIndex = terrainObjectToHeightMap.Count - 1;//terrainObjectToNormalMip0.Count; //terrainObjectToNormalMip0.Add(origNormals); //normalsPrev = origNormals; terrainObjectToHeightMips.Add(new List<float[]>()); //terrainObjectToNormalMips.Add(new List<Vector3[]>()); if (mipRes > 0) { floats = new float[mipRes * mipRes]; //normals = new Vector3[mipRes * mipRes]; for(int y=0; y<mipRes; y++) { for(int x=0; x<mipRes; x++) { /*h00 = heightmap[y*2,x*2]; h10 = heightmap[y*2,x*2+1]; h01 = heightmap[y*2+1,x*2]; h11 = heightmap[y*2+1,x*2+1]; height = h00 > 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<mipRes; y++) { for(int x=0; x<mipRes; x++) { h00 = floatsPrev[y*2 * mipRes*2 + x*2]; h10 = floatsPrev[y*2 * mipRes*2 + x*2+1]; h01 = floatsPrev[(y*2+1) * mipRes*2 + x*2]; h11 = floatsPrev[(y*2+1) * mipRes*2 + x*2+1]; height = h00 > 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<MeshFilter>(); var mr = terrGO.AddComponent<MeshRenderer>(); 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<numPatches; patchX++) { for (int patchY=0; patchY<numPatches; patchY++) { int patchResX = patchX < numPatches-1 ? patchRes : (res - patchRes*(numPatches-1)); int patchResY = patchY < numPatches-1 ? patchRes : (res - patchRes*(numPatches-1)); if (patchX < numPatches-1) patchResX += 1; if (patchY < numPatches-1) patchResY += 1; numVerts = patchResX * patchResY; var posOffset = gposOffset + new Vector3(patchX*patchRes*scaleX, 0, patchY*patchRes*scaleZ); var positions = new Vector3[numVerts]; var uvs = new Vector2[numVerts]; var normals = new Vector3[numVerts]; var indices = new int[(patchResX-1) * (patchResY-1) * 2 * 3]; int vertOffset = 0; int indexOffset = 0; for (int x=0;x<patchResX;x++) { for (int y=0;y<patchResY;y++) { int gx = x + patchX * patchRes; int gy = y + patchY * patchRes; int index = x * patchResY + y; float height = heightmap[gy,gx]; positions[index] = new Vector3(x * scaleX, height * scaleY, y * scaleZ) + posOffset; uvs[index] = new Vector2(gx * uvscale.x + uvoffset.x, gy * uvscale.y + uvoffset.y); normals[index] = tdata.GetInterpolatedNormal(gx / (float)res, gy / (float)res); if (x < patchResX-1 && y < patchResY-1) { indices[indexOffset] = vertOffset + patchResY + 1; indices[indexOffset + 1] = vertOffset + patchResY; indices[indexOffset + 2] = vertOffset; indices[indexOffset + 3] = vertOffset + 1; indices[indexOffset + 4] = vertOffset + patchResY + 1; indices[indexOffset + 5] = vertOffset; indexOffset += 6; } vertOffset++; } } var mesh = new Mesh(); mesh.vertices = positions; mesh.triangles = indices; mesh.normals = normals; mesh.uv = uvs; mesh.uv2 = uvs; var terrGO = new GameObject(); terrGO.name = "__ExportTerrain"; GameObjectUtility.SetStaticEditorFlags(terrGO, StaticEditorFlags.ContributeGI); terrGO.transform.parent = terrParent.transform; var mf = terrGO.AddComponent<MeshFilter>(); var mr = terrGO.AddComponent<MeshRenderer>(); 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<LODGroup>(); if (lodGroup == null) { var renderers = newObj.GetComponentsInChildren<Renderer>(); for(int r=0; r<renderers.Length; r++) { GameObjectUtility.SetStaticEditorFlags(renderers[r].gameObject, StaticEditorFlags.ContributeGI); var s = new SerializedObject(renderers[r]); s.FindProperty("m_ScaleInLightmap").floatValue = 0; s.ApplyModifiedProperties(); } } else { var lods = lodGroup.GetLODs(); for (int tl = 0; tl < lods.Length; tl++) { for (int h = 0; h < lods[tl].renderers.Length; h++) { GameObjectUtility.SetStaticEditorFlags(lods[tl].renderers[h].gameObject, tl==0 ? StaticEditorFlags.ContributeGI : 0); if (tl == 0) { var s = new SerializedObject(lods[tl].renderers[h]); s.FindProperty("m_ScaleInLightmap").floatValue = 0; s.ApplyModifiedProperties(); } } } } var xform = newObj.transform; xform.localScale = new Vector3(trees[t].widthScale, trees[t].heightScale, trees[t].widthScale); } } } static bool ConvertUnityAreaLight(GameObject obj) { // Add temporary meshes to area lights var areaLightMesh = obj.GetComponent<BakeryLightMesh>(); if (areaLightMesh != null) { var areaLight = obj.GetComponent<Light>(); var mr = obj.GetComponent<Renderer>(); var mf = obj.GetComponent<MeshFilter>(); 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<MeshFilter>(); mf.sharedMesh = BuildAreaLightMesh(areaLight); mr = areaObj.AddComponent<MeshRenderer>(); 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<maxSceneLodLevels; i++) sceneLodUsed[i] = -1; var lodGroups = Resources.FindObjectsOfTypeAll(typeof(LODGroup)); var lodLevelsInLodGroup = new List<int>[lodGroups.Length]; var localLodLevelsInLodGroup = new List<int>[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<lods.Length; i++) { var lodRenderers = lods[i].renderers; if (lodRenderers.Length == 0) continue; bool lightmappedLOD = false; for(int j=0; j<lodRenderers.Length; j++) { var r = lodRenderers[j]; if (r == null) continue; if (!r.enabled) continue; if (!r.gameObject.activeInHierarchy) continue; if ((r.gameObject.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects if (r.gameObject.tag == "EditorOnly") continue; // skip temp objects if ((GameObjectUtility.GetStaticEditorFlags(r.gameObject) & StaticEditorFlags.ContributeGI) == 0) continue; // skip dynamic var mr = r.gameObject.GetComponent<Renderer>(); var sharedMesh = GetSharedMesh(mr); if (mr == null || sharedMesh == null) continue; // must have visible mesh var mrEnabled = mr.enabled || r.gameObject.GetComponent<BakeryAlwaysRender>() != 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<int>(); localLodLevelsInLodGroup[lcounter] = new List<int>(); } if (lodLevelsInLodGroup[lcounter].IndexOf(newLodLevel) < 0) { lodLevelsInLodGroup[lcounter].Add(newLodLevel); localLodLevelsInLodGroup[lcounter].Add(i); } for(int j=0; j<lodRenderers.Length; j++) { var r = lodRenderers[j]; if (r == null) continue; int existingLodLevel = -1; if (objToLodLevel.ContainsKey(r.gameObject)) existingLodLevel = objToLodLevel[r.gameObject]; if (existingLodLevel < newLodLevel) { objToLodLevel[r.gameObject] = existingLodLevel < 0 ? newLodLevel : existingLodLevel; // set to lowest LOD // Collect LOD levels where this object is visible List<int> visList; if (!objToLodLevelVisible.TryGetValue(r.gameObject, out visList)) objToLodLevelVisible[r.gameObject] = visList = new List<int>(); visList.Add(newLodLevel); } } } } // Sort scene LOD levels int counter = 0; var unsortedLodToSortedLod = new int[maxSceneLodLevels]; for(int i=0; i<maxSceneLodLevels; i++) { int unsorted = sceneLodUsed[i]; if (unsorted >= 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<visList.Count; j++) { visList[j] = unsortedLodToSortedLod[visList[j]]; } } for(int i=0; i<lodLevelsInLodGroup.Length; i++) { if (lodLevelsInLodGroup[i] == null) continue; var levels = lodLevelsInLodGroup[i]; for(int j=0; j<levels.Count; j++) { levels[j] = unsortedLodToSortedLod[levels[j]]; } } // Fill LOD gaps for(int i=0; i<lodLevelsInLodGroup.Length; i++) { if (lodLevelsInLodGroup[i] == null) continue; var levels = lodLevelsInLodGroup[i]; var localLevels = localLodLevelsInLodGroup[i]; var lgroup = lodGroups[i] as LODGroup; var lods = lgroup.GetLODs(); for(int j=0; j<levels.Count; j++) { int level = levels[j]; int localLevel = localLevels[j]; int nextLevel = (j == levels.Count-1) ? (sceneLodsUsed-1) : levels[j+1]; if (nextLevel - level > 1) { var lodRenderers = lods[localLevel].renderers; for(int k=0; k<lodRenderers.Length; k++) { var r = lodRenderers[k]; if (r == null) continue; var visList = objToLodLevelVisible[r.gameObject]; for(int l=level+1; l<nextLevel; l++) { visList.Add(l); } } } } } Debug.Log("Scene LOD levels: " + sceneLodsUsed); // Init scene LOD index buffers data.indicesOpaqueLOD = new List<int>[sceneLodsUsed]; data.indicesTransparentLOD = new List<int>[sceneLodsUsed]; for(int i=0; i<sceneLodsUsed; i++) { data.indicesOpaqueLOD[i] = new List<int>(); data.indicesTransparentLOD[i] = new List<int>(); } // 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<BakeryLightMesh>(); 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<Renderer>(); 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<BakeryAlwaysRender>() != null; if (!mrEnabled && areaLight == null) continue; var so = new SerializedObject(obj.GetComponent<Renderer>()); 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<BakeryLightmapGroup>(); 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<usedUVs.Length; v++) { if (usedUVs[v].x < -0.0001f || usedUVs[v].x > 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<inds.Length; n++) inds[n] = sharedMesh.GetTriangles(n); objsToWriteIndices.Add(inds); } return true; } static void CalculateVertexCountForVertexGroups(ExportSceneData data) { var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; // Calculate total vertex count for vertex-baked groups for(int i=0; i<objsToWrite.Count; i++) { var lmgroup = objsToWriteGroup[i]; if (lmgroup == null || lmgroup.mode != BakeryLightmapGroup.ftLMGroupMode.Vertex) continue; var sharedMesh = GetSharedMesh(objsToWrite[i]); lmgroup.totalVertexCount += sharedMesh.vertexCount; } } static void CreateAutoAtlasLMGroups(ExportSceneData data, bool renderTextures, bool atlasOnly) { var objsToWrite = data.objsToWrite; var objsToWriteLightmapped = data.objsToWriteLightmapped; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objToLodLevel = data.objToLodLevel; var storages = data.storages; var sceneToID = data.sceneToID; var groupList = data.groupList; var lmBounds = data.lmBounds; var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; // Create implicit temp LMGroups. // If object is a part of prefab, and if UVs are not generated in Unity, group is only addded to the topmost object (aka holder). // Implicit groups are added on every static object without ftLMGroupSelector. // (Also init lmBounds and LMID as well) // if autoAtlas == false: new group for every holder. // if autoAtlas == true: single group for all holders (will be split later). for(int i=0; i<objsToWrite.Count; i++) { if (!objsToWriteLightmapped[i]) continue; // skip objects with scaleInLM == 0 if (objsToWriteGroup[i] != null) continue; // skip if already has lightmap assigned var obj = objsToWrite[i]; var holder = obj; // holder is object itself (packed individually) holder = TestPackAsSingleSquare(holder); var prefabParent = PrefabUtility.GetPrefabParent(obj) as GameObject; if (prefabParent != null) // object is part of prefab { // unity doesn't generate non-overlapping UVs for the whole model, only submeshes // // if importer == null, it's an actual prefab, not model <-- not really; importer points to mesh's prefab, not real // importer of a mesh is always model asset // importers of components never exist // at least check the prefab type var ptype = PrefabUtility.GetPrefabType(prefabParent); if (ptype == PrefabType.ModelPrefab) { var sharedMesh = GetSharedMesh(obj); var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sharedMesh)) as ModelImporter; if (importer != null && !ModelUVsOverlap(importer, gstorage)) { // 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; var assetG = PrefabUtility.GetPrefabParent(g) as GameObject; while(assetG != assetTopMost && g.transform.parent != null && assetG.transform.parent != null) { var g2 = g.transform.parent.gameObject; var assetG2 = assetG.transform.parent.gameObject; if (PrefabUtility.GetPrefabParent(g2) != assetG2) break; // avoid using parents which don't belong to this model g = g2; assetG = assetG2; } var sceneTopMost = g; holder = sceneTopMost; // holder is topmost model object (non-overlapped UVs) int lodLevel; if (objToLodLevel.TryGetValue(obj, out lodLevel)) holder = obj; // separated if used in LOD } } } else if (obj.name == "__ExportTerrain") { holder = obj.transform.parent.gameObject; // holder is terrain parent int lodLevel; if (objToLodLevel.TryGetValue(obj, out lodLevel)) holder = obj; // separated if used in LOD } if (!storages[sceneToID[holder.scene]].implicitGroupedObjects.Contains(holder)) { BakeryLightmapGroup newGroup; if (autoAtlas && autoAtlasGroups.Count > 0) { newGroup = autoAtlasGroups[0]; } else { newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>(); // 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<gholders.Count; g++) { if (gholders[g] == holder) { tempStorage.implicitGroupMap[holder] = grs[g]; break; } } } objsToWriteGroup[i] = (BakeryLightmapGroup)tempStorage.implicitGroupMap[holder]; objsToWriteHolder[i] = holder; } } static void TransformVertices(ExportSceneData data, bool tangentSHLights, int onlyID = -1) { var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW; var objsToWriteVerticesNormalW = data.objsToWriteVerticesNormalW; var objsToWriteVerticesTangentW = data.objsToWriteVerticesTangentW; int startIndex = 0; int endIndex = objsToWrite.Count-1; if (onlyID >= 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<vertices.Length; t++) { vertices[t].Scale(inverseWorldScale); } } for(int t=0; t<vertices.Length; t++) { objsToWriteVerticesPosW[i][t] = tform.TransformPoint(vertices[t]); } var normals = m.normals; objsToWriteVerticesNormalW[i] = new Vector3[vertices.Length]; var nbuff = objsToWriteVerticesNormalW[i]; var localScale = obj.transform.localScale; bool flipX = localScale.x < 0; bool flipY = localScale.y < 0; bool flipZ = localScale.z < 0; if (lmgroup != null && lmgroup.flipNormal) { flipX = !flipX; flipY = !flipY; flipZ = !flipZ; } for(int t=0; t<vertices.Length; t++) { if (normals.Length == 0) { nbuff[t] = Vector3.up; } else { nbuff[t] = normals[t]; if (flipX) nbuff[t].x *= -1; if (flipY) nbuff[t].y *= -1; if (flipZ) nbuff[t].z *= -1; nbuff[t] = tform.TransformDirection(nbuff[t]); } } if (NeedsTangents(lmgroup, tangentSHLights)) { var tangents = m.tangents; while(objsToWriteVerticesTangentW.Count <= i) objsToWriteVerticesTangentW.Add(null); objsToWriteVerticesTangentW[i] = new Vector4[vertices.Length]; var tbuff = objsToWriteVerticesTangentW[i]; Vector3 tangent = Vector3.zero; for(int t=0; t<vertices.Length; t++) { if (tangents.Length == 0) { tbuff[t] = Vector3.right; } else { tangent.Set(flipX ? -tangents[t].x : tangents[t].x, flipY ? -tangents[t].y : tangents[t].y, flipZ ? -tangents[t].z : tangents[t].z); tangent = tform.TransformDirection(tangent); tbuff[t] = new Vector4(tangent.x, tangent.y, tangent.z, tangents[t].w); } } } } } static void CalculateUVPadding(ExportSceneData data, AdjustUVPaddingData adata) { var meshToPaddingMap = adata.meshToPaddingMap; var meshToObjIDs = adata.meshToObjIDs; float smallestMapScale = 1; float colorScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.mainLightmapScale) * 6)); float maskScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.maskLightmapScale) * 6)); float dirScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.dirLightmapScale) * 6)); smallestMapScale = Mathf.Min(colorScale, maskScale); smallestMapScale = Mathf.Min(smallestMapScale, dirScale); var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW; var objsToWriteIndices = data.objsToWriteIndices; var objsToWriteHolder = data.objsToWriteHolder; // Calculate every implicit mesh area and convert to proper padding value var explicitGroupTotalArea = new Dictionary<int, float>(); var objsWithExplicitGroupPadding = new List<int>(); var objsWithExplicitGroupPaddingWidth = new List<float>(); for(int i=0; i<objsToWrite.Count; i++) { var lmgroup = objsToWriteGroup[i]; if (lmgroup == null) continue; var prefabParent = PrefabUtility.GetPrefabParent(objsToWrite[i]) as GameObject; if (prefabParent == null) continue; var sharedMesh = GetSharedMesh(objsToWrite[i]); var assetPath = AssetDatabase.GetAssetPath(sharedMesh); var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter; if (importer == null || !importer.generateSecondaryUV) continue; // user doesn't care much about UVs - adjust var m = sharedMesh; var vpos = objsToWriteVerticesPosW[i]; float area = 0; var inds = objsToWriteIndices[i]; for(int k=0;k<m.subMeshCount;k++) { var indices = inds[k];// m.GetTriangles(k); int indexA, indexB, indexC; for(int j=0;j<indices.Length;j+=3) { indexA = indices[j]; indexB = indices[j + 1]; indexC = indices[j + 2]; var v1 = vpos[indexA]; var v2 = vpos[indexB]; var v3 = vpos[indexC]; area += Vector3.Cross(v2 - v1, v3 - v1).magnitude; } } var so = new SerializedObject(objsToWrite[i].GetComponent<Renderer>()); 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<BakeryLightmapGroupSelector>(); 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<int> arr; if (!meshToObjIDs.TryGetValue(m, out arr)) { meshToObjIDs[m] = arr = new List<int>(); } if (!arr.Contains(i)) arr.Add(i); } for(int j=0; j<objsWithExplicitGroupPadding.Count; j++) { int i = objsWithExplicitGroupPadding[j]; float width = objsWithExplicitGroupPaddingWidth[j]; var lmgroup = objsToWriteGroup[i]; float totalArea = explicitGroupTotalArea[lmgroup.id]; float twidth = (width / Mathf.Sqrt(totalArea)) * lmgroup.resolution; var m = GetSharedMesh(objsToWrite[i]); // Following is copy-pasted from the loop above 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<int> arr; if (!meshToObjIDs.TryGetValue(m, out arr)) { meshToObjIDs[m] = arr = new List<int>(); } 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<storages.Length; s++) { var str = storages[s]; if (str == null) continue; str.modifiedAssetPathList = new List<string>(); str.modifiedAssets = new List<ftGlobalStorage.AdjustedMesh>(); } } 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<sceneCount; s++) { var objStorage = gstorage;// == null ? storages[0] : gstorage;// storages[s]; int mstoreIndex = objStorage.modifiedAssetPathList.IndexOf(assetPath); int ind = -1; var mname = m.name; if (mstoreIndex >= 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<string>(); newStruct.padding = new List<int>(); 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<int>(); 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<ids.Count; xx++) dirtyObjList.Add(ids[xx]); #if UNITY_2017_1_OR_NEWER objStorage.SyncModifiedAsset(mstoreIndex); #endif } else { 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<int>(); 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<ids.Count; xx++) dirtyObjList.Add(ids[xx]); paddingList[ind] = requiredPaddingClamped; unwrapperList[ind] = (int)ftRenderLightmap.unwrapper; #if UNITY_2017_1_OR_NEWER objStorage.SyncModifiedAsset(mstoreIndex); #endif } } // Backup padding storage to scene for(int s=0; s<storages.Length; s++) { var str = storages[s]; if (str == null) continue; var localIndex = str.modifiedAssetPathList.IndexOf(assetPath); if (localIndex < 0) { str.modifiedAssetPathList.Add(assetPath); str.modifiedAssets.Add(objStorage.modifiedAssets[mstoreIndex]); } else { str.modifiedAssets[localIndex] = objStorage.modifiedAssets[mstoreIndex]; } } } } EditorUtility.SetDirty(gstorage); } static bool ValidatePaddingImmutability(AdjustUVPaddingData adata) { if (validateLightmapStorageImmutability) { if (adata.dirtyAssetList.Count > 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<objsToWrite.Count; i++) { var obj = objsToWrite[i]; var lmgroup = objsToWriteGroup[i]; var holderObj = objsToWriteHolder[i]; if (holderObj != null) { if (!holderRect.TryGetValue(holderObj, out rc)) { holderObj = null; } } var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y); var sceneID = sceneToID[obj.scene]; var st = storages[sceneID]; if (st == null) { Debug.LogError("ValidateScaleOffsetImmutability: no storage"); return false; } var storedScaleOffset = Vector4.zero; if (obj.name == "__ExportTerrain") { var tindex = terrainObjectList.IndexOf(obj.transform.parent.gameObject); var terrain = terrainObjectToActual[tindex]; int index = st.bakedRenderersTerrain.IndexOf(terrain); /*if (st.bakedIDsTerrain[index] != lmgroup.id) { Debug.LogError("ValidateScaleOffsetImmutability: terrain LMID does not match"); return false; }*/ if (index < 0 || st.bakedScaleOffsetTerrain.Count <= index) continue; storedScaleOffset = st.bakedScaleOffsetTerrain[index]; } else { int index = st.bakedRenderers.IndexOf(obj.GetComponent<Renderer>()); /*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<objsToWrite.Count; i++) { var sharedMesh = GetSharedMesh(objsToWrite[i]); var assetPath = AssetDatabase.GetAssetPath(sharedMesh); int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(assetPath); if (mstoreIndex < 0) continue; dirtyObjList.Add(i); if (!dirtyAssetList.Contains(assetPath)) { dirtyAssetList.Add(assetPath); } } if (!ValidatePaddingImmutability(adata)) return false; for(int i=0; i<dirtyAssetList.Count; i++) { var assetPath = dirtyAssetList[i]; int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(assetPath); Debug.Log("Reimport " + assetPath); ProgressBarShow("Exporting scene - clearing UV adjustment for " + assetPath + "...", 0); gstorage.ClearAssetModifications(mstoreIndex); } EditorUtility.SetDirty(gstorage); return true; } static bool ReimportModifiedAssets(AdjustUVPaddingData adata) { var dirtyAssetList = adata.dirtyAssetList; for(int i=0; i<dirtyAssetList.Count; i++) { var assetPath = dirtyAssetList[i]; Debug.Log("Reimport " + assetPath); ProgressBarShow("Exporting scene - adjusting UV padding for " + assetPath + "...", 0); //AssetDatabase.ImportAsset(assetPath); (AssetImporter.GetAtPath(assetPath) as ModelImporter).SaveAndReimport(); if (CheckUnwrapError()) { return false; } } return true; } static void TransformModifiedAssets(ExportSceneData data, AdjustUVPaddingData adata, bool tangentSHLights) { // Transform modified vertices to world space again for(int d=0; d<adata.dirtyObjList.Count; d++) { int i = adata.dirtyObjList[d]; // Refresh attributes and indices after reimport bool isSkin; var m = GetSharedMeshSkinned(data.objsToWrite[i], out isSkin); data.objsToWriteVerticesUV[i] = m.uv; data.objsToWriteVerticesUV2[i] = m.uv2; var inds = new int[m.subMeshCount][]; for(int n=0; n<inds.Length; n++) inds[n] = m.GetTriangles(n); data.objsToWriteIndices[i] = inds; TransformVertices(data, tangentSHLights, i); // because vertex count/order could be modified } } static void CalculateHolderUVBounds(ExportSceneData data) { var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var objsToWriteHolder = data.objsToWriteHolder; var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW; var objsToWriteVerticesUV = data.objsToWriteVerticesUV; var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2; var objsToWriteIndices = data.objsToWriteIndices; var objToLodLevel = data.objToLodLevel; var holderObjUVBounds = data.holderObjUVBounds; var holderObjArea = data.holderObjArea; var groupToHolderObjects = data.groupToHolderObjects; // Calculate implicit group / atlas packing data // UV bounds and worldspace area for(int i=0; i<objsToWrite.Count; i++) { var obj = objsToWrite[i]; var lmgroup = objsToWriteGroup[i]; var calculateArea = lmgroup == null ? false : (lmgroup.isImplicit || lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas); if (!calculateArea) continue; var holderObj = objsToWriteHolder[i]; var m = GetSharedMesh(obj); var mr = obj.GetComponent<Renderer>(); var vpos = objsToWriteVerticesPosW[i]; var vuv = objsToWriteVerticesUV2[i];//m.uv2; var inds = objsToWriteIndices[i]; //if (vuv.Length == 0 || obj.GetComponent<BakeryLightMesh>()!=null) vuv = objsToWriteVerticesUV[i];//m.uv; // area lights or objects without UV2 export UV1 instead if (vuv.Length == 0 || obj.GetComponent<BakeryLightMesh>()!=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<m.subMeshCount;k++) { var indices = inds[k];//m.GetTriangles(k); int indexA, indexB, indexC; float area = 0; //float areaUV = 0; Vector4 uvBounds = new Vector4(1,1,0,0); // minx, miny, maxx, maxy for(int j=0;j<indices.Length;j+=3) { indexA = indices[j]; indexB = indices[j + 1]; indexC = indices[j + 2]; var v1 = vpos[indexA]; var v2 = vpos[indexB]; var v3 = vpos[indexC]; area += Vector3.Cross(v2 - v1, v3 - v1).magnitude; if (vuv.Length > 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<GameObject> holderList; if (!groupToHolderObjects.TryGetValue(lmgroup, out holderList)) { groupToHolderObjects[lmgroup] = holderList = new List<GameObject>(); } 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<groupList.Count; i++) { var lmgroup = groupList[i]; if (lmgroup.isImplicit) { lmgroup.resolution = ResolutionFromArea(lmgroup.area); } } } static void NormalizeHolderArea(BakeryLightmapGroup lmgroup, List<GameObject> 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.Count; i++) { // space outside of UV bounds shouldn't affect area var uvbounds = holderObjUVBounds[holderObjs[i]]; var width = uvbounds.z - uvbounds.x; var height = uvbounds.w - uvbounds.y; float uvboundsArea = width * height; lmgroupArea += holderObjArea[holderObjs[i]] * uvboundsArea; } areaMult = 1.0f / lmgroupArea; } // Perform the division and sum up total UV area for(int i=0; i<holderObjs.Count; i++) { holderObjArea[holderObjs[i]] *= areaMult; } } static void SumHolderAreaPerLODLevel(List<GameObject> holderObjs, ExportSceneData data, PackData pdata) { var objToLodLevel = data.objToLodLevel; var holderObjArea = data.holderObjArea; var remainingAreaPerLodLevel = pdata.remainingAreaPerLodLevel; for(int i=0; i<holderObjs.Count; i++) { int lodLevel = -1; if (!objToLodLevel.TryGetValue(holderObjs[i], out lodLevel)) lodLevel = -1; float lodArea = 0; if (!remainingAreaPerLodLevel.TryGetValue(lodLevel, out lodArea)) lodArea = 0; remainingAreaPerLodLevel[lodLevel] = lodArea + holderObjArea[holderObjs[i]]; } } static int CompareGameObjectsForPacking(GameObject a, GameObject b) { if (splitByScene) { if (a.scene.name != b.scene.name) return a.scene.name.CompareTo(b.scene.name); } if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { bool ba = a.name != "__ExportTerrainParent"; bool bb = b.name != "__ExportTerrainParent"; if (ba != bb) return ba.CompareTo(bb); } int lodLevelA = -1; int lodLevelB = -1; if (!cmp_objToLodLevel.TryGetValue(a, out lodLevelA)) lodLevelA = -1; if (!cmp_objToLodLevel.TryGetValue(b, out lodLevelB)) lodLevelB = -1; if (lodLevelA != lodLevelB) return lodLevelA.CompareTo(lodLevelB); float areaA = cmp_holderObjArea[a]; float areaB = cmp_holderObjArea[b]; // Workaround for "override resolution" // Always pack such rectangles first var comp = a.GetComponent<BakeryLightmapGroupSelector>(); if (comp != null && comp.instanceResolutionOverride) areaA = comp.instanceResolution * 10000; comp = b.GetComponent<BakeryLightmapGroupSelector>(); 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<GameObject> 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<holderObjs.Count; i++) { var area = holderObjArea[holderObjs[i]]; var uvbounds = holderObjUVBounds[holderObjs[i]]; // Calculate width and height of each holder in atlas UV space float width, height; var comp = holderObjs[i].GetComponent<BakeryLightmapGroupSelector>(); 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<autoAtlasGroups.Count; g++) { if (splitByScene) { if (autoAtlasGroups[g].sceneName != holderObjs[i].scene.name) continue; } if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { bool ba = holderObjs[i].name != "__ExportTerrainParent"; bool bb = !autoAtlasGroups[g].containsTerrains; if (ba != bb) continue; } if (autoAtlasGroups[g].sceneLodLevel != lodLevel) continue; twidth = (width * texelsPerUnit) / autoAtlasGroups[g].resolution; theight = (height * texelsPerUnit) / autoAtlasGroups[g].resolution; //unclampedTwidth = twidth; //unclampedTheight = twidth; twidth = twidth > 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<BakeryLightmapGroup>(); 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<storages[sceneToID[holder.scene]].implicitGroupedObjects.Count; k++) { if (storages[sceneToID[holder.scene]].implicitGroupedObjects[k] == holder) { storages[sceneToID[holder.scene]].implicitGroups[k] = newGroup; //Debug.LogError("Implicit set: " + k+" "+newGroup.name+" "+holder.name); } } */ //lmgroup = newGroup; } else { if (!pdata.repackStage2) { // explicit packed atlas - can't fit - try shrinking the whole atlas pdata.repackTries++; if (pdata.repackTries < atlasMaxTries) { pdata.repack = true; pdata.repackScale *= 0.75f; //Debug.LogError("Can't fit, set " +repackScale); break; } } else { // explicit packed atlas - did fit, now trying to scale up, doesn't work - found optimal fit pdata.repack = true; pdata.repackScale /= atlasScaleUpValue;//*= 0.75f; //Debug.LogError("Final, set " +repackScale); pdata.finalRepack = true; pdata.repackTries = 0; break; } } } if (node == null) { // No way to fit ExportSceneError("Can't fit " + holderObjs[i].name+" "+rect.width+" "+rect.height); return false; } else { // Generate final rectangle to transform local UV -> 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<storages[sceneToID[holder.scene]].implicitGroupedObjects.Count; k++) { if (storages[sceneToID[holder.scene]].implicitGroupedObjects[k] == holder) { storages[sceneToID[holder.scene]].implicitGroups[k] = newGroup; //Debug.LogError("Implicit set: " + k+" "+newGroup.name+" "+holder.name); } } } static List<int> GetAtlasBucketRanges(List<GameObject> holderObjs, ExportSceneData data, bool onlyUserSplits) { var objToLodLevel = data.objToLodLevel; var ranges = new List<int>(); 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.Count; i++) { bool splitAtlas = false; // Split by scene if (splitByScene) { var objSceneName = holderObjs[i].scene.name; if (objSceneName != sceneName) { splitAtlas = true; sceneName = objSceneName; } } if (!onlyUserSplits) { // Split by LOD int objLodLevel; if (!objToLodLevel.TryGetValue(holderObjs[i], out objLodLevel)) objLodLevel = -1; if (objLodLevel != lodLevel) { lodLevel = objLodLevel; splitAtlas = true; } // Split by terrain if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { bool ba = holderObjs[i].name == "__ExportTerrainParent"; if (ba != isTerrain) { isTerrain = ba; splitAtlas = true; } } } if (splitAtlas) { end = i; ranges.Add(start); ranges.Add(end-1); start = end; } } } end = holderObjs.Count-1; ranges.Add(start); ranges.Add(end); return ranges; } static float SumObjectsArea(List<GameObject> 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<count; i++) { // Create additional lightmaps newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>(); 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<GameObject> 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<int> buckets = null; if (lmgroup.isImplicit) { buckets = GetAtlasBucketRanges(holderObjs, data, postPacking); bucketCount = buckets.Count; } var holderAutoIndex = new int[holderObjs.Count]; for(int bucket=0; bucket<bucketCount; bucket+=2) { if (lmgroup.isImplicit) { bStart = buckets[bucket]; bEnd = buckets[bucket+1]; } int bSize = bEnd - bStart; if (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<BakeryLightmapGroupSelector>(); 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<int> indexList = null; List<Vector2> uvList = null; vertCount = indexCount = 0; int numMeshes = 0; var ubounds = holderObjUVBounds[holderObjs[i]]; var holder = holderObjs[i]; for(int o=0; o<objsToWriteHolder.Count; o++) { if (objsToWriteHolder[o] != holder) continue; if (numMeshes == 1) { indexList = new List<int>(); uvList = new List<Vector2>(); for(int j=0; j<indices.Length; j++) { indexList.Add(indices[j]); } for(int j=0; j<uv.Length; j++) { uvList.Add(uv[j]); } } bool isSkin; var mesh = GetSharedMeshSkinned(objsToWrite[o], out isSkin); indices = mesh.triangles; var uv1 = mesh.uv; var uv2 = mesh.uv2; if (uv2 == null || uv2.Length == 0) { uv = uv1; } else { uv = uv2; } for(int t=0; t<uv.Length; t++) { float u = (uv[t].x - ubounds.x) / (ubounds.z - ubounds.x); float v = (uv[t].y - ubounds.y) / (ubounds.w - ubounds.y); u *= twidth; v *= theight; uv[t] = new Vector2(u, v); } if (numMeshes > 0) { for(int j=0; j<indices.Length; j++) { indexList.Add(indices[j] + vertCount); } for(int j=0; j<uv.Length; j++) { uvList.Add(uv[j]); } } vertCount += uv.Length; indexCount += indices.Length; numMeshes++; } if (numMeshes > 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<newVertCount; j++) { if (uvBuffer[j].x < minU) minU = uvBuffer[j].x; if (uvBuffer[j].y < minV) minV = uvBuffer[j].y; if (uvBuffer[j].x > 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<int>[autoAtlasGroups.Count]; for(int bucket=0; bucket<bucketCount; bucket+=2) { bStart = buckets[bucket]; bEnd = buckets[bucket+1]; for(int i=bStart; i<=bEnd; i++) { int autoLM = holderAutoIndex[i]; if (autoLMBuckets[autoLM] == null) autoLMBuckets[autoLM] = new List<int>(); if (!autoLMBuckets[autoLM].Contains(bucket)) autoLMBuckets[autoLM].Add(bucket); } } int origGroupCount = autoAtlasGroups.Count; for(int i=0; i<origGroupCount; i++) { if (autoLMBuckets[i] != null && autoLMBuckets[i].Count > 1) { // Split for(int j=1; j<autoLMBuckets[i].Count; j++) { autoAtlasGroups[i].sceneName = holderObjs[bStart].scene.name; var newGroup = AllocateAutoAtlas(1, autoAtlasGroups[i], data); int bucket = autoLMBuckets[i][j]; bStart = buckets[bucket]; bEnd = buckets[bucket+1]; // Update LMGroup data //newGroup.sceneName = holderObjs[bStart].scene.name; int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1; newGroup.sceneLodLevel = lodLevel; if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { newGroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent"; } newGroup.parentName = autoAtlasGroups[i].name; autoAtlasGroups[i].parentName = "|"; //Debug.LogError(autoAtlasGroups[i].name+" (" +autoAtlasGroups[i].id+") -> "+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<origGroupCount; i++) { if (autoLMBuckets[i] != null) { for(int j=0; j<holderObjs.Count; j++) { if (holderAutoIndex[j] != i) continue; // Update LMGroup data var newGroup = autoAtlasGroups[i]; newGroup.sceneName = holderObjs[j].scene.name; int lodLevel; if (!objToLodLevel.TryGetValue(holderObjs[j], out lodLevel)) lodLevel = -1; newGroup.sceneLodLevel = lodLevel; if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { newGroup.containsTerrains = holderObjs[j].name == "__ExportTerrainParent"; } break; } } } } else if (bucketCount > 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<bucketCount; bucket+=2) { bStart = buckets[bucket]; bEnd = buckets[bucket+1]; var newGroup = AllocateAutoAtlas(1, lmgroup, data); if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1; newGroup.sceneLodLevel = lodLevel; if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap) { newGroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent"; } newGroup.mode = lmgroup.mode; newGroup.parentName = lmgroup.name; lmgroup.parentName = "|"; //Debug.LogError(newGroup.name+": "+ newGroup.sceneLodLevel+" because of " + holderObjs[bStart].name); for(int k=bStart; k<=bEnd; k++) { //MoveObjectToImplicitGroup(holderObjs[k], newGroup, data); data.storages[data.sceneToID[holderObjs[k].scene]].implicitGroupedObjects.Add(holderObjs[k]); data.storages[data.sceneToID[holderObjs[k].scene]].implicitGroups.Add(newGroup); tempStorage.implicitGroupMap[holderObjs[k]] = newGroup; } } } } return true; } static void NormalizeAtlas(BakeryLightmapGroup lmgroup, List<GameObject> 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<holderObjs.Count; i++) { var rect = holderRect[holderObjs[i]]; if ((rect.x + rect.width) > 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<holderObjs.Count; i++) { var rect = holderRect[holderObjs[i]]; holderRect[holderObjs[i]] = new Rect(rect.x * normalizeScale, rect.y * normalizeScale, rect.width * normalizeScale, rect.height * normalizeScale); } } } static bool PackAtlases(ExportSceneData data) { // IN, OUT lmgroup.containsTerrains, OUT holderObjs (sort) var groupToHolderObjects = data.groupToHolderObjects; // IN var objToLodLevel = data.objToLodLevel; // LODs packed to separate atlases cmp_objToLodLevel = objToLodLevel; // IN/OUT var holderObjArea = data.holderObjArea; // performs normalization cmp_holderObjArea = holderObjArea; // Pack atlases // Try to scale all objects to occupy all atlas space foreach(var pair in groupToHolderObjects) { // For every LMGroup with PackAtlas mode var lmgroup = pair.Key; var holderObjs = pair.Value; // get all objects var pdata = new PackData(); // Normalize by worldspace area and uv area // Read/write holderObjArea NormalizeHolderArea(lmgroup, holderObjs, data); // Sort objects by area and scene LOD level // + optionally by scene // + split by terrain holderObjs.Sort(CompareGameObjectsForPacking); var packer = lmgroup.atlasPacker == BakeryLightmapGroup.AtlasPacker.Auto ? atlasPacker : (ftGlobalStorage.AtlasPacker)lmgroup.atlasPacker; if (packer == ftGlobalStorage.AtlasPacker.xatlas) { if (!PackWithXatlas(lmgroup, holderObjs, data, pdata)) { ExportSceneError("Failed packing atlas"); return false; } } else { // Calculate area sum for every scene LOD level in LMGroup // Write remainingAreaPerLodLevel SumHolderAreaPerLODLevel(holderObjs, data, pdata); // Perform recursive packing while(pdata.repack) { pdata.continueRepack = true; if (!Pack(lmgroup, holderObjs, data, pdata)) return false; if (!pdata.continueRepack) break; } // Normalize atlas by largest axis NormalizeAtlas(lmgroup, holderObjs, data, pdata); } } cmp_objToLodLevel = null; cmp_holderObjArea = null; if (!ValidateScaleOffsetImmutability(data)) { sceneNeedsToBeRebuilt = true; return false; } return true; } static void NormalizeAutoAtlases(ExportSceneData data) { var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; var holderRect = data.holderRect; // Normalize autoatlases var stack = new Stack<AtlasNode>(); for(int g=0; g<autoAtlasGroups.Count; g++) { var lmgroup = autoAtlasGroups[g]; var rootNode = autoAtlasGroupRootNodes[g]; float maxx = 0; float maxy = 0; rootNode.GetMax(ref maxx, ref maxy); float maxDimension = maxx > maxy ? maxx : maxy; float normalizeScale = 1.0f / maxDimension; stack.Clear(); stack.Push(rootNode); while(stack.Count > 0) { var node = stack.Pop(); if (node.obj != null) { var rect = holderRect[node.obj]; holderRect[node.obj] = new Rect(rect.x * normalizeScale, rect.y * normalizeScale, rect.width * normalizeScale, rect.height * normalizeScale); } if (node.child0 != null) stack.Push(node.child0); if (node.child1 != null) stack.Push(node.child1); } if (maxDimension < 0.5f) { lmgroup.resolution /= 2; // shrink the lightmap after normalization if it was too empty lmgroup.resolution = Math.Max(lmgroup.resolution, minAutoResolution); } } } static void JoinAutoAtlases(ExportSceneData data) { var autoAtlasGroups = data.autoAtlasGroups; var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes; var groupList = data.groupList; var lmBounds = data.lmBounds; var holderRect = data.holderRect; var objsToWrite = data.objsToWrite; var objsToWriteGroup = data.objsToWriteGroup; var stack = new Stack<AtlasNode>(); // Join autoatlases var autoAtlasCategories = new List<string>(); bool joined = false; for(int g=0; g<autoAtlasGroups.Count; g++) { string cat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) cat = autoAtlasGroups[g].sceneName + cat; if (!autoAtlasCategories.Contains(cat)) autoAtlasCategories.Add(cat); } for(int alod=0; alod<autoAtlasCategories.Count; alod++) { var cat = autoAtlasCategories[alod]; var autoAtlasSizes = new List<int>(); var atlasStack = new Stack<BakeryLightmapGroup>(); for(int g=0; g<autoAtlasGroups.Count; g++) { var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat; if (thisCat != cat) continue; if (autoAtlasGroups[g].resolution == maxAutoResolution) continue; if (!autoAtlasSizes.Contains(autoAtlasGroups[g].resolution)) autoAtlasSizes.Add(autoAtlasGroups[g].resolution); } autoAtlasSizes.Sort(); for(int s=0; s<autoAtlasSizes.Count; s++) { int asize = autoAtlasSizes[s]; atlasStack.Clear(); for(int g=0; g<autoAtlasGroups.Count; g++) { var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel; if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat; if (thisCat != cat) continue; if (autoAtlasGroups[g].resolution != asize) continue; atlasStack.Push(autoAtlasGroups[g]); if (atlasStack.Count == 4) { var newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>(); 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<groupList.Count; x++) { groupList[x].id--; data.lmid--; } // Modify implicit group storage joined = true; stack.Clear(); stack.Push(subgroupRootNode); while(stack.Count > 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<storages[sceneToID[node.obj.scene]].implicitGroupedObjects.Count; k++) { if (storages[sceneToID[node.obj.scene]].implicitGroupedObjects[k] == node.obj) { storages[sceneToID[node.obj.scene]].implicitGroups[k] = newGroup; //Debug.LogError("Implicit set (join): " + k+" "+newGroup.name); } } */ } if (node.child0 != null) stack.Push(node.child0); if (node.child1 != null) stack.Push(node.child1); } } } } } } if (joined) { for(int i=0; i<objsToWrite.Count; i++) { objsToWriteGroup[i] = GetLMGroupFromObject(objsToWrite[i], data); } } } static bool ExportSceneValidationMessage(string msg) { ProgressBarEnd(false); if (ftRenderLightmap.verbose) { if (!EditorUtility.DisplayDialog("Bakery", msg, "Continue anyway", "Cancel")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); return false; } } else { Debug.LogError(msg); } ProgressBarInit("Exporting scene - preparing..."); return true; } static void ExportSceneError(string phase) { DebugLogError("Error exporting scene (" + phase + ") - see console for details"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); } class ExportSceneData { // Per-scene data public ftLightmapsStorage[] storages; public int firstNonNullStorage; //public ftLightmapsStorage settingsStorage; public Dictionary<Scene, int> sceneToID = new Dictionary<Scene, int>(); public Dictionary<Scene,bool> sceneHasStorage = new Dictionary<Scene,bool>(); // Object properties public Dictionary<GameObject,int> objToLodLevel = new Dictionary<GameObject,int>(); // defines atlas LOD level public Dictionary<GameObject,List<int>> objToLodLevelVisible = new Dictionary<GameObject,List<int>>(); // defines LOD levels where this object is visible public List<GameObject> objsToWrite = new List<GameObject>(); public List<bool> objsToWriteLightmapped = new List<bool>(); public List<BakeryLightmapGroup> objsToWriteGroup = new List<BakeryLightmapGroup>(); public List<GameObject> objsToWriteHolder = new List<GameObject>(); public List<Vector4> objsToWriteScaleOffset = new List<Vector4>(); public List<Vector2[]> objsToWriteUVOverride = new List<Vector2[]>(); public List<string> objsToWriteNames = new List<string>(); public List<Vector3[]> objsToWriteVerticesPosW = new List<Vector3[]>(); public List<Vector3[]> objsToWriteVerticesNormalW = new List<Vector3[]>(); public List<Vector4[]> objsToWriteVerticesTangentW = new List<Vector4[]>(); public List<Vector2[]> objsToWriteVerticesUV = new List<Vector2[]>(); public List<Vector2[]> objsToWriteVerticesUV2 = new List<Vector2[]>(); public List<int[][]> objsToWriteIndices = new List<int[][]>(); // Auto-atlasing public List<BakeryLightmapGroup> autoAtlasGroups = new List<BakeryLightmapGroup>(); public List<AtlasNode> autoAtlasGroupRootNodes = new List<AtlasNode>(); public BakeryLightmapGroup autoVertexGroup; // Data to collect for atlas packing public Dictionary<GameObject, float> holderObjArea = new Dictionary<GameObject, float>(); // LMGroup holder area, accumulated from all children public Dictionary<GameObject, Vector4> holderObjUVBounds = new Dictionary<GameObject, Vector4>(); // LMGroup holder 2D UV AABB public Dictionary<BakeryLightmapGroup, List<GameObject>> groupToHolderObjects = new Dictionary<BakeryLightmapGroup, List<GameObject>>(); // LMGroup -> holders map public Dictionary<GameObject, Rect> holderRect = new Dictionary<GameObject, Rect>(); // Per-LMGroup data public List<BakeryLightmapGroup> groupList = new List<BakeryLightmapGroup>(); public List<Bounds> lmBounds = new List<Bounds>(); // list of bounding boxes around LMGroups for testing lights // Geometry data public List<int>[] indicesOpaqueLOD = null; public List<int>[] indicesTransparentLOD = null; public int lmid = 0; // LMID counter public ExportSceneData(int sceneCount) { storages = new ftLightmapsStorage[sceneCount]; } } class AdjustUVPaddingData { public List<int> dirtyObjList = new List<int>(); public List<string> dirtyAssetList = new List<string>(); public Dictionary<Mesh, List<int>> meshToObjIDs = new Dictionary<Mesh, List<int>>(); public Dictionary<Mesh, int> meshToPaddingMap = new Dictionary<Mesh, int>(); } class PackData { public Dictionary<int,float> remainingAreaPerLodLevel = new Dictionary<int,float>(); 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<int>(); var indicesTransparent = new List<int>(); var data = new ExportSceneData(sceneCount); bool tangentSHLights = CheckForTangentSHLights(); // Per-LMGroup data var lmAlbedoList = new List<IntPtr>(); // list of albedo texture for UV GBuffer rendering var lmAlbedoListTex = new List<Texture>(); var lmAlphaList = new List<IntPtr>(); // list of alpha textures for alpha buffer generation var lmAlphaListTex = new List<Texture>(); var lmAlphaRefList = new List<float>(); // list of alpha texture refs var lmAlphaChannelList = new List<int>(); // list of alpha channels // lod-related var lmVOffset = new List<int>(); var lmUVArrays = new List<List<float>>(); var lmUVArrays2 = new List<float[]>(); var lmUVArrays3 = new List<float[]>(); var lmIndexArrays = new List<List<int>>(); var lmIndexArrays2 = new List<int[]>(); var lmLocalToGlobalIndices = new List<List<int>>(); vbtraceTexPosNormalArray = new List<float>(); vbtraceTexUVArray = new List<float>(); 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<GameObject>(); terrainObjectToActual = new List<Terrain>(); terrainObjectToHeightMap = new List<Texture>(); terrainObjectToBounds = new List<float>(); terrainObjectToBoundsUV = new List<float>(); terrainObjectToFlags = new List<int>(); terrainObjectToLMID = new List<int>(); terrainObjectToHeightMips = new List<List<float[]>>(); treeObjectList = new List<GameObject>(); temporaryAreaLightMeshList = new List<GameObject>(); temporaryAreaLightMeshList2 = new List<BakeryLightMesh>(); 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<GameObject, UnityEngine.Object>(); // 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<objsToWrite.Count; i++) { objsToWriteGroup[i] = GetLMGroupFromObject(objsToWrite[i], data); } } // Done collecting groups if (groupList.Count == 0 && modifyLightmapStorage) { DebugLogError("You need to mark some objects static or add Bakery Lightmap Group Selector components on them."); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } if (objsToWrite.Count == 0) { DebugLogError("You need to mark some objects static or add Bakery Lightmap Group Selector components on them."); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } if (atlasOnly) { atlasOnlyObj = new List<Renderer>(); atlasOnlySize = new List<int>(); atlasOnlyID = new List<int>(); atlasOnlyScaleOffset = new List<Vector4>(); var emptyVec4 = new Vector4(1,1,0,0); Rect rc = new Rect(); for(int i=0; i<objsToWrite.Count; i++) { var lmgroup = objsToWriteGroup[i]; var holderObj = objsToWriteHolder[i]; if (holderObj != null) { if (!holderRect.TryGetValue(holderObj, out rc)) { holderObj = null; } } var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y); atlasOnlyObj.Add(objsToWrite[i].GetComponent<Renderer>()); 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<groupList.Count; i++) { groupList[i].sortingID = i; } groupList.Sort(delegate(BakeryLightmapGroup a, BakeryLightmapGroup b) { int aa = (a.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) ? -1 : 1; int bb = (b.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) ? -1 : 1; return bb.CompareTo(aa); }); var lmBounds2 = new List<Bounds>(); for(int i=0; i<groupList.Count; i++) { lmBounds2.Add(lmBounds[groupList[i].sortingID]); // apply same sorting to lmBounds groupList[i].id = i; } lmBounds = lmBounds2; // Check for existing files if (overwriteWarning) { var checkGroupList = groupList; if (overwriteWarningSelectedOnly) { var selObjs = Selection.objects; checkGroupList = new List<BakeryLightmapGroup>(); for(int o=0; o<selObjs.Length; o++) { if (selObjs[o] as GameObject == null) continue; var selGroup = GetLMGroupFromObject(selObjs[o] as GameObject, data); if (selGroup == null) continue; if (!checkGroupList.Contains(selGroup)) { checkGroupList.Add(selGroup); } } } var existingFilenames = ""; for(int i=0; i<checkGroupList.Count; i++) { var nm = checkGroupList[i].name; var filename = nm + "_final" + overwriteExtensionCheck; var outputPath = ftRenderLightmap.outputPath; if (File.Exists("Assets/" + outputPath + "/" + filename)) { existingFilenames += filename + "\n"; } } if (existingFilenames.Length > 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<groupList.Count; i++) { var lmgroup = groupList[i]; var res = lmgroup.resolution; ulong lightingSize = (ulong)(res * res * 4 * 2); // RGBA16f approxMem += lightingSize; } var tileSize = ftRenderLightmap.tileSize; approxMem += (ulong)(tileSize * tileSize * 16 * 2); // maximum 2xRGBA32f (for fixPos12) } if (memoryWarning && ftRenderLightmap.verbose) { ProgressBarEnd(false); if (!EditorUtility.DisplayDialog("Lightmap memory check", "Rendering may require more than " + (ulong)((approxMem/1024)/1024) + "MB of video memory. Continue?", "Continue", "Cancel")) { CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } ProgressBarInit("Exporting scene - preparing...", window); } if (ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.Auto) { approxMem /= 1024; approxMem /= 1024; approxMem += 1024; // scene geometry size estimation - completely random if ((int)approxMem > 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<objsToWrite.Count; i++) { var obj = objsToWrite[i]; if (obj.name != "__ExportTerrain") continue; var holderObj = objsToWriteHolder[i]; Rect rc = new Rect(); if (holderObj != null) { if (!holderRect.TryGetValue(holderObj, out rc)) { holderObj = null; } } if (holderObj == null) continue; var lmgroup = objsToWriteGroup[i]; //float terrainPixelWidth = rc.width * lmgroup.resolution; //float terrainPixelHeight = rc.height * lmgroup.resolution; var index = terrainObjectList.IndexOf(obj.transform.parent.gameObject); var terrain = terrainObjectToActual[index]; var tdata = terrain.terrainData; //var heightmapResolution = tdata.heightmapResolution; //int closestSize = (int)Mathf.Min(Mathf.NextPowerOfTwo((int)Mathf.Max(terrainPixelWidth, terrainPixelHeight)), heightmapResolution-1); //if (closestSize < 2) continue; //int mipLog2 = (int)(Mathf.Log(closestSize) / Mathf.Log(2.0f)); //int maxMipLog2 = (int)(Mathf.Log(heightmapResolution-1) / Mathf.Log(2.0f)); //int mip = maxMipLog2 - mipLog2; float scaleX = tdata.size.x;// / (heightmapResolution-1); //float scaleY = tdata.size.y; float scaleZ = tdata.size.z;// / (heightmapResolution-1); float offsetX = obj.transform.position.x; float offsetY = obj.transform.position.y; float offsetZ = obj.transform.position.z; terrainObjectToLMID[index] = lmgroup.id; terrainObjectToBoundsUV[index*4] = rc.x; terrainObjectToBoundsUV[index*4+1] = rc.y; terrainObjectToBoundsUV[index*4+2] = rc.width; terrainObjectToBoundsUV[index*4+3] = rc.height; if (uvgbHeightmap) { var indexArrays = objsToWriteIndices[i] = new int[1][]; var indexArray = indexArrays[0] = new int[6];//(closestSize-1)*(closestSize-1)*6]; //int indexOffset = 0; //int vertOffset = 0; var uvArray = objsToWriteVerticesUV[i] = objsToWriteVerticesUV2[i] = new Vector2[4];//closestSize*closestSize]; var posArray = objsToWriteVerticesPosW[i] = new Vector3[4]; var normalArray = objsToWriteVerticesNormalW[i] = new Vector3[4]; posArray[0] = new Vector3(offsetX, offsetY, offsetZ); posArray[1] = new Vector3(offsetX + scaleX, offsetY, offsetZ); posArray[2] = new Vector3(offsetX, offsetY, offsetZ + scaleZ); posArray[3] = new Vector3(offsetX + scaleX, offsetY, offsetZ + scaleZ); normalArray[0] = Vector3.up; normalArray[1] = Vector3.up; normalArray[2] = Vector3.up; normalArray[3] = Vector3.up; uvArray[0] = new Vector2(0,0); uvArray[1] = new Vector2(1,0); uvArray[2] = new Vector2(0,1); uvArray[3] = new Vector2(1,1); indexArray[0] = 0; indexArray[1] = 2; indexArray[2] = 3; indexArray[3] = 0; indexArray[4] = 3; indexArray[5] = 1; } else { /*if (mip == 0) { // use existing heightmap var heights = tdata.GetHeights(0, 0, heightmapResolution, heightmapResolution); var posArray = objsToWriteVerticesPosW[i] = new Vector3[heightmapResolution * heightmapResolution]; objsToWriteVerticesNormalW[i] = terrainObjectToNormalMip0[index]; closestSize = heightmapResolution; scaleX /= closestSize-1; scaleZ /= closestSize-1; for(int y=0; y<closestSize; y++) { for(int x=0; x<closestSize; x++) { float px = x * scaleX + offsetX; float pz = y * scaleZ + offsetZ; posArray[y * closestSize + x] = new Vector3(px, heights[y, x] * scaleY + offsetY, pz); } } } else { // use mip var heights = terrainObjectToHeightMips[index][mip - 1]; var posArray = objsToWriteVerticesPosW[i] = new Vector3[closestSize * closestSize]; objsToWriteVerticesNormalW[i] = terrainObjectToNormalMips[index][mip - 1]; scaleX /= closestSize-1; scaleZ /= closestSize-1; for(int y=0; y<closestSize; y++) { for(int x=0; x<closestSize; x++) { float px = x * scaleX + offsetX; float pz = y * scaleZ + offsetZ; posArray[y * closestSize + x] = new Vector3(px, heights[y * closestSize + x] * scaleY + offsetY, pz); } } } var indexArrays = objsToWriteIndices[i] = new int[1][]; var indexArray = indexArrays[0] = new int[(closestSize-1)*(closestSize-1)*6]; int indexOffset = 0; int vertOffset = 0; var uvArray = objsToWriteVerticesUV[i] = objsToWriteVerticesUV2[i] = new Vector2[closestSize*closestSize]; for(int y=0; y<closestSize; y++) { for(int x=0; x<closestSize; x++) { uvArray[y * closestSize + x] = new Vector2(x / (float)(closestSize-1), y / (float)(closestSize-1)); if (x < closestSize-1 && y < closestSize-1) { indexArray[indexOffset] = vertOffset; indexArray[indexOffset + 1] = vertOffset + closestSize; indexArray[indexOffset + 2] = vertOffset + closestSize + 1; indexArray[indexOffset + 3] = vertOffset; indexArray[indexOffset + 4] = vertOffset + closestSize + 1; indexArray[indexOffset + 5] = vertOffset + 1; indexOffset += 6; } vertOffset++; } }*/ } } // Export heightmap metadata if (terrainObjectToActual.Count > 0) { terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; for(int i=0; i<terrainObjectToHeightMap.Count; i++) { fhmaps.Write(terrainObjectToLMID[i]); for(int fl=0; fl<6; fl++) fhmaps.Write(terrainObjectToBounds[i*6+fl]); for(int fl=0; fl<4; fl++) fhmaps.Write(terrainObjectToBoundsUV[i*4+fl]); fhmaps.Write(terrainObjectToFlags[i]); } } } // Write mark last written scene File.WriteAllText(scenePath + "/lastscene.txt", ftRenderLightmap.GenerateLightingDataAssetName()); // Write lightmap definitions var flms = new BinaryWriter(File.Open(scenePath + "/lms.bin", FileMode.Create)); var flmlod = new BinaryWriter(File.Open(scenePath + "/lmlod.bin", FileMode.Create)); var flmuvgb = new BinaryWriter(File.Open(scenePath + "/lmuvgb.bin", FileMode.Create)); if (ftRenderLightmap.clientMode) { ftClient.serverFileList.Add("lms.bin"); ftClient.serverFileList.Add("lmlod.bin"); ftClient.serverFileList.Add("lmuvgb.bin"); } // Init global UVGBuffer flags int uvgbGlobalFlags = 0; if (exportShaderColors) { if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.BakedNormalMaps) { uvgbGlobalFlags = UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS; } else if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.RNM || (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.SH && tangentSHLights)) { uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS | UVGBFLAG_TANGENT; } else { uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS; } } else { uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_ALBEDO | UVGBFLAG_EMISSIVE | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS; } if (terrainObjectToActual.Count > 0) uvgbGlobalFlags |= UVGBFLAG_TERRAIN; SetUVGBFlags(uvgbGlobalFlags); for(int i=0; i<groupList.Count; i++) { var lmgroup = groupList[i]; flms.Write(lmgroup.name); flmlod.Write(lmgroup.sceneLodLevel); int uvgbflags = 0; if (lmgroup.containsTerrains && exportTerrainAsHeightmap) uvgbflags = uvgbGlobalFlags | (UVGBFLAG_NORMAL | UVGBFLAG_TERRAIN); if (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.BakedNormalMaps) uvgbflags = UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS; if (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.RNM || (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.SH && tangentSHLights)) uvgbflags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS | UVGBFLAG_TANGENT; if (lmgroup.probes) uvgbflags = UVGBFLAG_RESERVED; flmuvgb.Write(uvgbflags); if (ftRenderLightmap.clientMode) { if (uvgbflags == 0) uvgbflags = uvgbGlobalFlags; ftClient.serverFileList.Add("uvpos_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); ftClient.serverFileList.Add("uvnormal_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); ftClient.serverFileList.Add("uvalbedo_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); if (lmgroup.mode != BakeryLightmapGroup.ftLMGroupMode.Vertex) { if ((uvgbflags & UVGBFLAG_SMOOTHPOS) != 0) ftClient.serverFileList.Add("uvsmoothpos_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); } if ((uvgbflags & UVGBFLAG_FACENORMAL) != 0) ftClient.serverFileList.Add("uvfacenormal_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); if ((uvgbflags & UVGBFLAG_TANGENT) != 0) ftClient.serverFileList.Add("uvtangent_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds")); } if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) { int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)lmgroup.totalVertexCount)); if (atlasTexSize > 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<ushort>(); int albedoCounter = 0; var albedoMap = new Dictionary<IntPtr, int>(); // albedo ptr -> ID map int alphaCounter = 0; var alphaMap = new Dictionary<IntPtr, List<int>>(); // alpha ptr -> ID map var dummyTexList = new List<Texture>(); // 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<MeshFilter>().sharedMesh; var pmesh = plane.GetComponent<MeshFilter>().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<groupList.Count; g++) { var lmgroup = groupList[g]; if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) continue; for(int i=0; i<objsToWrite.Count; i++) { if (objsToWriteGroup[i] != lmgroup) continue; var obj = objsToWrite[i]; var mesh = GetSharedMesh(obj); if (mesh == qmesh || mesh == pmesh) continue; var uv = objsToWriteVerticesUV[i];//mesh.uv; var uv2 = objsToWriteVerticesUV2[i];//mesh.uv2; var usedUVs = uv2.Length == 0 ? uv : uv2; bool validUVs = true; for(int v=0; v<usedUVs.Length; v++) { if (usedUVs[v].x < -0.0001f || usedUVs[v].x > 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<sceneLodsUsed; i++) { fib32lod[i] = new BinaryWriter(File.Open(scenePath + "/ib32_lod" + i + ".bin", FileMode.Create)); if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("ib32_lod" + i + ".bin"); } falphaidlod = new BinaryWriter[sceneLodsUsed]; for(int i=0; i<sceneLodsUsed; i++) { falphaidlod[i] = new BinaryWriter(File.Open(scenePath + "/alphaid_lod" + i + ".bin", FileMode.Create)); if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("alphaid2_lod" + i + ".bin"); // alphaid2, not alphaid } if (ftRenderLightmap.clientMode) { ftClient.serverFileList.Add("objects.bin"); ftClient.serverFileList.Add("mesh.bin"); ftClient.serverFileList.Add("lmid.bin"); ftClient.serverFileList.Add("seamfix.bin"); ftClient.serverFileList.Add("surf.bin"); ftClient.serverFileList.Add("matid.bin"); ftClient.serverFileList.Add("emissiveid.bin"); ftClient.serverFileList.Add("emissivemul.bin"); ftClient.serverFileList.Add("heightmapid.bin"); ftClient.serverFileList.Add("alphaid2.bin"); // alphaid2, not alphaid ftClient.serverFileList.Add("alphabuffer.bin"); ftClient.serverFileList.Add("vbfull.bin"); ftClient.serverFileList.Add("vbtrace.bin"); ftClient.serverFileList.Add("vbtraceTex.bin"); ftClient.serverFileList.Add("vbtraceUV0.bin"); ftClient.serverFileList.Add("ib.bin"); ftClient.serverFileList.Add("ib32.bin"); } // Export heightmap metadata //fhmaps.Write(terrainObjectToActual.Count); if (terrainObjectToActual.Count > 0) { //terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; /*for(int i=0; i<terrainObjectToHeightMap.Count; i++) { for(int fl=0; fl<6; fl++) fhmaps.Write(terrainObjectToBounds[i*6+fl]); }*/ } // Export some scene data // - LMIDs // - mesh definitions // - surface definitions // - albedo IDs // - alpha IDs // - update LMGroup bounds // - export index buffer // - generate tracing index buffer areaLightCounter = -2; //var defaultTexST = new Vector4(1,1,0,0); //var objsToWriteTexST = new List<Vector4>(); for(int i=0; i<objsToWrite.Count; i++) { var obj = objsToWrite[i]; var lmgroup = objsToWriteGroup[i]; var holderObj = objsToWriteHolder[i]; if (obj == null) { // wtf DebugLogError("Object " + objsToWriteNames[i] + " was destroyed mid-export"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } var mr = obj.GetComponent<Renderer>(); 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<m.subMeshCount; k++) { // Get mesh albedos int texID = -1; Material mat = null; Texture tex = null; //var texST = defaultTexST; if (k < mr.sharedMaterials.Length) { mat = mr.sharedMaterials[k]; if (mat != null) { if (mat.HasProperty("_MainTex")) { tex = mat.mainTexture; //if (mat.HasProperty("_MainTex_ST")) //{ // texST = mat.GetVector("_MainTex_ST"); //} } else if (mat.HasProperty("_BaseColorMap")) { // HDRP tex = mat.GetTexture("_BaseColorMap"); } else if (mat.HasProperty("_BaseMap")) { // URP tex = mat.GetTexture("_BaseMap"); } } } IntPtr texPtr = (IntPtr)0; Texture texWrite = null; if (tex != null) { texPtr = tex.GetNativeTexturePtr(); texWrite = tex; if (texPtr == (IntPtr)0) { if ((tex as RenderTexture) != null) { Debug.LogError("RenderTexture " + tex.name + " cannot be used as albedo (GetNativeTexturePtr returned null)"); tex = null; texWrite = null; } else { Debug.LogError("Texture " + tex.name + " cannot be used as albedo (GetNativeTexturePtr returned null)"); tex = null; texWrite = null; } } } if (tex == null) { // Create dummy 1px texture var dummyTex = new Texture2D(1,1); dummyPixelArray[0] = (mat == null || !mat.HasProperty("_Color")) ? Color.white : mat.color; dummyTex.SetPixels(dummyPixelArray); dummyTex.Apply(); texWrite = dummyTex; dummyTexList.Add(dummyTex); texPtr = dummyTex.GetNativeTexturePtr(); if (texPtr == (IntPtr)0) { Debug.LogError("Failed to call GetNativeTexturePtr() on newly created texture"); texWrite = null; } } if (!albedoMap.TryGetValue(texPtr, out texID)) { lmAlbedoList.Add(texPtr); lmAlbedoListTex.Add(texWrite); albedoMap[texPtr] = albedoCounter; texID = albedoCounter; albedoCounter++; } // Write albedo ID fmatid.Write((ushort)texID); // Get mesh alphas ushort alphaID = 0xFFFF; int alphaChannel = 3; // A if (mat != null && mat.HasProperty("_TransparencyLM")) // will override _MainTex.a if present { var tex2 = mat.GetTexture("_TransparencyLM"); if (tex2 != null) { tex = tex2; texPtr = tex.GetNativeTexturePtr(); alphaChannel = 0; // R } } if (tex != null) { var matTag = mat.GetTag("RenderType", true); bool isCutout = matTag == "TransparentCutout"; if (isCutout || matTag == "Transparent" || matTag == "TreeLeaf") { float alphaRef = 0.5f; if (mat != null && mat.HasProperty("_Cutoff")) { alphaRef = mat.GetFloat("_Cutoff"); } float opacity = 1.0f; if (!isCutout && mat.HasProperty("_Color")) { opacity = mat.color.a; } // let constant alpha affect cutout theshold for alphablend materials alphaRef = 1.0f - (1.0f - alphaRef) * opacity; if (alphaRef > 1) alphaRef = 1; // allow same map instances with different threshold List<int> texIDs; if (!alphaMap.TryGetValue(texPtr, out texIDs)) { alphaMap[texPtr] = texIDs = new List<int>(); 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<texIDs.Count; instance++) { texID = texIDs[instance]; if (Mathf.Abs(lmAlphaRefList[texID] - alphaRef) <= alphaInstanceThreshold) { if (lmAlphaChannelList[texID] == alphaChannel) { matchingInstance = instance; alphaID = (ushort)texID; break; } } } if (matchingInstance < 0) { 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; } } } } alphaIDs.Add(alphaID); // Get mesh emissives if (exportShaderColors) { for(int s=0; s<sceneCount; s++) { if (storages[s] == null) continue; while(storages[s].hasEmissive.Count <= id) storages[s].hasEmissive.Add(true); storages[s].hasEmissive[id] = true; } } texID = -1; tex = null; if (mat!=null && mat.shaderKeywords.Contains("_EMISSION")) { if (mat.HasProperty("_EmissionMap")) tex = mat.GetTexture("_EmissionMap"); if (tex != null) { texPtr = tex.GetNativeTexturePtr(); if (texPtr == (IntPtr)0) { if ((tex as RenderTexture) != null) { Debug.LogError("RenderTexture " + tex.name + " cannot be used as emission (GetNativeTexturePtr returned null)"); tex = null; } else { Debug.LogError("Texture " + tex.name + " cannot be used as emission (GetNativeTexturePtr returned null)"); tex = null; } //DebugLogError("Null emission tex ptr"); } } if (tex == null && mat.HasProperty("_EmissionColor")) { // Create dummy 1px texture var dummyTex = new Texture2D(1,1); dummyPixelArray[0] = mat.GetColor("_EmissionColor"); dummyTex.SetPixels(dummyPixelArray); dummyTex.Apply(); tex = dummyTex; dummyTexList.Add(dummyTex); texPtr = dummyTex.GetNativeTexturePtr(); if (texPtr == (IntPtr)0) { Debug.LogError("Failed to call GetNativeTexturePtr() on newly created texture"); texWrite = null; //DebugLogError("Null dummy tex ptr"); } } if (!albedoMap.TryGetValue(texPtr, out texID)) { lmAlbedoList.Add(texPtr); lmAlbedoListTex.Add(tex); albedoMap[texPtr] = albedoCounter; texID = albedoCounter; albedoCounter++; } for(int s=0; s<sceneCount; s++) { if (storages[s] == null) continue; while(storages[s].hasEmissive.Count <= id) storages[s].hasEmissive.Add(false); storages[s].hasEmissive[id] = true; } fmatide.Write((ushort)texID); fmatideb.Write(mat.HasProperty("_EmissionColor") ? mat.GetColor("_EmissionColor").maxColorComponent : 1); } else { fmatide.Write((ushort)0xFFFF); fmatideb.Write(0.0f); } if (isTerrain && uvgbHeightmap) { var hindex = terrainObjectList.IndexOf(obj.transform.parent.gameObject); //var htex = terrainObjectToHeightMap[hindex]; //texPtr = htex.GetNativeTexturePtr(); //heightmapList.Add(texPtr); //heightmapListTex.Add(htex); //heightmapListBounds.Add(); //texID = heightmapCounter; //heightmapCounter++; fmatidh.Write((ushort)hindex);//texID); } else { fmatidh.Write((ushort)0xFFFF); } } // Update LMGroup bounds if (modifyLightmapStorage) { if (lmBounds[id].size == Vector3.zero) { lmBounds[id] = mr.bounds; } else { var b = lmBounds[id]; b.Encapsulate(mr.bounds); lmBounds[id] = b; } } } else { // Write empty albedo/alpha IDs for non-lightmapped for(int k=0; k<m.subMeshCount; k++) { fmatid.Write((ushort)0); alphaIDs.Add(0xFFFF); fmatide.Write((ushort)0xFFFF); fmatideb.Write(0.0f); fmatidh.Write((ushort)0xFFFF); } } int currentVoffset = voffset; voffset += objsToWriteVerticesPosW[i].Length;// m.vertexCount; // Check if mesh is flipped bool isFlipped = Mathf.Sign(obj.transform.lossyScale.x*obj.transform.lossyScale.y*obj.transform.lossyScale.z) < 0; if (lmgroup != null && lmgroup.flipNormal) isFlipped = !isFlipped; while(lmIndexArrays.Count <= id) { lmIndexArrays.Add(new List<int>()); lmLocalToGlobalIndices.Add(new List<int>()); lmVOffset.Add(0); } var mmr = obj.GetComponent<Renderer>(); 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<m.subMeshCount;k++) { // Export regular index buffer //var indexCount = exportIB(fib, m, k, isFlipped, false, 0, null, 0); var indexCount = exportIB(fib, inds[k], isFlipped, false, 0, null, 0); bool submeshCastsShadows = castsShadows; if (submeshCastsShadows) { var mats = mmr.sharedMaterials; if (mats.Length > 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<visList.Count; vlod++) { int lod = visList[vlod]; var indicesOpaqueArray = data.indicesOpaqueLOD[lod]; var indicesTransparentArray = data.indicesTransparentLOD[lod]; var falphaidFile = falphaidlod[lod]; exportIB32(indicesOpaqueArray, indicesTransparentArray, id>=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<vcount; k++) { remapArray.Add(k + currentVoffset); } lmVOffset[id] += vcount; } } } catch(Exception e) { DebugLogError("Error exporting scene - see console for details"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); Debug.LogError("Exception caught: " + e.ToString()); throw; } ProgressBarShow("Exporting scene - finishing objects...", 0.5f); if (userCanceled) { CloseAllFiles(); ProgressBarEnd(true); yield break; } yield return null; try { // Write vertex buffers and update storage Rect rc = new Rect(); var emptyVec4 = new Vector4(1,1,0,0); for(int i=0; i<objsToWrite.Count; i++) { var obj = objsToWrite[i]; var m = GetSharedMesh(obj); var lmgroup = objsToWriteGroup[i]; var id = lmgroup == null ? -1 : objsToWriteGroup[i].id; BakeryLightMesh areaLight = obj.GetComponent<BakeryLightMesh>(); 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<Renderer>().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<tformedPos.Length; t++) { if (holderObj != null) { tformedUV2[t].x = uv2[t].x * rc.width + rc.x; tformedUV2[t].y = uv2[t].y * rc.height + rc.y; } } objsToWriteUVOverride.Add(null); } else if (vertexBake) { tformedUV2 = GenerateVertexBakeUVs(lmgroup.vertexCounter, tformedPos.Length, lmgroup.totalVertexCount); lmgroup.vertexCounter += tformedPos.Length; objsToWriteUVOverride.Add(tformedUV2); } else { tformedUV2 = uv; objsToWriteUVOverride.Add(null); } if (id >= 0) { while(lmUVArrays.Count <= id) { lmUVArrays.Add(new List<float>()); } var lmUVArray = lmUVArrays[id]; for(int k=0; k<tformedUV2.Length; k++) { lmUVArray.Add(tformedUV2[k].x); lmUVArray.Add(tformedUV2[k].y); } } exportVBFull(fvbfull, tformedPos, tformedNormals, tformedTangents, uv, tformedUV2); vbTimeWriteFull += GetTime() - time; time = GetTime(); //if (castsShadows) //{ exportVBTrace(fvbtrace, m, tformedPos, tformedNormals); vbTimeWriteT += GetTime() - time; time = GetTime(); exportVBTraceTexAttribs(vbtraceTexPosNormalArray, vbtraceTexUVArray, tformedPos, tformedNormals, tformedUV2, id, vertexBake, obj); vbTimeWriteT2 += GetTime() - time; time = GetTime(); exportVBTraceUV0(fvbtraceUV0, uv, tformedPos.Length); vbTimeWriteT3 += GetTime() - time; time = GetTime(); //} voffset += tformedPos.Length; vbTimeWrite += GetTime() - time2; // update storage // also write seamfix.bin var sceneID = sceneToID[obj.scene]; if (obj.name == "__ExportTerrain") { fseamfix.Write(false); var index = terrainObjectList.IndexOf(obj.transform.parent.gameObject); var terrain = terrainObjectToActual[index]; var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y); if (!storages[sceneID].bakedRenderersTerrain.Contains(terrain)) { if (modifyLightmapStorage) { storages[sceneID].bakedRenderersTerrain.Add(terrain); storages[sceneID].bakedIDsTerrain.Add(CorrectLMGroupID(id, lmgroup, groupList)); storages[sceneID].bakedScaleOffsetTerrain.Add(scaleOffset); } } objsToWriteScaleOffset.Add(scaleOffset); } else { fseamfix.Write(true); var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y); if (modifyLightmapStorage) { bool vertexImplicit = false; if (vertexBake) { if (lmgroup.isImplicit) vertexImplicit = true; } if (!vertexImplicit) { storages[sceneID].bakedRenderers.Add(obj.GetComponent<Renderer>()); 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<sceneCount; s++) { if (storages[s] == null) continue; storages[s].lmGroupMinLOD = new int[groupList.Count]; storages[s].lmGroupLODMatrix = new int[groupList.Count * groupList.Count]; } for(int i=0; i<groupList.Count; i++) { var lmgroup = groupList[i]; if (lmgroup.resolution < 128) { lmUVArrays2.Add(null); lmIndexArrays2.Add(null); lmUVArrays3.Add(null); continue; } if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex || lmgroup.containsTerrains) { lmUVArrays2.Add(null); lmIndexArrays2.Add(null); lmUVArrays3.Add(null); if (lmgroup.containsTerrains) { int minLodResolutionTerrain = 128; for(int s=0; s<sceneCount; s++) { if (storages[s] == null) continue; int minLOD = (int)(Mathf.Log(lmgroup.resolution, 2.0f) - Mathf.Log(minLodResolutionTerrain, 2.0f)) - 1; if (minLOD < 0) minLOD = 0; storages[s].lmGroupMinLOD[lmgroup.id] = minLOD; } } continue; } int id = lmgroup.id; lmUVArrays2.Add(lmUVArrays[i].ToArray()); lmIndexArrays2.Add(lmIndexArrays[i].ToArray()); lmUVArrays3.Add(lmUVArrays[i].ToArray()); int uvIslands = uvrLoad(lmUVArrays2[i], lmUVArrays2[i].Length/2, lmIndexArrays2[i], lmIndexArrays2[i].Length); if (uvIslands <= 0) { Debug.LogError("Can't generate LOD UVs for " + lmgroup.name+" "+lmUVArrays2[i].Length+" "+lmIndexArrays2[i].Length+" "+lmgroup.containsTerrains); uvrUnload(); continue; } int minLodResolution = Mathf.NextPowerOfTwo((int)Mathf.Ceil(Mathf.Sqrt((float)uvIslands))); minLodResolution = minLodResolution << 1; if (minLodResolution > 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<sceneCount; s++) { if (storages[s] == null) continue; int minLOD = (int)(Mathf.Log(lmgroup.resolution, 2.0f) - Mathf.Log(minLodResolution, 2.0f)) - 1; if (minLOD < 0) minLOD = 0; storages[s].lmGroupMinLOD[lmgroup.id] = minLOD; } int uvrErrCode = uvrRepack(0, minLodResolution); if (uvrErrCode == -1) { Debug.LogError("Can't repack LOD UVs for " + lmgroup.name); uvrUnload(); continue; } Debug.Log("Tries left: " + uvrErrCode); uvrUnload(); var numLocalVerts = lmUVArrays2[i].Length / 2; for(int k=0; k<numLocalVerts; k++) { float u = lmUVArrays2[i][k * 2]; u = Mathf.Clamp(u, 0, 0.99999f); u += id * 10; if (i >= 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<numTraceVerts; i++) { fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6]); fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 1]); fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 2]); fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 3]); fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 4]); fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 5]); fvbtraceTex.Write(vbtraceTexUVArray[i * 2]); fvbtraceTex.Write(vbtraceTexUVArray[i * 2 + 1]); } // Generate LOD UV buffer if (ftRenderLightmap.giLodModeEnabled) { var uvBuffOffsets = new int[lmUVArrays3.Count]; var uvBuffLengths = new int[lmUVArrays3.Count]; int uvBuffSize = 0; for(int i=0; i< lmUVArrays3.Count; i++) { if (lmUVArrays3[i] == null) continue; uvBuffOffsets[i] = uvBuffSize; uvBuffLengths[i] = lmUVArrays3[i].Length; uvBuffSize += lmUVArrays3[i].Length; } var uvSrcBuff = new float[uvBuffSize]; var uvDestBuff = new float[uvBuffSize]; for(int i=0; i< lmUVArrays3.Count; i++) { if (lmUVArrays3[i] == null) continue; var arr = lmUVArrays3[i]; var arr2 = lmUVArrays2[i]; var offset = uvBuffOffsets[i]; for(int j=0; j<arr.Length; j++) { uvSrcBuff[j + offset] = arr[j]; uvDestBuff[j + offset] = arr2[j]; } } var lmrIndicesOffsets = new int[lmIndexArrays2.Count]; var lmrIndicesLengths = new int[lmIndexArrays2.Count]; int lmrIndicesSize = 0; for(int i=0; i< lmIndexArrays2.Count; i++) { if (lmIndexArrays2[i] == null) continue; lmrIndicesOffsets[i] = lmrIndicesSize; lmrIndicesLengths[i] = lmIndexArrays2[i].Length; lmrIndicesSize += lmIndexArrays2[i].Length; } var lmrIndicesBuff = new int[lmrIndicesSize]; for(int i=0; i< lmIndexArrays2.Count; i++) { if (lmIndexArrays2[i] == null) continue; var arr = lmIndexArrays2[i]; var offset = lmrIndicesOffsets[i]; for(int j=0; j<arr.Length; j++) { lmrIndicesBuff[j + offset] = arr[j]; } } for(int s=0; s<sceneCount; s++) { if (storages[s] == null) continue; storages[s].uvBuffOffsets = uvBuffOffsets; storages[s].uvBuffLengths = uvBuffLengths; storages[s].uvSrcBuff = uvSrcBuff; storages[s].uvDestBuff = uvDestBuff; storages[s].lmrIndicesOffsets = lmrIndicesOffsets; storages[s].lmrIndicesLengths = lmrIndicesLengths; storages[s].lmrIndicesBuff = lmrIndicesBuff; } vbtraceTexUVArrayLOD = new float[vbtraceTexUVArray.Count]; for(int i=0; i<groupList.Count; i++) { var lmgroup = groupList[i]; if (lmgroup.resolution < 128) continue; if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex || lmgroup.containsTerrains) continue; var remapArray = lmLocalToGlobalIndices[i]; var uvArray = lmUVArrays2[i]; for(int j=0; j<remapArray.Count; j++) { vbtraceTexUVArrayLOD[remapArray[j]*2] = uvArray[j*2]; vbtraceTexUVArrayLOD[remapArray[j]*2+1] = uvArray[j*2+1]; } } } // Write tracing index buffer fib32.Write(indicesOpaque.Count); // firstAlphaTriangle for(int i=0; i<indicesOpaque.Count; i++) fib32.Write(indicesOpaque[i]); // opaque triangles for(int i=0; i<indicesTransparent.Count; i++) fib32.Write(indicesTransparent[i]); // alpha triangles // Write scene LOD tracing index buffers for(int lod=0; lod<sceneLodsUsed; lod++) { var indicesOpaqueArray = data.indicesOpaqueLOD[lod]; var indicesTransparentArray = data.indicesTransparentLOD[lod]; fib32lod[lod].Write(indicesOpaqueArray.Count); for(int i=0; i<indicesOpaqueArray.Count; i++) fib32lod[lod].Write(indicesOpaqueArray[i]); // opaque triangles for(int i=0; i<indicesTransparentArray.Count; i++) fib32lod[lod].Write(indicesTransparentArray[i]); // alpha triangles } Debug.Log("Wrote binaries in " + ((GetTime() - totalTime)/1000.0) + "s"); Debug.Log("VB read time " + (vbTimeRead/1000.0) + "s"); Debug.Log("VB write time " + (vbTimeWrite/1000.0) + "s"); Debug.Log("VB write time (full) " + (vbTimeWriteFull/1000.0) + "s"); Debug.Log("VB write time (trace) " + (vbTimeWriteT/1000.0) + "s"); Debug.Log("VB write time (trace tex) " + (vbTimeWriteT2/1000.0) + "s"); Debug.Log("VB write time (UV0) " + (vbTimeWriteT3/1000.0) + "s"); Debug.Log("IB time " + (ibTime/1000.0) + "s"); fscene.Write(objsToWrite.Count); int meshID = 0; foreach(var obj in objsToWrite) { fscene.Write(meshID); meshID++; } foreach(var obj in objsToWrite) { fscene.Write(obj.name); } fscene.Close(); fmesh.Close(); flmid.Close(); fseamfix.Close(); fsurf.Close(); fmatid.Close(); fmatide.Close(); fmatideb.Close(); fmatidh.Close(); fvbfull.Close(); fvbtrace.Close(); fvbtraceTex.Close(); fvbtraceUV0.Close(); fib.Close(); fib32.Close(); falphaid.Close(); fhmaps.Close(); if (fib32lod != null) { for(int i=0; i<fib32lod.Length; i++) fib32lod[i].Close(); } if (falphaidlod != null) { for(int i=0; i<falphaidlod.Length; i++) falphaidlod[i].Close(); } if (modifyLightmapStorage) { for(int s=0; s<sceneCount; s++) { if (storages[s] == null) continue; storages[s].bounds = lmBounds; } } //} startMsU = GetTime(); } catch(Exception e) { DebugLogError("Error exporting scene - see console for details"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); Debug.LogError("Exception caught: " + e.ToString()); throw; } if (exportShaderColors && renderTextures) { yield return null; ProgressBarShow("Exporting scene - shaded surface colors...", 0.55f); for(int g=0; g<groupList.Count; g++) { var str = storages[data.firstNonNullStorage]; if (str == null) Debug.LogError("storages[data.firstNonNullStorage] == null"); if (str.hasEmissive == null) Debug.LogError("storages[data.firstNonNullStorage].hasEmissive == null"); if (groupList[g] == null) Debug.LogError("group is null"); var hasEmissive = str.hasEmissive.Count > 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<objsToWrite.Count; i++) { var obj = objsToWrite[i]; var lmgroup = objsToWriteGroup[i]; if (lmgroup == null) continue; if (lmgroup.id != groupList[g].id) continue; if (obj.GetComponent<BakeryLightMesh>()) continue; var bakedMesh = GetSharedMeshBaked(obj); ftUVGBufferGen.RenderUVGBuffer(bakedMesh, obj.GetComponent<Renderer>(), 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<lmAlbedoListTex.Count; i++) { Graphics.Blit(lmAlbedoListTex[i] as Texture2D, forceRt); Graphics.SetRenderTarget(forceRt); forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true); forceTex.Apply(); lmAlbedoList[i] = lmAlbedoListTex[i].GetNativeTexturePtr(); } } for(int i=0; i<lmAlphaListTex.Count; i++) { Graphics.Blit(lmAlphaListTex[i] as Texture2D, forceRt); Graphics.SetRenderTarget(forceRt); forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true); forceTex.Apply(); lmAlphaList[i] = lmAlphaListTex[i].GetNativeTexturePtr(); } if (exportShaderColors) { if (terrainObjectToActual.Count > 0) { //for(int i=0; i<heightmapListTex.Count; i++) terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count]; for(int i=0; i<terrainObjectToHeightMap.Count; i++) { Graphics.Blit(terrainObjectToHeightMap[i] as Texture2D, forceRt); Graphics.SetRenderTarget(forceRt); forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true); forceTex.Apply(); terrainObjectToHeightMapPtr[i] = terrainObjectToHeightMap[i].GetNativeTexturePtr(); } SetAlbedos(terrainObjectToHeightMap.Count, terrainObjectToHeightMapPtr); int cerr = CopyAlbedos(); if (cerr != 0) { DebugLogError("Failed to copy textures (" + cerr + ")"); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } } else { SetAlbedos(0, null); } } else { SetAlbedos(lmAlbedoList.Count, lmAlbedoList.ToArray()); } SetAlphas(lmAlphaList.Count, lmAlphaList.ToArray(), lmAlphaRefList.ToArray(), lmAlphaChannelList.ToArray(), sceneLodsUsed, flipAlpha); GL.IssuePluginEvent(6); // render alpha buffer int uerr = 0; while(uerr == 0) { uerr = GetABGErrorCode(); yield return null; } /*yield return new WaitForEndOfFrame(); int uerr = ftGenerateAlphaBuffer();*/ if (uerr != 0 && uerr != 99999) { DebugLogError("ftGenerateAlphaBuffer error: " + uerr); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } if (!renderTextures) { ProgressBarEnd(true);//false); yield break; } //ProgressBarShow("Exporting scene - UV GBuffer...", 0.8f); //if (userCanceled) yield break; //yield return null; //GL.IssuePluginEvent(1); // render UV GBuffer //yield return new WaitForEndOfFrame(); SetFixPos(false);//true); // do it manually SetCompression(ftRenderLightmap.compressedGBuffer); if (!exportShaderColors) { uerr = ftRenderUVGBuffer(); if (uerr != 0) { DebugLogError("ftRenderUVGBuffer error: " + uerr); CloseAllFiles(); userCanceled = true; ProgressBarEnd(true); yield break; } } ms = GetTime(); Debug.Log("UVGB/fixPos/alpha time: " + ((ms - startMsU) / 1000.0) + " seconds"); ProgressBarEnd(true); Debug.Log("Scene export finished"); } int countChildrenFlat(Transform tform) { int count = 1; foreach(Transform t in tform) { count += countChildrenFlat(t); } return count; } } #endif