//---------------------------------------------- // MeshBaker // Copyright © 2011-2012 Ian Deane //---------------------------------------------- using UnityEngine; using System.Collections; using System.Collections.Specialized; using System; using System.Collections.Generic; using System.Text; using System.IO; using DigitalOpus.MB.Core; /// /// Component that handles baking materials into a combined material. /// /// The result of the material baking process is a MB2_TextureBakeResults object, which /// becomes the input for the mesh baking. /// /// This class uses the MB_TextureCombiner to do the combining. /// /// This class is a Component (MonoBehavior) so it is serialized and found using GetComponent. If /// you want to access the texture baking functionality without creating a Component then use MB_TextureCombiner /// directly. /// public class MB3_TextureBaker : MB3_MeshBakerRoot { public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; [SerializeField] protected MB2_TextureBakeResults _textureBakeResults; public override MB2_TextureBakeResults textureBakeResults { get { return _textureBakeResults; } set { _textureBakeResults = value; } } [SerializeField] protected int _atlasPadding = 1; public virtual int atlasPadding { get { return _atlasPadding; } set { _atlasPadding = value; } } [SerializeField] protected int _maxAtlasSize = 4096; public virtual int maxAtlasSize { get { return _maxAtlasSize; } set { _maxAtlasSize = value; } } [SerializeField] protected bool _useMaxAtlasWidthOverride = false; public virtual bool useMaxAtlasWidthOverride { get { return _useMaxAtlasWidthOverride; } set { _useMaxAtlasWidthOverride = value; } } [SerializeField] protected int _maxAtlasWidthOverride = 4096; public virtual int maxAtlasWidthOverride { get { return _maxAtlasWidthOverride; } set { _maxAtlasWidthOverride = value; } } [SerializeField] protected bool _useMaxAtlasHeightOverride = false; public virtual bool useMaxAtlasHeightOverride { get { return _useMaxAtlasHeightOverride; } set { _useMaxAtlasHeightOverride = value; } } [SerializeField] protected int _maxAtlasHeightOverride = 4096; public virtual int maxAtlasHeightOverride { get { return _maxAtlasHeightOverride; } set { _maxAtlasHeightOverride = value; } } [SerializeField] protected bool _resizePowerOfTwoTextures = false; public virtual bool resizePowerOfTwoTextures { get { return _resizePowerOfTwoTextures; } set { _resizePowerOfTwoTextures = value; } } [SerializeField] protected bool _fixOutOfBoundsUVs = false; //is considerMeshUVs but can't change because it would break all existing bakers public virtual bool fixOutOfBoundsUVs { get { return _fixOutOfBoundsUVs; } set { _fixOutOfBoundsUVs = value; } } [SerializeField] protected int _maxTilingBakeSize = 1024; public virtual int maxTilingBakeSize { get { return _maxTilingBakeSize; } set { _maxTilingBakeSize = value; } } [SerializeField] protected MB2_PackingAlgorithmEnum _packingAlgorithm = MB2_PackingAlgorithmEnum.MeshBakerTexturePacker; public virtual MB2_PackingAlgorithmEnum packingAlgorithm { get { return _packingAlgorithm; } set { _packingAlgorithm = value; } } [SerializeField] protected bool _meshBakerTexturePackerForcePowerOfTwo = true; public bool meshBakerTexturePackerForcePowerOfTwo { get { return _meshBakerTexturePackerForcePowerOfTwo; } set { _meshBakerTexturePackerForcePowerOfTwo = value; } } [SerializeField] protected List _customShaderProperties = new List(); public virtual List customShaderProperties { get { return _customShaderProperties; } set { _customShaderProperties = value; } } //this is depricated [SerializeField] protected List _customShaderPropNames_Depricated = new List(); public virtual List customShaderPropNames { get { return _customShaderPropNames_Depricated; } set { _customShaderPropNames_Depricated = value; } } [SerializeField] protected bool _doMultiMaterial; public virtual bool doMultiMaterial { get { return _doMultiMaterial; } set { _doMultiMaterial = value; } } [SerializeField] protected bool _doMultiMaterialSplitAtlasesIfTooBig = true; public virtual bool doMultiMaterialSplitAtlasesIfTooBig { get { return _doMultiMaterialSplitAtlasesIfTooBig; } set { _doMultiMaterialSplitAtlasesIfTooBig = value; } } [SerializeField] protected bool _doMultiMaterialSplitAtlasesIfOBUVs = true; public virtual bool doMultiMaterialSplitAtlasesIfOBUVs { get { return _doMultiMaterialSplitAtlasesIfOBUVs; } set { _doMultiMaterialSplitAtlasesIfOBUVs = value; } } [SerializeField] protected Material _resultMaterial; public virtual Material resultMaterial { get { return _resultMaterial; } set { _resultMaterial = value; } } [SerializeField] protected bool _considerNonTextureProperties = false; public bool considerNonTextureProperties { get { return _considerNonTextureProperties; } set { _considerNonTextureProperties = value; } } [SerializeField] protected bool _doSuggestTreatment = true; public bool doSuggestTreatment { get { return _doSuggestTreatment; } set { _doSuggestTreatment = value; } } private CreateAtlasesCoroutineResult _coroutineResult; public CreateAtlasesCoroutineResult CoroutineResult { get { return _coroutineResult; } } public MB_MultiMaterial[] resultMaterials = new MB_MultiMaterial[0]; public List objsToMesh; //todo make this Renderer public override List GetObjectsToCombine() { if (objsToMesh == null) objsToMesh = new List(); return objsToMesh; } public MB_AtlasesAndRects[] CreateAtlases() { return CreateAtlases(null, false, null); } public delegate void OnCombinedTexturesCoroutineSuccess(); public delegate void OnCombinedTexturesCoroutineFail(); public OnCombinedTexturesCoroutineSuccess onBuiltAtlasesSuccess; public OnCombinedTexturesCoroutineFail onBuiltAtlasesFail; public MB_AtlasesAndRects[] OnCombinedTexturesCoroutineAtlasesAndRects; /* bool _CreateAtlasesCoroutineSuccess = false; public bool CreateAtlasesCoroutineSuccess { get { return _CreateAtlasesCoroutineSuccess; } } bool _CreateAtlasesCoroutineIsFinished = false; public bool CreateAtlasesCoroutineIsFinished { get { return _CreateAtlasesCoroutineIsFinished; } } */ public class CreateAtlasesCoroutineResult { public bool success = true; public bool isFinished = false; } public IEnumerator CreateAtlasesCoroutine(ProgressUpdateDelegate progressInfo, CreateAtlasesCoroutineResult coroutineResult, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null, float maxTimePerFrame = .01f) { MBVersionConcrete mbv = new MBVersionConcrete (); if (!MB3_TextureCombiner._RunCorutineWithoutPauseIsRunning &&( mbv.GetMajorVersion() < 5 ||(mbv.GetMajorVersion() == 5 && mbv.GetMinorVersion() < 3))){ Debug.LogError("Running the texture combiner as a coroutine only works in Unity 5.3 and higher"); coroutineResult.success = false; yield break; } this.OnCombinedTexturesCoroutineAtlasesAndRects = null; if (maxTimePerFrame <= 0f) { Debug.LogError("maxTimePerFrame must be a value greater than zero"); coroutineResult.isFinished = true; yield break; } MB2_ValidationLevel vl = Application.isPlaying ? MB2_ValidationLevel.quick : MB2_ValidationLevel.robust; if (!DoCombinedValidate(this, MB_ObjsToCombineTypes.dontCare, null, vl)) { coroutineResult.isFinished = true; yield break; } if (_doMultiMaterial && !_ValidateResultMaterials()) { coroutineResult.isFinished = true; yield break; } else if (!_doMultiMaterial) { if (_resultMaterial == null) { Debug.LogError("Combined Material is null please create and assign a result material."); coroutineResult.isFinished = true; yield break; } Shader targShader = _resultMaterial.shader; for (int i = 0; i < objsToMesh.Count; i++) { Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); for (int j = 0; j < ms.Length; j++) { Material m = ms[j]; if (m != null && m.shader != targShader) { Debug.LogWarning("Game object " + objsToMesh[i] + " does not use shader " + targShader + " it may not have the required textures. If not small solid color textures will be generated."); } } } } MB3_TextureCombiner combiner = CreateAndConfigureTextureCombiner(); combiner.saveAtlasesAsAssets = saveAtlasesAsAssets; //initialize structure to store results int numResults = 1; if (_doMultiMaterial) numResults = resultMaterials.Length; OnCombinedTexturesCoroutineAtlasesAndRects = new MB_AtlasesAndRects[numResults]; for (int i = 0; i < OnCombinedTexturesCoroutineAtlasesAndRects.Length; i++) { OnCombinedTexturesCoroutineAtlasesAndRects[i] = new MB_AtlasesAndRects(); } //Do the material combining. for (int i = 0; i < OnCombinedTexturesCoroutineAtlasesAndRects.Length; i++) { Material resMatToPass = null; List sourceMats = null; if (_doMultiMaterial) { sourceMats = resultMaterials[i].sourceMaterials; resMatToPass = resultMaterials[i].combinedMaterial; combiner.fixOutOfBoundsUVs = resultMaterials[i].considerMeshUVs; } else { resMatToPass = _resultMaterial; } MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult coroutineResult2 = new MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult(); yield return combiner.CombineTexturesIntoAtlasesCoroutine(progressInfo, OnCombinedTexturesCoroutineAtlasesAndRects[i], resMatToPass, objsToMesh, sourceMats, editorMethods, coroutineResult2, maxTimePerFrame); coroutineResult.success = coroutineResult2.success; if (!coroutineResult.success) { coroutineResult.isFinished = true; yield break; } } unpackMat2RectMap(textureBakeResults); //Save the results textureBakeResults.doMultiMaterial = _doMultiMaterial; //textureBakeResults.resultMaterial = _resultMaterial; if (_doMultiMaterial) { textureBakeResults.resultMaterials = resultMaterials; } else { MB_MultiMaterial[] resMats = new MB_MultiMaterial[1]; resMats[0] = new MB_MultiMaterial(); resMats[0].combinedMaterial = _resultMaterial; resMats[0].considerMeshUVs = _fixOutOfBoundsUVs; resMats[0].sourceMaterials = new List(); for (int i = 0; i < textureBakeResults.materialsAndUVRects.Length; i++) { resMats[0].sourceMaterials.Add(textureBakeResults.materialsAndUVRects[i].material); } textureBakeResults.resultMaterials = resMats; } //textureBakeResults.fixOutOfBoundsUVs = combiner.fixOutOfBoundsUVs; //set the texture bake resultAtlasesAndRects on the Mesh Baker component if it exists MB3_MeshBakerCommon[] mb = GetComponentsInChildren(); for (int i = 0; i < mb.Length; i++) { mb[i].textureBakeResults = textureBakeResults; } if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Created Atlases"); coroutineResult.isFinished = true; if (coroutineResult.success && onBuiltAtlasesSuccess != null) { onBuiltAtlasesSuccess(); } if (!coroutineResult.success && onBuiltAtlasesFail != null) { onBuiltAtlasesFail(); } } /// /// Creates the atlases. /// /// /// The atlases. /// /// /// Progress info is a delegate function that displays a progress dialog. Can be null /// /// /// if true atlases are saved as assets in the project folder. Othersise they are instances in memory /// /// /// Texture format tracker. Contains editor functionality such as save assets. Can be null. /// public MB_AtlasesAndRects[] CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null) { MB_AtlasesAndRects[] mAndAs = null; try { //mAndAs = _CreateAtlases(progressInfo, saveAtlasesAsAssets, editorMethods); _coroutineResult = new CreateAtlasesCoroutineResult(); MB3_TextureCombiner.RunCorutineWithoutPause(CreateAtlasesCoroutine(progressInfo, _coroutineResult, saveAtlasesAsAssets, editorMethods, 1000f), 0); if (_coroutineResult.success && textureBakeResults != null) { mAndAs = this.OnCombinedTexturesCoroutineAtlasesAndRects; } } catch (Exception e) { Debug.LogError(e); } finally { if (saveAtlasesAsAssets) { //Atlases were saved to project so we don't need these ones if (mAndAs != null) { for (int j = 0; j < mAndAs.Length; j++) { MB_AtlasesAndRects mAndA = mAndAs[j]; if (mAndA != null && mAndA.atlases != null) { for (int i = 0; i < mAndA.atlases.Length; i++) { if (mAndA.atlases[i] != null) { if (editorMethods != null) editorMethods.Destroy(mAndA.atlases[i]); else MB_Utility.Destroy(mAndA.atlases[i]); } } } } } } } return mAndAs; } void unpackMat2RectMap(MB2_TextureBakeResults tbr) { List ms = new List(); List mss = new List(); List rs = new List(); for (int i = 0; i < OnCombinedTexturesCoroutineAtlasesAndRects.Length; i++) { MB_AtlasesAndRects newMesh = this.OnCombinedTexturesCoroutineAtlasesAndRects[i]; List map = newMesh.mat2rect_map; if (map != null) { for (int j = 0; j < map.Count; j++) { mss.Add(map[j]); ms.Add(map[j].material); rs.Add(map[j].atlasRect); } } } tbr.version = MB2_TextureBakeResults.VERSION; tbr.materialsAndUVRects = mss.ToArray(); } public MB3_TextureCombiner CreateAndConfigureTextureCombiner() { MB3_TextureCombiner combiner = new MB3_TextureCombiner(); combiner.LOG_LEVEL = LOG_LEVEL; combiner.atlasPadding = _atlasPadding; combiner.maxAtlasSize = _maxAtlasSize; combiner.maxAtlasHeightOverride = _maxAtlasHeightOverride; combiner.maxAtlasWidthOverride = _maxAtlasWidthOverride; combiner.useMaxAtlasHeightOverride = _useMaxAtlasHeightOverride; combiner.useMaxAtlasWidthOverride = _useMaxAtlasWidthOverride; combiner.customShaderPropNames = _customShaderProperties; combiner.fixOutOfBoundsUVs = _fixOutOfBoundsUVs; combiner.maxTilingBakeSize = _maxTilingBakeSize; combiner.packingAlgorithm = _packingAlgorithm; combiner.meshBakerTexturePackerForcePowerOfTwo = _meshBakerTexturePackerForcePowerOfTwo; combiner.resizePowerOfTwoTextures = _resizePowerOfTwoTextures; combiner.considerNonTextureProperties = _considerNonTextureProperties; return combiner; } public static void ConfigureNewMaterialToMatchOld(Material newMat, Material original) { if (original == null) { Debug.LogWarning("Original material is null, could not copy properties to " + newMat + ". Setting shader to " + newMat.shader); return; } newMat.shader = original.shader; newMat.CopyPropertiesFromMaterial(original); ShaderTextureProperty[] texPropertyNames = MB3_TextureCombinerPipeline.shaderTexPropertyNames; for (int j = 0; j < texPropertyNames.Length; j++) { Vector2 scale = Vector2.one; Vector2 offset = Vector2.zero; if (newMat.HasProperty(texPropertyNames[j].name)) { newMat.SetTextureOffset(texPropertyNames[j].name, offset); newMat.SetTextureScale(texPropertyNames[j].name, scale); } } } string PrintSet(HashSet s) { StringBuilder sb = new StringBuilder(); foreach (Material m in s) { sb.Append(m + ","); } return sb.ToString(); } bool _ValidateResultMaterials() { HashSet allMatsOnObjs = new HashSet(); for (int i = 0; i < objsToMesh.Count; i++) { if (objsToMesh[i] != null) { Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); for (int j = 0; j < ms.Length; j++) { if (ms[j] != null) allMatsOnObjs.Add(ms[j]); } } } HashSet allMatsInMapping = new HashSet(); for (int i = 0; i < resultMaterials.Length; i++) { for (int j = i+1; j < resultMaterials.Length; j++) { if (resultMaterials[i].combinedMaterial == resultMaterials[j].combinedMaterial) { Debug.LogError(String.Format("Source To Combined Mapping: Submesh {0} and Submesh {1} use the same combined material. These should be different", i, j)); return false; } } MB_MultiMaterial mm = resultMaterials[i]; if (mm.combinedMaterial == null) { Debug.LogError("Combined Material is null please create and assign a result material."); return false; } Shader targShader = mm.combinedMaterial.shader; for (int j = 0; j < mm.sourceMaterials.Count; j++) { if (mm.sourceMaterials[j] == null) { Debug.LogError("There are null entries in the list of Source Materials"); return false; } if (targShader != mm.sourceMaterials[j].shader) { Debug.LogWarning("Source material " + mm.sourceMaterials[j] + " does not use shader " + targShader + " it may not have the required textures. If not empty textures will be generated."); } if (allMatsInMapping.Contains(mm.sourceMaterials[j])) { Debug.LogError("A Material " + mm.sourceMaterials[j] + " appears more than once in the list of source materials in the source material to combined mapping. Each source material must be unique."); return false; } allMatsInMapping.Add(mm.sourceMaterials[j]); } } if (allMatsOnObjs.IsProperSubsetOf(allMatsInMapping)) { allMatsInMapping.ExceptWith(allMatsOnObjs); Debug.LogWarning("There are materials in the mapping that are not used on your source objects: " + PrintSet(allMatsInMapping)); } if (resultMaterials != null && resultMaterials.Length > 0 && allMatsInMapping.IsProperSubsetOf(allMatsOnObjs)) { allMatsOnObjs.ExceptWith(allMatsInMapping); Debug.LogError("There are materials on the objects to combine that are not in the mapping: " + PrintSet(allMatsOnObjs)); return false; } return true; } }