//posibilities // using fixOutOfBoundsUVs or not // public static void ConfigureMutiMaterialsFromObjsToCombine(MB3_TextureBaker mom, SerializedProperty resultMaterials, SerializedObject textureBaker) { if (mom.GetObjectsToCombine().Count == 0) { Debug.LogError("You need to add some objects to combine before building the multi material list."); return; } if (resultMaterials.arraySize > 0) { Debug.LogError("You already have some source to combined material mappings configured. You must remove these before doing this operation."); return; } if (mom.textureBakeResults == null) { Debug.LogError("Texture Bake Result asset must be set before using this operation."); return; } Dictionary <MultiMatSubmeshInfo, List <List <Material> > > shader2Material_map = new Dictionary <MultiMatSubmeshInfo, List <List <Material> > >(); Dictionary <Material, Mesh> obUVobject2mesh_map = new Dictionary <Material, Mesh>(); //validate that the objects to be combined are valid for (int i = 0; i < mom.GetObjectsToCombine().Count; i++) { GameObject go = mom.GetObjectsToCombine()[i]; if (go == null) { Debug.LogError("Null object in list of objects to combine at position " + i); return; } Renderer r = go.GetComponent <Renderer>(); if (r == null || (!(r is MeshRenderer) && !(r is SkinnedMeshRenderer))) { Debug.LogError("GameObject at position " + i + " in list of objects to combine did not have a renderer"); return; } if (r.sharedMaterial == null) { Debug.LogError("GameObject at position " + i + " in list of objects to combine has a null material"); return; } } //first pass put any meshes with obUVs on their own submesh if not fixing OB uvs if (mom.doMultiMaterialSplitAtlasesIfOBUVs) { for (int i = 0; i < mom.GetObjectsToCombine().Count; i++) { GameObject go = mom.GetObjectsToCombine()[i]; Mesh m = MB_Utility.GetMesh(go); MB_Utility.MeshAnalysisResult dummyMar = new MB_Utility.MeshAnalysisResult(); Renderer r = go.GetComponent <Renderer>(); for (int j = 0; j < r.sharedMaterials.Length; j++) { if (MB_Utility.hasOutOfBoundsUVs(m, ref dummyMar, j)) { if (!obUVobject2mesh_map.ContainsKey(r.sharedMaterials[j])) { Debug.LogWarning("Object " + go + " submesh " + j + " uses UVs outside the range 0,0..1,1 to generate tiling. This object has been mapped to its own submesh in the combined mesh. It can share a submesh with other objects that use different materials if you use the fix out of bounds UVs feature which will bake the tiling"); obUVobject2mesh_map.Add(r.sharedMaterials[j], m); } } } } } //second pass put other materials without OB uvs in a shader to material map for (int i = 0; i < mom.GetObjectsToCombine().Count; i++) { Renderer r = mom.GetObjectsToCombine()[i].GetComponent <Renderer>(); for (int j = 0; j < r.sharedMaterials.Length; j++) { if (!obUVobject2mesh_map.ContainsKey(r.sharedMaterials[j])) { //if not already added if (r.sharedMaterials[j] == null) { continue; } List <List <Material> > binsOfMatsThatUseShader = null; MultiMatSubmeshInfo newKey = new MultiMatSubmeshInfo(r.sharedMaterials[j].shader, r.sharedMaterials[j]); if (!shader2Material_map.TryGetValue(newKey, out binsOfMatsThatUseShader)) { binsOfMatsThatUseShader = new List <List <Material> >(); binsOfMatsThatUseShader.Add(new List <Material>()); shader2Material_map.Add(newKey, binsOfMatsThatUseShader); } if (!binsOfMatsThatUseShader[0].Contains(r.sharedMaterials[j])) { binsOfMatsThatUseShader[0].Add(r.sharedMaterials[j]); } } } } int numResMats = shader2Material_map.Count; //third pass for each shader grouping check how big the atlas would be and group into bins that would fit in an atlas if (mom.doMultiMaterialSplitAtlasesIfTooBig) { if (mom.packingAlgorithm == MB2_PackingAlgorithmEnum.UnitysPackTextures) { Debug.LogWarning("Unity texture packer does not support splitting atlases if too big. Atlases will not be split."); } else { numResMats = 0; foreach (MultiMatSubmeshInfo sh in shader2Material_map.Keys) { List <List <Material> > binsOfMatsThatUseShader = shader2Material_map[sh]; List <Material> allMatsThatUserShader = binsOfMatsThatUseShader[0];//at this point everything is in the same list binsOfMatsThatUseShader.RemoveAt(0); MB3_TextureCombiner combiner = mom.CreateAndConfigureTextureCombiner(); combiner.saveAtlasesAsAssets = false; if (allMatsThatUserShader.Count > 1) { combiner.fixOutOfBoundsUVs = mom.fixOutOfBoundsUVs; } else { combiner.fixOutOfBoundsUVs = false; } // Do the texture pack List <AtlasPackingResult> packingResults = new List <AtlasPackingResult>(); Material tempMat = new Material(sh.shader); combiner.CombineTexturesIntoAtlases(null, null, tempMat, mom.GetObjectsToCombine(), allMatsThatUserShader, null, packingResults, true, true); for (int i = 0; i < packingResults.Count; i++) { List <MatsAndGOs> matsData = (List <MatsAndGOs>)packingResults[i].data; List <Material> mats = new List <Material>(); for (int j = 0; j < matsData.Count; j++) { for (int kk = 0; kk < matsData[j].mats.Count; kk++) { if (!mats.Contains(matsData[j].mats[kk].mat)) { mats.Add(matsData[j].mats[kk].mat); } } } binsOfMatsThatUseShader.Add(mats); } numResMats += binsOfMatsThatUseShader.Count; } } } //build the result materials if (shader2Material_map.Count == 0 && obUVobject2mesh_map.Count == 0) { Debug.LogError("Found no materials in list of objects to combine"); } mom.resultMaterials = new MB_MultiMaterial[numResMats + obUVobject2mesh_map.Count]; string pth = AssetDatabase.GetAssetPath(mom.textureBakeResults); string baseName = Path.GetFileNameWithoutExtension(pth); string folderPath = pth.Substring(0, pth.Length - baseName.Length - 6); int k = 0; foreach (MultiMatSubmeshInfo sh in shader2Material_map.Keys) { foreach (List <Material> matsThatUse in shader2Material_map[sh]) { MB_MultiMaterial mm = mom.resultMaterials[k] = new MB_MultiMaterial(); mm.sourceMaterials = matsThatUse; if (mm.sourceMaterials.Count == 1) { mm.considerMeshUVs = false; } else { mm.considerMeshUVs = mom.fixOutOfBoundsUVs; } string matName = folderPath + baseName + "-mat" + k + ".mat"; Material newMat = new Material(Shader.Find("Diffuse")); if (matsThatUse.Count > 0 && matsThatUse[0] != null) { MB3_TextureBaker.ConfigureNewMaterialToMatchOld(newMat, matsThatUse[0]); } AssetDatabase.CreateAsset(newMat, matName); mm.combinedMaterial = (Material)AssetDatabase.LoadAssetAtPath(matName, typeof(Material)); k++; } } foreach (Material m in obUVobject2mesh_map.Keys) { MB_MultiMaterial mm = mom.resultMaterials[k] = new MB_MultiMaterial(); mm.sourceMaterials = new List <Material>(); mm.sourceMaterials.Add(m); mm.considerMeshUVs = false; string matName = folderPath + baseName + "-mat" + k + ".mat"; Material newMat = new Material(Shader.Find("Diffuse")); MB3_TextureBaker.ConfigureNewMaterialToMatchOld(newMat, m); AssetDatabase.CreateAsset(newMat, matName); mm.combinedMaterial = (Material)AssetDatabase.LoadAssetAtPath(matName, typeof(Material)); k++; } MBVersionEditor.UpdateIfDirtyOrScript(textureBaker); }
//a material can appear more than once in an atlas if using fixOutOfBoundsUVs. //in this case need to use the UV rect of the mesh to find the correct rectangle. public bool TryMapMaterialToUVRect(Material mat, Mesh m, int submeshIdx, int idxInResultMats, MB3_MeshCombinerSingle.MeshChannelsCache meshChannelCache, Dictionary <int, MB_Utility.MeshAnalysisResult[]> meshAnalysisCache, out Rect rectInAtlas, //out Rect subrectInAtlasMatTiling, out Rect encapsulatingRect, out Rect sourceMaterialTilingOut, ref String errorMsg, MB2_LogLevel logLevel) { if (tbr.materialsAndUVRects.Length == 0 && tbr.materials.Length > 0) { errorMsg = "The 'Texture Bake Result' needs to be re-baked to be compatible with this version of Mesh Baker. Please re-bake using the MB3_TextureBaker."; rectInAtlas = new Rect(); //subrectInAtlasMatTiling = new Rect(); encapsulatingRect = new Rect(); sourceMaterialTilingOut = new Rect(); return(false); } if (mat == null) { rectInAtlas = new Rect(); //subrectInAtlasMatTiling = new Rect(); encapsulatingRect = new Rect(); sourceMaterialTilingOut = new Rect(); errorMsg = String.Format("Mesh {0} Had no material on submesh {1} cannot map to a material in the atlas", m.name, submeshIdx); return(false); } if (submeshIdx >= m.subMeshCount) { errorMsg = "Submesh index is greater than the number of submeshes"; rectInAtlas = new Rect(); //subrectInAtlasMatTiling = new Rect(); encapsulatingRect = new Rect(); sourceMaterialTilingOut = new Rect(); return(false); } //find the first index of this material int idx = -1; for (int i = 0; i < matsAndSrcUVRect.Length; i++) { if (mat == matsAndSrcUVRect[i].material) { idx = i; break; } } // if couldn't find material if (idx == -1) { rectInAtlas = new Rect(); //subrectInAtlasMatTiling = new Rect(); encapsulatingRect = new Rect(); sourceMaterialTilingOut = new Rect(); errorMsg = String.Format("Material {0} could not be found in the Texture Bake Result", mat.name); return(false); } if (!tbr.resultMaterials[idxInResultMats].considerMeshUVs) { if (numTimesMatAppearsInAtlas[idx] != 1) { Debug.LogError("There is a problem with this TextureBakeResults. FixOutOfBoundsUVs is false and a material appears more than once."); } rectInAtlas = matsAndSrcUVRect[idx].atlasRect; //subrectInAtlasMatTiling = matsAndSrcUVRect[idx].atlasSubrectMaterialOnly; encapsulatingRect = matsAndSrcUVRect[idx].samplingEncapsulatinRect; sourceMaterialTilingOut = matsAndSrcUVRect[idx].sourceMaterialTiling; return(true); } else { //todo what if no UVs //Find UV rect in source mesh MB_Utility.MeshAnalysisResult[] mar; if (!meshAnalysisCache.TryGetValue(m.GetInstanceID(), out mar)) { mar = new MB_Utility.MeshAnalysisResult[m.subMeshCount]; for (int j = 0; j < m.subMeshCount; j++) { Vector2[] uvss = meshChannelCache.GetUv0Raw(m); MB_Utility.hasOutOfBoundsUVs(uvss, m, ref mar[j], j); } meshAnalysisCache.Add(m.GetInstanceID(), mar); } //this could be a mesh that was not used in the texture baking that has huge UV tiling too big for the rect that was baked //find a record that has an atlas uvRect capable of containing this bool found = false; if (logLevel >= MB2_LogLevel.trace) { Debug.Log(String.Format("Trying to find a rectangle in atlas capable of holding tiled sampling rect for mesh {0} using material {1} meshUVrect={2}", m, mat, mar[submeshIdx].uvRect.ToString("f5"))); } for (int i = idx; i < matsAndSrcUVRect.Length; i++) { if (matsAndSrcUVRect[i].material == mat) { if (IsMeshAndMaterialRectEnclosedByAtlasRect(mar[submeshIdx].uvRect, matsAndSrcUVRect[i].sourceMaterialTiling, matsAndSrcUVRect[i].samplingEncapsulatinRect, logLevel)) { if (logLevel >= MB2_LogLevel.trace) { Debug.Log("Found rect in atlas capable of containing tiled sampling rect for mesh " + m + " at idx=" + i); } idx = i; found = true; break; } } } if (found) { rectInAtlas = matsAndSrcUVRect[idx].atlasRect; //subrectInAtlasMatTiling = matsAndSrcUVRect[idx].atlasSubrectMaterialOnly; encapsulatingRect = matsAndSrcUVRect[idx].samplingEncapsulatinRect; sourceMaterialTilingOut = matsAndSrcUVRect[idx].sourceMaterialTiling; return(true); } else { rectInAtlas = new Rect(); //subrectInAtlasMatTiling = new Rect(); encapsulatingRect = new Rect(); sourceMaterialTilingOut = new Rect(); errorMsg = String.Format("Could not find a tiled rectangle in the atlas capable of containing the uv and material tiling on mesh {0} for material {1}", m.name, mat); return(false); } } }
//a material can appear more than once in an atlas if using fixOutOfBoundsUVs. //in this case need to use the UV rect of the mesh to find the correct rectangle. public bool TryMapMaterialToUVRect(Material mat, Mesh m, int submeshIdx, MB3_MeshCombinerSingle.MeshChannelsCache meshChannelCache, Dictionary <int, MB_Utility.MeshAnalysisResult[]> meshAnalysisCache, out Rect rectInAtlas, out Rect subrectInAtlasMatTiling, ref String errorMsg) { if (tbr.materialsAndUVRects.Length == 0 && tbr.materials.Length > 0) { errorMsg = "The 'Material Bake Result' needs to be re-baked to be compatible with this version of Mesh Baker. Please re-bake using the MB3_TextureBaker."; rectInAtlas = new Rect(); subrectInAtlasMatTiling = new Rect(); return(false); } if (mat == null) { rectInAtlas = new Rect(); subrectInAtlasMatTiling = new Rect(); errorMsg = String.Format("Mesh {0} Had no material on submesh {1} cannot map to a material in the atlas", m.name, submeshIdx); return(false); } if (submeshIdx >= m.subMeshCount) { errorMsg = "Submesh index is greater than the number of submeshes"; rectInAtlas = new Rect(); subrectInAtlasMatTiling = new Rect(); return(false); } //find the first index of this material int idx = -1; for (int i = 0; i < matsAndSrcUVRect.Length; i++) { if (mat == matsAndSrcUVRect[i].material) { idx = i; break; } } // if couldn't find material if (idx == -1) { rectInAtlas = new Rect(); subrectInAtlasMatTiling = new Rect(); errorMsg = String.Format("Material {0} could not be found in the Material Bake Result", mat.name); return(false); } if (!tbr.fixOutOfBoundsUVs) { if (numTimesMatAppearsInAtlas[idx] != 1) { Debug.LogError("There is a problem with this TextureBakeResults. FixOutOfBoundsUVs is false and a material appears more than once."); } rectInAtlas = matsAndSrcUVRect[idx].atlasRect; subrectInAtlasMatTiling = matsAndSrcUVRect[idx].atlasSubrectMaterialOnly; return(true); } else { //todo what if no UVs //Find UV rect in source mesh MB_Utility.MeshAnalysisResult[] mar; if (!meshAnalysisCache.TryGetValue(m.GetInstanceID(), out mar)) { mar = new MB_Utility.MeshAnalysisResult[m.subMeshCount]; for (int j = 0; j < m.subMeshCount; j++) { Vector2[] uvss = meshChannelCache.GetUv0Raw(m); MB_Utility.hasOutOfBoundsUVs(uvss, m, ref mar[j], j); } meshAnalysisCache.Add(m.GetInstanceID(), mar); } //this could be a mesh that was not used in the texture baking that has huge UV tiling too big for the rect that was baked //find a record that has an atlas uvRect capable of containing this bool found = false; for (int i = idx; i < matsAndSrcUVRect.Length; i++) { if (matsAndSrcUVRect[i].material == mat) { //calculate what the UV rect in the atlas would be if we combined the material tiling of this rect with the UV tiling of this submesh Rect potentialRect = new Rect(); Rect uvR = mar[submeshIdx].uvRect; Rect matR = matsAndSrcUVRect[i].atlasSubrectMaterialOnly; potentialRect = MB3_UVTransformUtility.CombineTransforms(ref matR, ref uvR); //MB3_UVTransformUtility.Canonicalize(ref potentialRect); // test to see if this would fit in what was baked in the atlas Rect rr = new Rect(0f, 0f, 1f, 1f); if (MB3_UVTransformUtility.RectContains(ref rr, ref potentialRect)) { idx = i; found = true; break; } } } if (found) { rectInAtlas = matsAndSrcUVRect[idx].atlasRect; subrectInAtlasMatTiling = matsAndSrcUVRect[idx].atlasSubrectMaterialOnly; return(true); } else { rectInAtlas = new Rect(); subrectInAtlasMatTiling = new Rect(); errorMsg = String.Format("Could not find a tiled rectangle in the atlas capable of containing the uv and material tiling on mesh {0}", m.name); return(false); } } }