public void OptimizeShader(bool reuseTextures, bool generatePrefabs) { if (shaderName == "")//unknown shader doesnt need to be optimed { return; } int currentAtlasSize = CalculateAproxAtlasSize(reuseTextures); //(reuseTextures) ? aproxAtlasSizeReuseTextures : aproxAtlasSize; if ((objects.Count > 1 || //more than 1 obj or 1 obj with multiple mat (objects.Count == 1 && objects[0] != null && objects[0].ObjHasMoreThanOneMaterial)) && currentAtlasSize < Constants.MaxAtlasSize) //check the generated atlas size doesnt exceed max supported texture size { List <Rect> texturePositions = new List <Rect>(); //creo que puede morir porque el atlasser tiene adentro un rect. Node resultNode = null; //nodes for the tree for atlasing Atlasser generatedAtlas = new Atlasser(currentAtlasSize, currentAtlasSize); int resizeTimes = 1; TextureReuseManager textureReuseManager = new TextureReuseManager(); for (int j = objects.Count - 1; j >= 0; j--) //start from the largest to the shortest textures { if (objects[j].ObjHasMoreThanOneMaterial) //before atlassing multiple materials obj, combine it. { objects[j].ProcessAndCombineMaterials(); } Vector2 textureToAtlasSize = objects[j].TextureSize; if (reuseTextures) { //if texture is not registered already if (!textureReuseManager.TextureRefExists(objects[j])) { //generate a node resultNode = generatedAtlas.Insert(Mathf.RoundToInt((textureToAtlasSize.x != Constants.NULLV2.x) ? textureToAtlasSize.x : Constants.NullTextureSize), Mathf.RoundToInt((textureToAtlasSize.y != Constants.NULLV2.y) ? textureToAtlasSize.y : Constants.NullTextureSize)); if (resultNode != null) //save node if fits in atlas { textureReuseManager.AddTextureRef(objects[j], resultNode.NodeRect, j); } } } else { resultNode = generatedAtlas.Insert(Mathf.RoundToInt((textureToAtlasSize.x != Constants.NULLV2.x) ? textureToAtlasSize.x : Constants.NullTextureSize), Mathf.RoundToInt((textureToAtlasSize.y != Constants.NULLV2.y) ? textureToAtlasSize.y : Constants.NullTextureSize)); } if (resultNode == null) { int resizedAtlasSize = currentAtlasSize + Mathf.RoundToInt((float)currentAtlasSize * Constants.AtlasResizeFactor * resizeTimes); generatedAtlas = new Atlasser(resizedAtlasSize, resizedAtlasSize); j = objects.Count;//Count and not .Count-1 bc at the end of the loop it will be substracted j-- and we want to start from Count-1 texturePositions.Clear(); textureReuseManager.ClearTextureRefs(); resizeTimes++; } else { if (reuseTextures) { texturePositions.Add(textureReuseManager.GetTextureRefPosition(objects[j])); } else { texturePositions.Add(resultNode.NodeRect);//save the texture rectangle } } } Material atlasMaterial = CreateAtlasMaterialAndTexture(generatedAtlas, shaderName, textureReuseManager); OptimizeDrawCalls(ref atlasMaterial, generatedAtlas.GetAtlasSize().x, generatedAtlas.GetAtlasSize().y, texturePositions, reuseTextures, textureReuseManager, generatePrefabs); //after the game object has been organized, remove the combined game objects. for (int i = 0; i < objects.Count; i++) { if (objects[i].ObjWasCombined) { objects[i].ClearCombinedObject(); } } } }
void OnGUI() { if (NeedToReload()) { ReloadDataStructures(); } selectedMenuOption = GUI.SelectionGrid(new Rect(5, 8, window.position.width - 10, 20), selectedMenuOption, menuOptions, 3); switch (selectedMenuOption) { case 0: ObjectsGUI.Instance.DrawGUI(window); AdvancedMenuGUI.Instance.ClearConsole(); menuOptions[0] = "Objects"; break; case 1: ShadersGUI.Instance.DrawGUI(window); AdvancedMenuGUI.Instance.ClearConsole(); menuOptions[0] = "Objects(" + ObjSorter.GetTotalSortedObjects() + ")"; break; case 2: AdvancedMenuGUI.Instance.DrawGUI(window); menuOptions[0] = "Objects(" + ObjSorter.GetTotalSortedObjects() + ")"; break; default: Debug.LogError("Unrecognized menu option: " + selectedMenuOption); break; } if (GUI.Button(new Rect(5, window.position.height - 35, window.position.width / 2 - 10, 33), "Clear Atlas")) { GameObject[] objsInHierarchy = Utils.GetAllObjectsInHierarchy(); foreach (GameObject obj in objsInHierarchy) { if (obj.name.Contains(Constants.OptimizedObjIdentifier)) { DestroyImmediate(obj); } else if (obj.GetComponent <MeshRenderer>() != null) { obj.GetComponent <MeshRenderer>().enabled = true; } } // delete the folder where the atlas reside. string folderOfAtlas = EditorApplication.currentScene; if (folderOfAtlas == "") //scene is not saved yet. { folderOfAtlas = Constants.NonSavedSceneFolderName + ".unity"; Debug.LogWarning("WARNING: Scene has not been saved, clearing baked objects from NOT_SAVED_SCENE folder"); } folderOfAtlas = folderOfAtlas.Substring(0, folderOfAtlas.Length - 6) + "-Atlas"; //remove the ".unity" if (Directory.Exists(folderOfAtlas)) { FileUtil.DeleteFileOrDirectory(folderOfAtlas); AssetDatabase.Refresh(); } } GUI.enabled = CheckEmptyArray(); //if there are no textures deactivate the GUI if (GUI.Button(new Rect(window.position.width / 2, window.position.height - 35, window.position.width / 2 - 5, 33), "Bake Atlas")) { //Remove objects that are already optimized and start over. if (AdvancedMenuGUI.Instance.RemoveObjectsBeforeBaking) { GameObject[] objsInHierarchy = Utils.GetAllObjectsInHierarchy(); foreach (GameObject obj in objsInHierarchy) { if (obj.name.Contains(Constants.OptimizedObjIdentifier)) { GameObject.DestroyImmediate(obj); } } } List <Rect> texturePositions = new List <Rect>(); //creo que esto puede morir porque el atlasser tiene adentro un rect. string progressBarInfo = ""; float pace = 1 / (float)ObjSorter.GetRecognizableShadersCount(); float progress = pace; Node resultNode = null; //nodes for the tree for atlasing for (int shaderIndex = 0; shaderIndex < ObjSorter.GetObjs().Count; shaderIndex++) { EditorUtility.DisplayProgressBar("Optimization in progress... " + (AdvancedMenuGUI.Instance.CreatePrefabsForObjects ? " Get coffee this will take some time..." : ""), progressBarInfo, progress); progress += pace; texturePositions.Clear(); TextureReuseManager textureReuseManager = new TextureReuseManager(); string shaderToAtlas = (ObjSorter.GetObjs()[shaderIndex][0] != null && ObjSorter.GetObjs()[shaderIndex][0].IsCorrectlyAssembled) ? ObjSorter.GetObjs()[shaderIndex][0].ShaderName : ""; progressBarInfo = "Processing shader " + shaderToAtlas + "..."; int atlasSize = ObjSorter.GetAproxAtlasSize(shaderIndex, AdvancedMenuGUI.Instance.ReuseTextures); if (ShaderManager.Instance.ShaderExists(shaderToAtlas) && (ObjSorter.GetObjs()[shaderIndex].Count > 1 || (ObjSorter.GetObjs()[shaderIndex].Count == 1 && ObjSorter.GetObjs()[shaderIndex][0] != null && ObjSorter.GetObjs()[shaderIndex][0].ObjHasMoreThanOneMaterial)) && //more than 1 obj or 1obj wth multiple mat atlasSize < Constants.MaxAtlasSize) //check the generated atlas size doesnt exceed max supported texture size { generatedAtlas = new Atlasser(atlasSize, atlasSize); int resizeTimes = 1; for (int j = ObjSorter.GetObjs()[shaderIndex].Count - 1; j >= 0; j--) //start from the largest to the shortest textures //before atlassing multiple materials obj, combine it. { if (ObjSorter.GetObjs()[shaderIndex][j].ObjHasMoreThanOneMaterial) { progressBarInfo = "Combining materials..."; ObjSorter.GetObjs()[shaderIndex][j].ProcessAndCombineMaterials(); //mirar esto, aca esta el problema de multiple materiales y reimportacion } Vector2 textureToAtlasSize = ObjSorter.GetObjs()[shaderIndex][j].TextureSize; if (AdvancedMenuGUI.Instance.ReuseTextures) { //if texture is not registered already if (!textureReuseManager.TextureRefExists(ObjSorter.GetObjs()[shaderIndex][j])) { //generate a node resultNode = generatedAtlas.Insert(Mathf.RoundToInt((textureToAtlasSize.x != Constants.NULLV2.x) ? textureToAtlasSize.x : Constants.NullTextureSize), Mathf.RoundToInt((textureToAtlasSize.y != Constants.NULLV2.y) ? textureToAtlasSize.y : Constants.NullTextureSize)); if (resultNode != null) //save node if fits in atlas { textureReuseManager.AddTextureRef(ObjSorter.GetObjs()[shaderIndex][j], resultNode.NodeRect, j); } } } else { resultNode = generatedAtlas.Insert(Mathf.RoundToInt((textureToAtlasSize.x != Constants.NULLV2.x) ? textureToAtlasSize.x : Constants.NullTextureSize), Mathf.RoundToInt((textureToAtlasSize.y != Constants.NULLV2.y) ? textureToAtlasSize.y : Constants.NullTextureSize)); } if (resultNode == null) { int resizedAtlasSize = atlasSize + Mathf.RoundToInt((float)atlasSize * Constants.AtlasResizeFactor * resizeTimes); generatedAtlas = new Atlasser(resizedAtlasSize, resizedAtlasSize); j = ObjSorter.GetObjs()[shaderIndex].Count; //Count and not .Count-1 bc at the end of the loop it will be substracted j-- and we want to start from Count-1 texturePositions.Clear(); textureReuseManager.ClearTextureRefs(); resizeTimes++; } else { if (AdvancedMenuGUI.Instance.ReuseTextures) { texturePositions.Add(textureReuseManager.GetTextureRefPosition(ObjSorter.GetObjs()[shaderIndex][j])); } else { texturePositions.Add(resultNode.NodeRect); //save the texture rectangle } } } progressBarInfo = "Saving textures to atlas..."; Material atlasMaterial = CreateAtlasMaterialAndTexture(shaderToAtlas, shaderIndex, textureReuseManager); progressBarInfo = "Remapping coordinates..."; ObjSorter.OptimizeDrawCalls(ref atlasMaterial, shaderIndex, generatedAtlas.GetAtlasSize().x, generatedAtlas.GetAtlasSize().y, texturePositions, AdvancedMenuGUI.Instance.ReuseTextures, textureReuseManager, AdvancedMenuGUI.Instance.CreatePrefabsForObjects); } } //after the game object has been organized, remove the combined game objects. for (int shaderIndex = 0; shaderIndex < ObjSorter.GetObjs().Count; shaderIndex++) { for (int j = ObjSorter.GetObjs()[shaderIndex].Count - 1; j >= 0; j--) { if (ObjSorter.GetObjs()[shaderIndex][j].ObjWasCombined) { ObjSorter.GetObjs()[shaderIndex][j].ClearCombinedObject(); } } } EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); //reimport the created atlases so they get displayed in the editor. } }
private void OptimizeDrawCalls(ref Material atlasMaterial, float atlasWidth, float atlasHeight, List <Rect> texturePos, bool reuseTextures, TextureReuseManager texReuseMgr, bool generatePrefabsForObjects) { GameObject trash = new GameObject("Trash");//stores unnecesary objects that might be cloned and are children of objects // // // when generating prefabs // // // string folderToSavePrefabs = EditorApplication.currentScene; if (generatePrefabsForObjects) { if (folderToSavePrefabs == "") //scene is not saved yet. { folderToSavePrefabs = Constants.NonSavedSceneFolderName + ".unity"; } folderToSavePrefabs = folderToSavePrefabs.Substring(0, folderToSavePrefabs.Length - 6) + "-Atlas";//remove the ".unity" folderToSavePrefabs += Path.DirectorySeparatorChar + "Prefabs"; if (!Directory.Exists(folderToSavePrefabs)) { Directory.CreateDirectory(folderToSavePrefabs); AssetDatabase.Refresh(); } } /////////////////////////////////////////// for (int i = 0; i < objects.Count; i++) { string optimizedObjID = objects[i].GameObj.name + Constants.OptimizedObjIdentifier; objects[i].GameObj.GetComponent <MeshRenderer>().enabled = true;//activate renderers for instantiating GameObject instance = GameObject.Instantiate(objects[i].GameObj, objects[i].GameObj.transform.position, objects[i].GameObj.transform.rotation) as GameObject; Undo.RegisterCreatedObjectUndo(instance, "CreateObj" + optimizedObjID); //remove children of the created instance. Transform[] children = instance.GetComponentsInChildren <Transform>(); for (int j = 0; j < children.Length; j++) { children[j].transform.parent = trash.transform; } instance.transform.parent = objects[i].GameObj.transform.parent; instance.transform.localScale = objects[i].GameObj.transform.localScale; instance.GetComponent <Renderer>().sharedMaterial = atlasMaterial; instance.name = optimizedObjID; instance.GetComponent <MeshFilter>().sharedMesh = Utils.CopyMesh(objects[i].GameObj.GetComponent <MeshFilter>().sharedMesh); //Remap uvs Mesh remappedMesh = instance.GetComponent <MeshFilter>().sharedMesh; Vector2[] remappedUVs = instance.GetComponent <MeshFilter>().sharedMesh.uv; for (int j = 0; j < remappedUVs.Length; j++) { if (reuseTextures) { remappedUVs[j] = Utils.ReMapUV(remappedUVs[j], atlasWidth, atlasHeight, texReuseMgr.GetTextureRefPosition(objects[i]), instance.name); } else { remappedUVs[j] = Utils.ReMapUV(remappedUVs[j], atlasWidth, atlasHeight, texturePos[i], instance.name); } } remappedMesh.uv = remappedUVs; instance.GetComponent <MeshFilter>().sharedMesh = remappedMesh; Undo.RecordObject(objects[i].GameObj.GetComponent <MeshRenderer>(), "Active Obj"); //if the gameObject has multiple materials, search for the original one (the uncombined) in order to deactivate it if (objects[i].ObjWasCombined) { objects[i].UncombinedObject.GetComponent <MeshRenderer>().enabled = false; } else { objects[i].GameObj.GetComponent <MeshRenderer>().enabled = false; } if (generatePrefabsForObjects) { string prefabName = Utils.GetValiName(instance.name) + " " + instance.GetInstanceID(); string assetPath = folderToSavePrefabs + Path.DirectorySeparatorChar + prefabName; AssetDatabase.CreateAsset(instance.GetComponent <MeshFilter>().sharedMesh, assetPath + ".asset"); PrefabUtility.CreatePrefab(assetPath + ".prefab", instance, ReplacePrefabOptions.ConnectToPrefab); } } GameObject.DestroyImmediate(trash); }
//Optimizable object slice is a slice of the optimizableObjects in case they dont fit in a simple atlas private void OptimizeDrawCalls(List <OptimizableObject> objectsToOptimizeSlice, ref Material atlasMaterial, float atlasWidth, float atlasHeight, List <Rect> texturePos, bool reuseTextures, TextureReuseManager texReuseMgr, bool generatePrefabsForObjects) { GameObject trash = new GameObject("Trash");//stores unnecesary objects that might be cloned and are children of objects for (int i = 0; i < objectsToOptimizeSlice.Count; i++) { string optimizedObjStrID = objectsToOptimizeSlice[i].GameObj.name + Constants.OptimizedObjIdentifier; if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { objectsToOptimizeSlice[i].GameObj.GetComponent <SkinnedMeshRenderer>().enabled = true;//activate renderers for instantiating } else { objectsToOptimizeSlice[i].GameObj.GetComponent <MeshRenderer>().enabled = true; } GameObject instance = GameObject.Instantiate(objectsToOptimizeSlice[i].GameObj, objectsToOptimizeSlice[i].GameObj.transform.position, objectsToOptimizeSlice[i].GameObj.transform.rotation) as GameObject; Undo.RegisterCreatedObjectUndo(instance, "CreateObj" + optimizedObjStrID); //remove children of the created instance. Transform[] children = instance.GetComponentsInChildren <Transform>(); for (int j = 0; j < children.Length; j++) { children[j].transform.parent = trash.transform; } instance.transform.parent = objectsToOptimizeSlice[i].GameObj.transform.parent; instance.transform.localScale = objectsToOptimizeSlice[i].GameObj.transform.localScale; if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { instance.GetComponent <SkinnedMeshRenderer>().sharedMaterial = atlasMaterial; } else { instance.GetComponent <MeshRenderer>().sharedMaterial = atlasMaterial; } instance.name = optimizedObjStrID; if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { instance.GetComponent <SkinnedMeshRenderer>().sharedMesh = Utils.CopyMesh(objectsToOptimizeSlice[i].GameObj.GetComponent <SkinnedMeshRenderer>().sharedMesh); } else { instance.GetComponent <MeshFilter>().sharedMesh = Utils.CopyMesh(objectsToOptimizeSlice[i].GameObj.GetComponent <MeshFilter>().sharedMesh); } // ************************************ Remap uvs ***************************************** // Mesh remappedMesh = objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer ? instance.GetComponent <SkinnedMeshRenderer>().sharedMesh : instance.GetComponent <MeshFilter>().sharedMesh; Vector2[] remappedUVs = remappedMesh.uv;//objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer ? instance.GetComponent<SkinnedMeshRenderer>().sharedMesh.uv : instance.GetComponent<MeshFilter>().sharedMesh.uv; Vector2[] remappedUVs2 = remappedMesh.uv2; Vector2[] remappedUVs3 = remappedMesh.uv3; Vector2[] remappedUVs4 = remappedMesh.uv4; bool hasUv2Channel = remappedUVs2.Length > 0; bool hasUv3Channel = remappedUVs3.Length > 0; bool hasUv4Channel = remappedUVs4.Length > 0; bool generatedTexture = objectsToOptimizeSlice[i].MainTexture == null; for (int j = 0; j < remappedUVs.Length; j++) { if (reuseTextures) { if (SettingsMenuGUI.Instance.ModifyMainUV) { remappedUVs[j] = Utils.ReMapUV(remappedUVs[j], atlasWidth, atlasHeight, texReuseMgr.GetTextureRefPosition(objectsToOptimizeSlice[i]), instance.name, generatedTexture); } if (hasUv2Channel && SettingsMenuGUI.Instance.ModifyUV2) { remappedUVs2[j] = Utils.ReMapUV(remappedUVs2[j], atlasWidth, atlasHeight, texReuseMgr.GetTextureRefPosition(objectsToOptimizeSlice[i]), instance.name, generatedTexture); } if (hasUv3Channel && SettingsMenuGUI.Instance.ModifyUV3) { remappedUVs3[j] = Utils.ReMapUV(remappedUVs3[j], atlasWidth, atlasHeight, texReuseMgr.GetTextureRefPosition(objectsToOptimizeSlice[i]), instance.name, generatedTexture); } if (hasUv4Channel && SettingsMenuGUI.Instance.ModifyUV4) { remappedUVs4[j] = Utils.ReMapUV(remappedUVs4[j], atlasWidth, atlasHeight, texReuseMgr.GetTextureRefPosition(objectsToOptimizeSlice[i]), instance.name, generatedTexture); } } else { if (SettingsMenuGUI.Instance.ModifyMainUV) { remappedUVs[j] = Utils.ReMapUV(remappedUVs[j], atlasWidth, atlasHeight, texturePos[i], instance.name, generatedTexture); } if (hasUv2Channel && SettingsMenuGUI.Instance.ModifyUV2) { remappedUVs2[j] = Utils.ReMapUV(remappedUVs2[j], atlasWidth, atlasHeight, texturePos[i], instance.name, generatedTexture); } if (hasUv3Channel && SettingsMenuGUI.Instance.ModifyUV3) { remappedUVs3[j] = Utils.ReMapUV(remappedUVs3[j], atlasWidth, atlasHeight, texturePos[i], instance.name, generatedTexture); } if (hasUv4Channel && SettingsMenuGUI.Instance.ModifyUV4) { remappedUVs4[j] = Utils.ReMapUV(remappedUVs4[j], atlasWidth, atlasHeight, texturePos[i], instance.name, generatedTexture); } } } remappedMesh.uv = remappedUVs; if (hasUv2Channel) { remappedMesh.uv2 = remappedUVs2; } if (hasUv3Channel) { remappedMesh.uv3 = remappedUVs3; } if (hasUv4Channel) { remappedMesh.uv4 = remappedUVs4; } if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { instance.GetComponent <SkinnedMeshRenderer>().sharedMesh = remappedMesh; Undo.RecordObject(objectsToOptimizeSlice[i].GameObj.GetComponent <SkinnedMeshRenderer>(), "Active Obj"); } else { instance.GetComponent <MeshFilter>().sharedMesh = remappedMesh; Undo.RecordObject(objectsToOptimizeSlice[i].GameObj.GetComponent <MeshRenderer>(), "Active Obj"); } //if the gameObject has multiple materials, search for the original one (the uncombined) in order to deactivate it if (objectsToOptimizeSlice[i].ObjWasCombined) { if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { objectsToOptimizeSlice[i].UncombinedObject.GetComponent <SkinnedMeshRenderer>().enabled = false; } else { objectsToOptimizeSlice[i].UncombinedObject.GetComponent <MeshRenderer>().enabled = false; } } else { if (objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer) { objectsToOptimizeSlice[i].GameObj.GetComponent <SkinnedMeshRenderer>().enabled = false; } else { objectsToOptimizeSlice[i].GameObj.GetComponent <MeshRenderer>().enabled = false; } } if (generatePrefabsForObjects && !combineMeshesFlags[i])//lets not generate a prefab for an object that is marked for combine as later on will be combined and made a prefab { string prefabName = Utils.GetValiName(instance.name) + " " + instance.GetInstanceID(); string assetPath = folderToSavePrefabs + Path.DirectorySeparatorChar + prefabName; Utils.GeneratePrefab(instance, assetPath, objectsToOptimizeSlice[i].UsesSkinnedMeshRenderer); } //useful only when building hierarchies //instanceID of the transform as we are comparing against parent transforms when building hierachies int originalOptimizedObjectInstanceID = objectsToOptimizeSlice[i].ObjWasCombined ? objectsToOptimizeSlice[i].UncombinedObject.transform.GetInstanceID() : objectsToOptimizeSlice[i].GameObj.transform.GetInstanceID(); optimizedObjects.Add(new Tuple <GameObject, int>(instance, originalOptimizedObjectInstanceID)); } GameObject.DestroyImmediate(trash); }