RepoTown/Assets/MeshBaker/Editor/MB_TextureBakerEditorConfigureTextureArrays.cs

549 lines
28 KiB
C#
Raw Normal View History

2025-07-25 15:29:14 +05:00
//----------------------------------------------
// MeshBaker
// Copyright © 2011-2012 Ian Deane
//----------------------------------------------
using UnityEngine;
using System.Collections;
using System.IO;
using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using DigitalOpus.MB.Core;
using UnityEditor;
namespace DigitalOpus.MB.MBEditor
{
public class MB_TextureBakerConfigureTextureArrays
{
private class Slice
{
public List<MB_MaterialAndUVRect> atlasRects;
public AtlasPackingResult packingResult;
public int numAtlasRects;
}
private static GUIContent gc_TextureArrayOutputFormats = new GUIContent(
"Texture Array Output Formats",
"Texture Arrays do not have a 'TextureImporter' that lets you change the TextureArray format." +
"You can provide a list of formats to be generated here. You will probably have one set of formats per platform.");
public static void DrawTextureArrayConfiguration(MB3_TextureBaker momm, SerializedObject textureBaker, MB3_TextureBakerEditorInternal editorInternal)
{
EditorGUILayout.BeginVertical(editorInternal.editorStyles.multipleMaterialBackgroundStyle);
EditorGUILayout.LabelField("Texture Array Slice Configuration", EditorStyles.boldLabel);
float oldLabelWidth = EditorGUIUtility.labelWidth;
if (GUILayout.Button(MB3_TextureBakerEditorInternal.configAtlasTextureSlicesFromObjsContent))
{
ConfigureTextureArraysFromObjsToCombine(momm, editorInternal.resultMaterialsTexArray, textureBaker);
}
if (GUILayout.Button("Report texture sizes"))
{
Debug.Log(ReportTextureSizesAndFormats(momm));
}
if (editorInternal.textureArrayOutputFormats.arraySize == 0)
{
EditorGUILayout.HelpBox("You need at least one output format.", MessageType.Error);
}
EditorGUILayout.PropertyField(editorInternal.textureArrayOutputFormats, gc_TextureArrayOutputFormats, true);
EditorGUILayout.BeginHorizontal();
editorInternal.resultMaterialsFoldout = EditorGUILayout.Foldout(editorInternal.resultMaterialsFoldout, MB3_TextureBakerEditorInternal.textureArrayCombinedMaterialFoldoutGUIContent);
if (GUILayout.Button(MB3_TextureBakerEditorInternal.insertContent, EditorStyles.miniButtonLeft, MB3_TextureBakerEditorInternal.buttonWidth))
{
if (editorInternal.resultMaterialsTexArray.arraySize == 0)
{
momm.resultMaterialsTexArray = new MB_MultiMaterialTexArray[1];
}
else
{
int idx = editorInternal.resultMaterialsTexArray.arraySize - 1;
editorInternal.resultMaterialsTexArray.InsertArrayElementAtIndex(idx);
}
}
if (GUILayout.Button(MB3_TextureBakerEditorInternal.deleteContent, EditorStyles.miniButtonRight, MB3_TextureBakerEditorInternal.buttonWidth))
{
editorInternal.resultMaterialsTexArray.DeleteArrayElementAtIndex(editorInternal.resultMaterialsTexArray.arraySize - 1);
}
EditorGUILayout.EndHorizontal();
if (editorInternal.resultMaterialsFoldout)
{
for (int i = 0; i < editorInternal.resultMaterialsTexArray.arraySize; i++)
{
EditorGUILayout.Separator();
if (i % 2 == 1)
{
EditorGUILayout.BeginVertical(editorInternal.editorStyles.multipleMaterialBackgroundStyle);
}
else
{
EditorGUILayout.BeginVertical(editorInternal.editorStyles.multipleMaterialBackgroundStyleDarker);
}
string s = "";
if (i < momm.resultMaterialsTexArray.Length && momm.resultMaterialsTexArray[i] != null && momm.resultMaterialsTexArray[i].combinedMaterial != null) s = momm.resultMaterialsTexArray[i].combinedMaterial.shader.ToString();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("---------- submesh:" + i + " " + s, EditorStyles.boldLabel);
if (GUILayout.Button(MB3_TextureBakerEditorInternal.deleteContent, EditorStyles.miniButtonRight, MB3_TextureBakerEditorInternal.buttonWidth))
{
editorInternal.resultMaterialsTexArray.DeleteArrayElementAtIndex(i);
}
EditorGUILayout.EndHorizontal();
if (i < editorInternal.resultMaterialsTexArray.arraySize)
{
EditorGUILayout.Separator();
SerializedProperty resMat = editorInternal.resultMaterialsTexArray.GetArrayElementAtIndex(i);
EditorGUILayout.PropertyField(resMat.FindPropertyRelative("combinedMaterial"));
SerializedProperty slices = resMat.FindPropertyRelative("slices");
EditorGUILayout.PropertyField(slices, true);
}
EditorGUILayout.EndVertical();
}
}
EditorGUILayout.EndVertical();
}
public static string ReportTextureSizesAndFormats(MB3_TextureBaker mom)
{
if (mom.resultType != MB2_TextureBakeResults.ResultType.textureArray)
{
Debug.LogError("Result Type must be Texture Array.");
return "";
}
for (int resMatIdx = 0; resMatIdx < mom.resultMaterialsTexArray.Length; resMatIdx++)
{
MB_MultiMaterialTexArray resMatTexArray = mom.resultMaterialsTexArray[resMatIdx];
if (resMatTexArray.combinedMaterial == null)
{
Debug.LogError("Result Material " + resMatIdx + " is null");
return "";
}
}
System.Text.StringBuilder sb = new System.Text.StringBuilder();
// Visit each result material
for (int resMatIdx = 0; resMatIdx < mom.resultMaterialsTexArray.Length; resMatIdx++)
{
MB_MultiMaterialTexArray resMatTexArray = mom.resultMaterialsTexArray[resMatIdx];
// Do an atlas pack in order to collect all the textures needed by the result material
// And group these by material texture property.
MB3_TextureCombiner combiner = mom.CreateAndConfigureTextureCombiner();
combiner.saveAtlasesAsAssets = false;
combiner.fixOutOfBoundsUVs = false;
List<AtlasPackingResult> packingResults = new List<AtlasPackingResult>();
Material tempMat = new Material(resMatTexArray.combinedMaterial.shader);
List<Material> allSourceMaterials = new List<Material>();
for (int sliceIdx = 0; sliceIdx < resMatTexArray.slices.Count; sliceIdx++)
{
List<Material> srcMats = new List<Material>();
resMatTexArray.slices[sliceIdx].GetAllUsedMaterials(srcMats);
for (int srcMatIdx = 0; srcMatIdx < srcMats.Count; srcMatIdx++)
{
if (srcMats[srcMatIdx] != null && !allSourceMaterials.Contains(srcMats[srcMatIdx]))
{
allSourceMaterials.Add(srcMats[srcMatIdx]);
}
}
}
MB_AtlasesAndRects atlasesAndRects = new MB_AtlasesAndRects();
combiner.CombineTexturesIntoAtlases(null, atlasesAndRects, tempMat, mom.GetObjectsToCombine(), allSourceMaterials, mom.texturePropNamesToIgnore, null, packingResults,
onlyPackRects:true, splitAtlasWhenPackingIfTooBig:false);
// Now vist the packing results and collect all the textures
Debug.Assert(packingResults.Count == 1);
for (int texPropIdx = 0; texPropIdx < atlasesAndRects.texPropertyNames.Length; texPropIdx++)
{
string propertyName = atlasesAndRects.texPropertyNames[texPropIdx];
sb.AppendLine(String.Format("Prop: {0}", propertyName));
List<MB_MaterialAndUVRect> matsData = (List<MB_MaterialAndUVRect>)packingResults[0].data;
HashSet<Material> visitedMats = new HashSet<Material>();
for (int matAndGoIdx = 0; matAndGoIdx < matsData.Count; matAndGoIdx++)
{
Material mat = matsData[matAndGoIdx].material;
if (visitedMats.Contains(mat)) continue;
visitedMats.Add(mat);
if (mat.HasProperty(propertyName))
{
Texture tex = mat.GetTexture(propertyName);
if (tex != null)
{
string texFormatString = "UnknownFormat";
string texWrapMode = "UnknownClampMode";
if (tex is Texture2D)
{
texFormatString = ((Texture2D)tex).format.ToString();
texWrapMode = ((Texture2D)tex).wrapMode.ToString();
}
sb.AppendLine(String.Format(" {0} x {1} format:{2} wrapMode:{3} {4}", tex.width.ToString().PadLeft(6,' '), tex.height.ToString().PadRight(6,' '), texFormatString.PadRight(20,' '), texWrapMode.PadRight(12,' '), tex.name));
}
}
}
}
}
return sb.ToString();
}
public static void ConfigureTextureArraysFromObjsToCombine(MB3_TextureBaker mom, SerializedProperty resultMaterialsTexArrays, SerializedObject textureBaker)
{
if (mom.GetObjectsToCombine().Count == 0)
{
Debug.LogError("You need to add some objects to combine before building the texture array result materials.");
return;
}
if (resultMaterialsTexArrays.arraySize > 0)
{
Debug.LogError("You already have some texture array result materials 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;
}
//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;
}
if (MB_Utility.GetMesh(go) == null)
{
Debug.LogError("Could not get mesh for 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;
}
}
//Will sort into "result material"
// slices
Dictionary<MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo, List<Slice>> shader2ResultMat_map = new Dictionary<MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo, List<Slice>>();
// first pass split by shader and analyse meshes.
List<GameObject> objsToCombine = mom.GetObjectsToCombine();
for (int meshIdx = 0; meshIdx < objsToCombine.Count; meshIdx++)
{
GameObject srcGo = objsToCombine[meshIdx];
Mesh mesh = MB_Utility.GetMesh(srcGo);
Renderer r = MB_Utility.GetRenderer(srcGo);
if (mom.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("1st Pass 'Split By Shader' Processing Mesh: "+ mesh +" Num submeshes: " + r.sharedMaterials.Length);
for (int submeshIdx = 0; submeshIdx < r.sharedMaterials.Length; submeshIdx++)
{
if (r.sharedMaterials[submeshIdx] == null) continue;
MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo newKey = new MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo(r.sharedMaterials[submeshIdx].shader, r.sharedMaterials[submeshIdx]);
// Initially we fill the list of srcMaterials with garbage MB_MaterialAndUVRects. Will get proper ones when we atlas pack.
MB_MaterialAndUVRect submeshMaterial = new MB_MaterialAndUVRect(
r.sharedMaterials[submeshIdx],
new Rect(0, 0, 0, 0), // garbage value
false, //garbage value
new Rect(0, 0, 0, 0), // garbage value
new Rect(0, 0, 0, 0), // garbage value
new Rect(0, 0, 1, 1), // garbage value
MB_TextureTilingTreatment.unknown, // garbage value
r.name);
submeshMaterial.objectsThatUse = new List<GameObject>();
submeshMaterial.objectsThatUse.Add(r.gameObject);
if (!shader2ResultMat_map.ContainsKey(newKey))
{
// this is a new shader create a new result material
Slice srcMaterials = new Slice
{
atlasRects = new List<MB_MaterialAndUVRect>(),
numAtlasRects = 1,
};
srcMaterials.atlasRects.Add(submeshMaterial);
List<Slice> binsOfMatsThatUseShader = new List<Slice>();
binsOfMatsThatUseShader.Add(srcMaterials);
if (mom.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" Adding Source Material: " + submeshMaterial.material);
shader2ResultMat_map.Add(newKey, binsOfMatsThatUseShader);
}
else
{
// there is a result material that uses this shader. Add this source material
Slice srcMaterials = shader2ResultMat_map[newKey][0]; // There should only be one list of source materials
if (srcMaterials.atlasRects.Find(x => x.material == submeshMaterial.material) == null)
{
if (mom.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" Adding Source Material: " + submeshMaterial.material);
srcMaterials.atlasRects.Add(submeshMaterial);
}
}
}
}
int resMatCount = 0;
foreach (MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo resultMat in shader2ResultMat_map.Keys)
{
// at this point there there should be only one slice with all the source materials
resMatCount++;
// For each result material, all source materials are in the first slice.
// We will now split these using a texture packer. Each "atlas" generated by the packer will be a slice.
{
// All source materials should be in the first slice at this point.
List<Slice> slices = shader2ResultMat_map[resultMat];
List<Slice> newSlices = new List<Slice>();
Slice firstSlice = slices[0];
List<Material> allMatsThatUserShader = new List<Material>();
List<GameObject> objsThatUseFirstSlice = new List<GameObject>();
for (int i = 0; i < firstSlice.atlasRects.Count; i++)
{
allMatsThatUserShader.Add(firstSlice.atlasRects[i].material);
if (!objsThatUseFirstSlice.Contains(firstSlice.atlasRects[i].objectsThatUse[0]))
{
objsThatUseFirstSlice.Add(firstSlice.atlasRects[i].objectsThatUse[0]);
}
}
MB3_TextureCombiner combiner = mom.CreateAndConfigureTextureCombiner();
combiner.packingAlgorithm = MB2_PackingAlgorithmEnum.MeshBakerTexturePacker;
combiner.saveAtlasesAsAssets = false;
combiner.fixOutOfBoundsUVs = true;
combiner.doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize = true;
List<AtlasPackingResult> packingResults = new List<AtlasPackingResult>();
Material tempMat = new Material(resultMat.shader);
if (mom.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("======== 2nd pass. Use atlas packer to split the first slice into multiple if it exceeds atlas size. ");
combiner.CombineTexturesIntoAtlases(null, null, tempMat, mom.GetObjectsToCombine(), allMatsThatUserShader, mom.texturePropNamesToIgnore, null, packingResults,
onlyPackRects:true, splitAtlasWhenPackingIfTooBig:true);
if (mom.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("======== Completed packing with texture packer. numPackingResults: " + packingResults.Count);
newSlices.Clear();
// The texture packing just split the atlas into multiple atlases. Each atlas will become a "slice".
for (int newSliceIdx = 0; newSliceIdx < packingResults.Count; newSliceIdx++)
{
List<MB_MaterialAndUVRect> sourceMats = new List<MB_MaterialAndUVRect>();
List<MB_MaterialAndUVRect> packedMatRects = (List<MB_MaterialAndUVRect>) packingResults[newSliceIdx].data;
HashSet<Rect> distinctAtlasRects = new HashSet<Rect>();
for (int packedMatRectIdx = 0; packedMatRectIdx < packedMatRects.Count; packedMatRectIdx++)
{
MB_MaterialAndUVRect muvr = packedMatRects[packedMatRectIdx];
distinctAtlasRects.Add(muvr.atlasRect);
//{
// Rect encapsulatingRect = muvr.GetEncapsulatingRect();
// Vector2 sizeInAtlas_px = new Vector2(
// packingResults[newSliceIdx].atlasX * encapsulatingRect.width,
// packingResults[newSliceIdx].atlasY * encapsulatingRect.height);
//}
sourceMats.Add(muvr);
}
Slice slice = new Slice()
{
atlasRects = sourceMats,
packingResult = packingResults[newSliceIdx],
numAtlasRects = distinctAtlasRects.Count,
};
newSlices.Add(slice);
}
// Replace first slice with split version.
if (mom.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("First slice exceeded atlas size splitting it into " + newSlices.Count + " slices");
slices.RemoveAt(0);
for (int i = 0; i < newSlices.Count; i++)
{
slices.Insert(i, newSlices[i]);
}
}
}
// build the texture array result materials
if (shader2ResultMat_map.Count == 0) Debug.LogError("Found no materials in list of objects to combine");
mom.resultMaterialsTexArray = new MB_MultiMaterialTexArray[shader2ResultMat_map.Count];
int k = 0;
foreach (MB3_TextureBakerEditorInternal.MultiMatSubmeshInfo resMatKey in shader2ResultMat_map.Keys)
{
List<Slice> srcSlices = shader2ResultMat_map[resMatKey];
MB_MultiMaterialTexArray mm = mom.resultMaterialsTexArray[k] = new MB_MultiMaterialTexArray();
for (int sliceIdx = 0; sliceIdx < srcSlices.Count; sliceIdx++)
{
Slice slice = srcSlices[sliceIdx];
MB_TexArraySlice resSlice = new MB_TexArraySlice();
for (int srcMatIdx = 0; srcMatIdx < slice.atlasRects.Count; srcMatIdx++)
{
MB_MaterialAndUVRect matAndUVRect = slice.atlasRects[srcMatIdx];
List<GameObject> objsThatUse = matAndUVRect.objectsThatUse;
for (int objsThatUseIdx = 0; objsThatUseIdx < objsThatUse.Count; objsThatUseIdx++)
{
GameObject obj = objsThatUse[objsThatUseIdx];
if (!resSlice.ContainsMaterialAndMesh(slice.atlasRects[srcMatIdx].material, MB_Utility.GetMesh(obj)))
{
resSlice.sourceMaterials.Add(
new MB_TexArraySliceRendererMatPair()
{
renderer = obj,
sourceMaterial = slice.atlasRects[srcMatIdx].material
}
);
}
}
}
{
// Should we use considerUVs
bool doConsiderUVs = false;
// If there is more than one atlas rectangle in a slice then use considerUVs
if (slice.numAtlasRects > 1)
{
doConsiderUVs = true;
}
else
{
// There is only one source material, could be:
// - lots of tiling (don't want consider UVs)
// - We are extracting a small part of a large atlas (want considerUVs)
if (slice.packingResult.atlasX >= mom.maxAtlasSize ||
slice.packingResult.atlasY >= mom.maxAtlasSize)
{
doConsiderUVs = false; // lots of tiling
}
else
{
doConsiderUVs = true; // extracting a small part of an atlas
}
}
resSlice.considerMeshUVs = doConsiderUVs;
}
mm.slices.Add(resSlice);
}
// Enforce integrity. If a material appears in more than one slice then all those slices must be considerUVs=true
{
// collect all distinct materials
HashSet<Material> distinctMats = new HashSet<Material>();
Dictionary<Material, int> mat2sliceCount = new Dictionary<Material, int>();
for (int sliceIdx = 0; sliceIdx < mm.slices.Count; sliceIdx++)
{
for (int sliceMatIdx = 0; sliceMatIdx < mm.slices[sliceIdx].sourceMaterials.Count; sliceMatIdx++)
{
Material mat = mm.slices[sliceIdx].sourceMaterials[sliceMatIdx].sourceMaterial;
distinctMats.Add(mat);
mat2sliceCount[mat] = 0;
}
}
// Count the number of slices that use each material.
foreach (Material mat in distinctMats)
{
for (int sliceIdx = 0; sliceIdx < mm.slices.Count; sliceIdx++)
{
if (mm.slices[sliceIdx].ContainsMaterial(mat))
{
mat2sliceCount[mat] = mat2sliceCount[mat] + 1;
}
}
}
// Check that considerUVs is true for any materials that appear more than once
foreach (Material mat in distinctMats)
{
if (mat2sliceCount[mat] > 1)
{
for (int sliceIdx = 0; sliceIdx < mm.slices.Count; sliceIdx++)
{
if (mm.slices[sliceIdx].ContainsMaterial(mat))
{
if (mom.LOG_LEVEL >= MB2_LogLevel.debug &&
mm.slices[sliceIdx].considerMeshUVs) Debug.Log("There was a material " + mat + " that was used by more than one slice and considerUVs was false. sliceIdx:" + sliceIdx);
mm.slices[sliceIdx].considerMeshUVs = true;
}
}
}
}
}
// Cleanup. remove "Renderer"s from source materials that do not use considerUVs and delete extra
{
// put any slices with consider UVs first
List<MB_TexArraySlice> newSlices = new List<MB_TexArraySlice>();
for (int sliceIdx = 0; sliceIdx < mm.slices.Count; sliceIdx++)
{
if (mm.slices[sliceIdx].considerMeshUVs == true)
{
newSlices.Add(mm.slices[sliceIdx]);
}
}
// for any slices without considerUVs, remove "renderer" and truncate
for (int sliceIdx = 0; sliceIdx < mm.slices.Count; sliceIdx++)
{
MB_TexArraySlice slice = mm.slices[sliceIdx];
if (slice.considerMeshUVs == false)
{
newSlices.Add(slice);
HashSet<Material> distinctMats = slice.GetDistinctMaterials();
slice.sourceMaterials.Clear();
foreach (Material mat in distinctMats)
{
slice.sourceMaterials.Add(new MB_TexArraySliceRendererMatPair() {sourceMaterial = mat });
}
}
}
mm.slices = newSlices;
}
string pth = AssetDatabase.GetAssetPath(mom.textureBakeResults);
string baseName = Path.GetFileNameWithoutExtension(pth);
string folderPath = pth.Substring(0, pth.Length - baseName.Length - 6);
string matName = folderPath + baseName + "-mat" + k + ".mat";
Material existingAsset = AssetDatabase.LoadAssetAtPath<Material>(matName);
if (!existingAsset)
{
Material newMat = new Material(Shader.Find("Standard"));
// Don't try to configure the material we need the user to pick a shader that has TextureArrays
AssetDatabase.CreateAsset(newMat, matName);
}
mm.combinedMaterial = (Material)AssetDatabase.LoadAssetAtPath(matName, typeof(Material));
k++;
}
MBVersionEditor.UpdateIfDirtyOrScript(textureBaker);
textureBaker.Update();
}
}
}