using System; using UnityEditor; using UnityEngine; using System.Collections.Generic; using System.Linq; using Unity.Collections; using Unity.HLODSystem.Utils; namespace Unity.HLODSystem { public class SimpleBatcher : IBatcher { public enum PackingType { White, Black, Normal, } [InitializeOnLoadMethod] static void RegisterType() { BatcherTypes.RegisterBatcherType(typeof(SimpleBatcher)); } private DisposableDictionary m_createdMaterials = new DisposableDictionary(); private SerializableDynamicObject m_batcherOptions; [Serializable] public class TextureInfo { public string InputName = "_InputProperty"; public string OutputName = "_OutputProperty"; public PackingType Type = PackingType.White; } public SimpleBatcher(SerializableDynamicObject batcherOptions) { m_batcherOptions = batcherOptions; } public void Dispose() { m_createdMaterials.Dispose(); } public void Batch(Transform rootTransform, DisposableList targets, Action onProgress) { dynamic options = m_batcherOptions; if (onProgress != null) onProgress(0.0f); using (TexturePacker packer = new TexturePacker()) { PackingTexture(packer, targets, options, onProgress); for (int i = 0; i < targets.Count; ++i) { Combine(rootTransform, packer, targets[i], options); if (onProgress != null) onProgress(0.5f + ((float)i / (float)targets.Count) * 0.5f); } } } class MaterialTextureCache : IDisposable { private NativeArray m_detector = new NativeArray(1, Allocator.Persistent); private List m_textureInfoList; private DisposableDictionary m_textureCache; private DisposableDictionary m_defaultTextures; private bool m_enableTintColor; private string m_tintColorName; public MaterialTextureCache(dynamic options) { m_defaultTextures = CreateDefaultTextures(); m_enableTintColor = options.EnableTintColor; m_tintColorName = options.TintColorName; m_textureInfoList = options.TextureInfoList; m_textureCache = new DisposableDictionary(); } public TexturePacker.MaterialTexture GetMaterialTextures(WorkingMaterial material) { if (m_textureCache.ContainsKey(material.Guid) == false) { AddToCache(material); } var textures = m_textureCache[material.Guid]; if (textures != null) { string inputName = m_textureInfoList[0].InputName; material.SetTexture(inputName, textures[0].Clone()); } return textures; } public void Dispose() { m_textureCache.Dispose(); m_defaultTextures.Dispose(); m_detector.Dispose(); } private void AddToCache(WorkingMaterial material) { string inputName = m_textureInfoList[0].InputName; WorkingTexture texture = material.GetTexture(inputName); if (texture == null) { texture = m_defaultTextures[m_textureInfoList[0].Type]; } TexturePacker.MaterialTexture materialTexture = new TexturePacker.MaterialTexture(); if (m_enableTintColor) { Color tintColor = material.GetColor(m_tintColorName); texture = texture.Clone(); ApplyTintColor(texture, tintColor); materialTexture.Add(texture); texture.Dispose(); } else { materialTexture.Add(texture); } for (int ti = 1; ti < m_textureInfoList.Count; ++ti) { string input = m_textureInfoList[ti].InputName; WorkingTexture tex = material.GetTexture(input); if (tex == null) { tex = m_defaultTextures[m_textureInfoList[ti].Type]; } materialTexture.Add(tex); } m_textureCache.Add(material.Guid, materialTexture); } private void ApplyTintColor(WorkingTexture texture, Color tintColor) { for (int ty = 0; ty < texture.Height; ++ty) { for (int tx = 0; tx < texture.Width; ++tx) { Color c = texture.GetPixel(tx, ty); c.r = c.r * tintColor.r; c.g = c.g * tintColor.g; c.b = c.b * tintColor.b; c.a = c.a * tintColor.a; texture.SetPixel(tx, ty, c); } } } private static DisposableDictionary CreateDefaultTextures() { DisposableDictionary textures = new DisposableDictionary(); textures.Add(PackingType.White, CreateEmptyTexture(4, 4, Color.white, false)); textures.Add(PackingType.Black, CreateEmptyTexture(4, 4, Color.black, false)); textures.Add(PackingType.Normal, CreateEmptyTexture(4, 4, new Color(0.5f, 0.5f, 1.0f), true)); return textures; } } private void PackingTexture(TexturePacker packer, DisposableList targets, dynamic options, Action onProgress) { List textureInfoList = options.TextureInfoList; using (MaterialTextureCache cache = new MaterialTextureCache(options)) { for (int i = 0; i < targets.Count; ++i) { var workingObjects = targets[i].WorkingObjects; Dictionary textures = new Dictionary(); for (int oi = 0; oi < workingObjects.Count; ++oi) { var materials = workingObjects[oi].Materials; for (int m = 0; m < materials.Count; ++m) { var materialTextures = cache.GetMaterialTextures(materials[m]); if (materialTextures == null) continue; if (textures.ContainsKey(materialTextures[0].GetGUID()) == true) continue; textures.Add(materialTextures[0].GetGUID(), materialTextures); } } packer.AddTextureGroup(targets[i], textures.Values.ToList()); if (onProgress != null) onProgress(((float) i / targets.Count) * 0.1f); } } packer.Pack(TextureFormat.RGBA32, options.PackTextureSize, options.LimitTextureSize, false); if ( onProgress != null) onProgress(0.3f); int index = 1; var atlases = packer.GetAllAtlases(); foreach (var atlas in atlases) { Dictionary textures = new Dictionary(); for (int i = 0; i < atlas.Textures.Count; ++i) { WorkingTexture wt = atlas.Textures[i]; wt.Name = "CombinedTexture " + index + "_" + i; if (textureInfoList[i].Type == PackingType.Normal) { wt.Linear = true; } textures.Add(textureInfoList[i].OutputName, wt); } WorkingMaterial mat = CreateMaterial(options.MaterialGUID, textures); mat.Name = "CombinedMaterial " + index; m_createdMaterials.Add(atlas, mat); index += 1; } } static WorkingMaterial CreateMaterial(string guidstr, Dictionary textures) { WorkingMaterial material = null; string path = AssetDatabase.GUIDToAssetPath(guidstr); if (string.IsNullOrEmpty(path) == false) { Material mat = AssetDatabase.LoadAssetAtPath(path); if (mat != null) { material = new WorkingMaterial(Allocator.Invalid, mat.GetInstanceID(), mat.name); } } if (material == null) { material = new WorkingMaterial(Allocator.Persistent, new Material(Shader.Find("Standard"))); } foreach (var texture in textures) { material.AddTexture(texture.Key, texture.Value.Clone()); } return material; } private void Combine(Transform rootTransform, TexturePacker packer, HLODBuildInfo info, dynamic options) { var atlas = packer.GetAtlas(info); if (atlas == null) return; List textureInfoList = options.TextureInfoList; List combineInfos = new List(); var hlodWorldToLocal = rootTransform.worldToLocalMatrix; for (int i = 0; i < info.WorkingObjects.Count; ++i) { var obj = info.WorkingObjects[i]; ConvertMesh(obj.Mesh, obj.Materials, atlas, textureInfoList[0].InputName); for (int si = 0; si < obj.Mesh.subMeshCount; ++si) { var ci = new MeshCombiner.CombineInfo(); var colliderLocalToWorld = obj.LocalToWorld; var matrix = hlodWorldToLocal * colliderLocalToWorld; ci.Mesh = obj.Mesh; ci.MeshIndex = si; ci.Transform = matrix; if (ci.Mesh == null) continue; combineInfos.Add(ci); } } MeshCombiner combiner = new MeshCombiner(); WorkingMesh combinedMesh = combiner.CombineMesh(Allocator.Persistent, combineInfos); WorkingObject newObj = new WorkingObject(Allocator.Persistent); WorkingMaterial newMat = m_createdMaterials[atlas].Clone(); combinedMesh.name = info.Name + "_Mesh"; newObj.Name = info.Name; newObj.SetMesh(combinedMesh); newObj.Materials.Add(newMat); info.WorkingObjects.Dispose(); info.WorkingObjects = new DisposableList(); info.WorkingObjects.Add(newObj); } private void ConvertMesh(WorkingMesh mesh, DisposableList materials, TexturePacker.TextureAtlas atlas, string mainTextureName) { var uv = mesh.uv; var updated = new bool[uv.Length]; // Some meshes have submeshes that either aren't expected to render or are missing a material, so go ahead and skip int subMeshCount = Mathf.Min(mesh.subMeshCount, materials.Count); for (int mi = 0; mi < subMeshCount; ++mi) { int[] indices = mesh.GetTriangles(mi); foreach (var i in indices) { if ( updated[i] == false ) { var uvCoord = uv[i]; var texture = materials[mi].GetTexture(mainTextureName); if (texture == null || texture.GetGUID() == Guid.Empty) { // Sample at center of white texture to avoid sampling edge colors incorrectly uvCoord.x = 0.5f; uvCoord.y = 0.5f; } else { var uvOffset = atlas.GetUV(texture.GetGUID()); uvCoord.x = Mathf.Lerp(uvOffset.xMin, uvOffset.xMax, uvCoord.x); uvCoord.y = Mathf.Lerp(uvOffset.yMin, uvOffset.yMax, uvCoord.y); } uv[i] = uvCoord; updated[i] = true; } } } mesh.uv = uv; } static private WorkingTexture CreateEmptyTexture(int width, int height, Color color, bool linear) { WorkingTexture texture = new WorkingTexture(Allocator.Persistent, TextureFormat.RGB24, width, height, linear); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { texture.SetPixel(x, y, color); } } return texture; } static class Styles { public static int[] PackTextureSizes = new int[] { 256, 512, 1024, 2048, 4096 }; public static string[] PackTextureSizeNames; public static int[] LimitTextureSizes = new int[] { 32, 64, 128, 256, 512, 1024 }; public static string[] LimitTextureSizeNames; static Styles() { PackTextureSizeNames = new string[PackTextureSizes.Length]; for (int i = 0; i < PackTextureSizes.Length; ++i) { PackTextureSizeNames[i] = PackTextureSizes[i].ToString(); } LimitTextureSizeNames = new string[LimitTextureSizes.Length]; for (int i = 0; i < LimitTextureSizes.Length; ++i) { LimitTextureSizeNames[i] = LimitTextureSizes[i].ToString(); } } } private static string[] inputTexturePropertyNames = null; private static string[] outputTexturePropertyNames = null; private static TextureInfo addingTextureInfo = new TextureInfo(); public static void OnGUI(HLOD hlod, bool isFirst) { if (isFirst ) { inputTexturePropertyNames = null; outputTexturePropertyNames = null; } EditorGUI.indentLevel += 1; dynamic batcherOptions = hlod.BatcherOptions; if (batcherOptions.PackTextureSize == null) batcherOptions.PackTextureSize = 1024; if (batcherOptions.LimitTextureSize == null) batcherOptions.LimitTextureSize = 128; if (batcherOptions.MaterialGUID == null) batcherOptions.MaterialGUID = ""; if (batcherOptions.TextureInfoList == null) { batcherOptions.TextureInfoList = new List(); batcherOptions.TextureInfoList.Add(new TextureInfo() { InputName = "_MainTex", OutputName = "_MainTex", Type = PackingType.White }); } if (batcherOptions.EnableTintColor == null) batcherOptions.EnableTintColor = false; if (batcherOptions.TintColorName == null) batcherOptions.TintColorName = ""; batcherOptions.PackTextureSize = EditorGUILayout.IntPopup("Pack texture size", batcherOptions.PackTextureSize, Styles.PackTextureSizeNames, Styles.PackTextureSizes); batcherOptions.LimitTextureSize = EditorGUILayout.IntPopup("Limit texture size", batcherOptions.LimitTextureSize, Styles.LimitTextureSizeNames, Styles.LimitTextureSizes); Material mat = null; string matGUID = batcherOptions.MaterialGUID; string path = ""; if (string.IsNullOrEmpty(matGUID) == false) { path = AssetDatabase.GUIDToAssetPath(matGUID); mat = AssetDatabase.LoadAssetAtPath(path); } mat = EditorGUILayout.ObjectField("Material", mat, typeof(Material), false) as Material; if( mat == null) mat = new Material(Shader.Find("Standard")); path = AssetDatabase.GetAssetPath(mat); matGUID = AssetDatabase.AssetPathToGUID(path); if (matGUID != batcherOptions.MaterialGUID) { batcherOptions.MaterialGUID = matGUID; outputTexturePropertyNames = mat.GetTexturePropertyNames(); } if (inputTexturePropertyNames == null) { inputTexturePropertyNames = GetAllMaterialTextureProperties(hlod.gameObject); } if (outputTexturePropertyNames == null) { outputTexturePropertyNames = mat.GetTexturePropertyNames(); } //apply tint color batcherOptions.EnableTintColor = EditorGUILayout.Toggle("Enable tint color", batcherOptions.EnableTintColor); if (batcherOptions.EnableTintColor == true) { EditorGUI.indentLevel += 1; var shader = mat.shader; List colorPropertyNames = new List(); int propertyCount = ShaderUtil.GetPropertyCount(shader); for (int i = 0; i < propertyCount; ++i) { string name = ShaderUtil.GetPropertyName(shader, i); if (ShaderUtil.GetPropertyType(shader, i) == ShaderUtil.ShaderPropertyType.Color) { colorPropertyNames.Add(name); } } int index = colorPropertyNames.IndexOf(batcherOptions.TintColorName); index = EditorGUILayout.Popup("Tint color property", index, colorPropertyNames.ToArray()); if (index >= 0) { batcherOptions.TintColorName = colorPropertyNames[index]; } else { batcherOptions.TintColorName = ""; } EditorGUI.indentLevel -= 1; } //ext textures EditorGUILayout.Space(); EditorGUILayout.LabelField("Textures"); EditorGUI.indentLevel += 1; EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(" "); //EditorGUILayout.LabelField(); EditorGUILayout.SelectableLabel("Input"); EditorGUILayout.SelectableLabel("Output"); EditorGUILayout.SelectableLabel("Type"); EditorGUILayout.EndHorizontal(); for (int i = 0; i < batcherOptions.TextureInfoList.Count; ++i) { TextureInfo info = batcherOptions.TextureInfoList[i]; EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(" "); info.InputName = StringPopup(info.InputName, inputTexturePropertyNames); info.OutputName = StringPopup(info.OutputName, outputTexturePropertyNames); info.Type = (PackingType)EditorGUILayout.EnumPopup(info.Type); if (i == 0) GUI.enabled = false; if (GUILayout.Button("x") == true) { batcherOptions.TextureInfoList.RemoveAt(i); i -= 1; } if (i == 0) GUI.enabled = true; EditorGUILayout.EndHorizontal(); } EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(" "); if (GUILayout.Button("Add new texture property") == true) { batcherOptions.TextureInfoList.Add(new TextureInfo()); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel(" "); if (GUILayout.Button("Update texture properties")) { //TODO: Need update automatically inputTexturePropertyNames = null; outputTexturePropertyNames = null; } EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel -= 1; EditorGUI.indentLevel -= 1; } static string StringPopup(string select, string[] options) { if (options == null || options.Length == 0) { EditorGUILayout.Popup(0, new string[] {select}); return select; } int index = Array.IndexOf(options, select); if (index < 0) index = 0; int selected = EditorGUILayout.Popup(index, options); return options[selected]; } static string[] GetAllMaterialTextureProperties(GameObject root) { var meshRenderers = root.GetComponentsInChildren(); HashSet texturePropertyNames = new HashSet(); for (int m = 0; m < meshRenderers.Length; ++m) { var mesh = meshRenderers[m]; foreach (Material material in mesh.sharedMaterials) { var names = material.GetTexturePropertyNames(); for (int n = 0; n < names.Length; ++n) { texturePropertyNames.Add(names[n]); } } } return texturePropertyNames.ToArray(); } } }