using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; namespace AutoLOD.MeshDecimator { using Utilities; public class AutoLODWindow : EditorWindow { private enum RendererType { Default, Skinned, Unhandled, Unknown } public AutoLODProperties commonSettings; public List targets; string currentObjectName; private Vector2 scrollPosition; private bool showPreview; PreviewRenderUtility previewRender; float previewFov = 30f; private Quaternion preview3dRotation; private Quaternion previewLight3dRotation; private GameObject previewObj; private GameObject previewObjWireframe; private float previewReductionRate = 2f; private int previewIndex = -1; private int previewSourceFaceCount; private int previewTargetFaceCount; private int previewActualFaceCount; private Material wireframeMaterial; private Vector3 previewPivotOffset = Vector3.zero; private bool wireframeOnly = false; private float wireframeOpacity = 0.5f; private Color wireframeColor = Color.black; private IMeshDecimator previewMeshDecimator; private MeshDecimatorBackend previewBackend = MeshDecimatorBackend.Fast; [MenuItem("Window/AutoLOD/MeshDecimator Documentation")] public static void ShowOnlineHelp() { Help.BrowseURL("https://leochaumartin.com/wiki/index.php/AutoLOD"); } [MenuItem("Window/AutoLOD/MeshDecimator")] public static void ShowWindow() { EditorWindow win = EditorWindow.GetWindow(typeof(AutoLODWindow)); win.titleContent = new GUIContent("AutoLOD", Resources.Load("Icon_AutoLOD")); win.minSize = new Vector2(284, 380); } [MenuItem("CONTEXT/MeshRenderer/AutoLOD...")] [MenuItem("CONTEXT/SkinnedMeshRenderer/AutoLOD...")] public static void ContextMenu(MenuCommand command) { Renderer rend = (Renderer)command.context; AutoLODWindow win = EditorWindow.GetWindow(typeof(AutoLODWindow)) as AutoLODWindow; win.titleContent = new GUIContent("AutoLOD", Resources.Load("Icon_AutoLOD")); win.minSize = new Vector2(284, 380); AutoLODProperties newObject = CreateInstance(); newObject._target = rend; if (win.targets == null) win.targets = new List(); win.targets.Add(newObject); } public void OnEnable() { previewRender = new PreviewRenderUtility(true); previewRender.camera.fieldOfView = previewFov; previewRender.camera.nearClipPlane = 0.001f; previewRender.camera.farClipPlane = 10000f; previewRender.camera.transform.LookAt(Vector3.zero); previewRender.camera.clearFlags = CameraClearFlags.SolidColor; previewRender.camera.backgroundColor = new Color(0.19f, 0.19f, 0.19f); if (GraphicsSettings.currentRenderPipeline != null && GraphicsSettings.currentRenderPipeline.name.Contains("HD")) { wireframeOnly = true; } previewObj = new GameObject(); previewObj.hideFlags = HideFlags.HideAndDontSave; previewObj.AddComponent(); previewObj.AddComponent(); previewObj.transform.position = Vector3.zero; previewObj.transform.rotation = Quaternion.identity; previewObjWireframe = new GameObject(); previewObjWireframe.hideFlags = HideFlags.HideAndDontSave; previewObjWireframe.AddComponent(); previewObjWireframe.AddComponent(); previewObjWireframe.transform.position = Vector3.zero; previewObjWireframe.transform.rotation = Quaternion.identity; previewRender.AddSingleGO(previewObj); previewRender.AddSingleGO(previewObjWireframe); preview3dRotation = Quaternion.identity; previewLight3dRotation = Quaternion.identity; wireframeMaterial = new Material(Shader.Find("AutoLOD/Wireframe")); wireframeMaterial.hideFlags = HideFlags.HideAndDontSave; wireframeMaterial.SetColor("_Color", wireframeColor); wireframeMaterial.SetFloat("_Opacity", wireframeOpacity); previewBackend = MeshDecimatorBackend.Fast; previewMeshDecimator = new CFastMeshDecimator(); previewMeshDecimator.Initialize(); } public void OnDisable() { previewRender.Cleanup(); } private void UpdatePreviewReductionRate() { if (previewIndex != -1 && previewIndex < targets.Count && targets[previewIndex]._target != null) { GameObject source = targets[previewIndex]._target.gameObject; RendererType rtype = GetRendererType(source); switch (rtype) { case RendererType.Default: { previewSourceFaceCount = source.GetComponent().sharedMesh.triangles.Length / 3; previewTargetFaceCount = Mathf.RoundToInt(previewSourceFaceCount / previewReductionRate); UnityEngine.Mesh decimatedMesh = previewMeshDecimator.DecimateMesh(source.GetComponent().sharedMesh, previewTargetFaceCount, false); previewActualFaceCount = decimatedMesh.triangles.Length / 3; previewObj.GetComponent().sharedMesh = decimatedMesh; previewObjWireframe.GetComponent().sharedMesh = decimatedMesh; } break; case RendererType.Skinned: { previewSourceFaceCount = source.GetComponent().sharedMesh.triangles.Length / 3; previewTargetFaceCount = Mathf.RoundToInt(previewSourceFaceCount / previewReductionRate); UnityEngine.Mesh decimatedMesh = previewMeshDecimator.DecimateMesh(source.GetComponent().sharedMesh, previewTargetFaceCount, false); previewActualFaceCount = decimatedMesh.triangles.Length / 3; previewObj.GetComponent().sharedMesh = decimatedMesh; previewObjWireframe.GetComponent().sharedMesh = decimatedMesh; } break; } } } private void UpdatePreviewObject() { if (previewIndex != -1 && previewIndex < targets.Count && targets[previewIndex]._target != null) { GameObject source = targets[previewIndex]._target.gameObject; RendererType rtype = GetRendererType(source); switch (rtype) { case RendererType.Default: { previewObj.GetComponent().sharedMesh = source.GetComponent().sharedMesh; Material[] sharedMaterials = new Material[source.GetComponent().sharedMaterials.Length]; Material[] sharedWireframeMaterials = new Material[source.GetComponent().sharedMaterials.Length]; for (int i = 0; i < sharedMaterials.Length; ++i) { sharedMaterials[i] = source.GetComponent().sharedMaterials[i]; sharedWireframeMaterials[i] = wireframeMaterial; } previewObj.GetComponent().sharedMaterials = sharedMaterials; previewObjWireframe.GetComponent().sharedMaterials = sharedWireframeMaterials; previewRender.camera.transform.position = Vector3.back * source.GetComponent().sharedMesh.bounds.size.magnitude * 2; previewRender.camera.transform.LookAt(Vector3.zero); previewPivotOffset = -source.GetComponent().sharedMesh.bounds.center; } break; case RendererType.Skinned: { previewObj.GetComponent().sharedMesh = source.GetComponent().sharedMesh; Material[] sharedMaterials = new Material[source.GetComponent().sharedMaterials.Length]; Material[] sharedWireframeMaterials = new Material[source.GetComponent().sharedMaterials.Length]; for (int i = 0; i < sharedMaterials.Length; ++i) { sharedMaterials[i] = source.GetComponent().sharedMaterials[i]; sharedWireframeMaterials[i] = wireframeMaterial; } previewObj.GetComponent().sharedMaterials = sharedMaterials; previewObjWireframe.GetComponent().sharedMaterials = sharedWireframeMaterials; previewRender.camera.transform.position = Vector3.back * source.GetComponent().sharedMesh.bounds.size.magnitude * 2; previewRender.camera.transform.LookAt(Vector3.zero); previewPivotOffset = -source.GetComponent().sharedMesh.bounds.center; } break; default: break; } previewObj.transform.position = preview3dRotation * previewPivotOffset; previewObjWireframe.transform.position = preview3dRotation * previewPivotOffset; previewObjWireframe.transform.position += previewRender.camera.transform.position * 0.01f; UpdatePreviewReductionRate(); } else { ResetPreviewObject(); } } private void ResetPreviewObject() { previewIndex = -1; previewSourceFaceCount = 0; previewTargetFaceCount = 0; previewActualFaceCount = 0; previewObj.GetComponent().sharedMesh = null; previewObj.GetComponent().sharedMaterial = null; previewObjWireframe.GetComponent().sharedMesh = null; previewObjWireframe.GetComponent().sharedMaterial = null; } void OnGUI() { if (targets == null) targets = new List(); if (commonSettings == null) commonSettings = CreateInstance(); AutoLODEditorUtility.InitializeStyle(); EditorGUI.DrawRect(new Rect(0, 0, Screen.width, Screen.height), new Color(0f, 0f, 0f, 0.156f)); GUILayout.Label(Resources.Load("Logo_AutoLOD"), AutoLODEditorUtility.centeredStyle, GUILayout.Height(32f)); EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginVertical(); SerializedProperty property; List indexesToRemove = new List(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("Targets", AutoLODEditorUtility.titleStyle); EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); Rect myRect = GUILayoutUtility.GetRect(0, 16, GUILayout.Height(24), GUILayout.Width(156), GUILayout.ExpandWidth(true)); GUI.skin.box = AutoLODEditorUtility.dropBoxStyle; GUI.SetNextControlName("DragDropBox"); GUI.Box(myRect, "Drag and Drop renderers here", AutoLODEditorUtility.dropBoxStyle); EditorGUILayout.EndHorizontal(); if (GUILayout.Button(EditorGUIUtility.IconContent("CreateAddNew"), GUILayout.Width(30), GUILayout.Height(30))) { targets.Add(CreateInstance()); } EditorGUILayout.EndHorizontal(); if (targets.Count > 0) EditorGUILayout.Separator(); if (myRect.Contains(Event.current.mousePosition)) { if (Event.current.type == EventType.DragUpdated) { GUI.FocusControl("DragDropBox"); DragAndDrop.visualMode = DragAndDropVisualMode.Copy; Event.current.Use(); } else if (Event.current.type == EventType.DragPerform) { for (int i = 0; i < DragAndDrop.objectReferences.Length; i++) { if ((DragAndDrop.objectReferences[i] as GameObject).GetComponent() != null) { bool found = false; for (int j = 0; j < targets.Count; ++j) { if (targets[j]._target == null) { targets[j]._target = (DragAndDrop.objectReferences[i] as GameObject).GetComponent(); found = true; break; } } if (!found) { targets.Add(CreateInstance()); targets[targets.Count - 1]._target = (DragAndDrop.objectReferences[i] as GameObject).GetComponent(); } } } Event.current.Use(); } } else { if (GUI.GetNameOfFocusedControl() == "DragDropBox") { GUI.FocusControl(null); } } Editor commonEditor = Editor.CreateEditor(commonSettings); scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar); for (int i = 0; i < targets.Count; ++i) { if (targets[i] == null) targets[i] = CreateInstance(); string name; name = targets[i]._target != null ? targets[i]._target.name : "Target " + i; EditorGUILayout.BeginHorizontal(); if (name.Length > 13) name = name.Substring(0, 13) + "..."; if (targets[i]._customSettings) name += " [custom]"; targets[i]._foldout = EditorGUILayout.BeginFoldoutHeaderGroup(targets[i]._foldout, name, AutoLODEditorUtility.subHeaderStyle); bool hasRenderer = targets[i]._target != null; Editor subEditor = Editor.CreateEditor(targets[i]); property = subEditor.serializedObject.FindProperty("_target"); EditorGUILayout.PropertyField(property, new GUIContent(""), GUILayout.Width(32), GUILayout.ExpandWidth(true)); EditorGUILayout.Space(2, false); bool isPreview = i == previewIndex; if (GUILayout.Toggle(isPreview, EditorGUIUtility.IconContent("d_ViewToolOrbit"), "Button", GUILayout.Width(24), GUILayout.Height(18))) { if (previewIndex == -1) { if (!this.docked) { Rect winRect = position; winRect.width = winRect.height * 1.33f; position = winRect; } } previewIndex = i; if (!isPreview) UpdatePreviewObject(); } else { if (isPreview) { if (!this.docked) { Rect winRect = position; winRect.width = minSize.x; position = winRect; } ResetPreviewObject(); } } EditorGUILayout.Space(2, false); if (GUILayout.Button(EditorGUIUtility.IconContent("d_Toolbar Minus@2x"), GUILayout.Width(24), GUILayout.Height(18))) indexesToRemove.Add(i); EditorGUILayout.EndHorizontal(); if (targets[i]._foldout) { EditorGUILayout.BeginVertical(EditorStyles.inspectorDefaultMargins); if (subEditor) { property = subEditor.serializedObject.FindProperty("_customSettings"); EditorGUILayout.PropertyField(property); if (targets[i]._customSettings) { AutoLODEditorUtility.DrawPropertiesPanel(targets[i], subEditor, out bool needsRepaint); if (needsRepaint) Repaint(); } } EditorGUILayout.EndVertical(); } subEditor.serializedObject.ApplyModifiedProperties(); if (previewIndex == i) { if (!hasRenderer && targets[i]._target != null) UpdatePreviewObject(); if (hasRenderer && targets[i]._target == null) ResetPreviewObject(); } EditorGUILayout.EndFoldoutHeaderGroup(); EditorGUILayout.Separator(); } EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); indexesToRemove.Reverse(); foreach (int index in indexesToRemove) { targets.RemoveAt(index); if (index == previewIndex) { ResetPreviewObject(); } if (index < previewIndex) previewIndex--; } EditorGUILayout.Separator(); EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("Common Settings", AutoLODEditorUtility.titleStyle); EditorGUILayout.Separator(); { AutoLODEditorUtility.DrawPropertiesPanel(commonSettings, commonEditor, out bool needsRepaint); if (needsRepaint) Repaint(); } commonEditor.serializedObject.ApplyModifiedProperties(); EditorGUILayout.Separator(); EditorGUILayout.EndVertical(); EditorGUILayout.Separator(); GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.FlexibleSpace(); if (GUILayout.Button(new GUIContent("Generate LOD", Resources.Load("Icon_AutoLOD")), GUILayout.Height(42), GUILayout.Width(225))) { GenerateLOD(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); showPreview = previewIndex > -1; if (showPreview) { EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.ExpandWidth(true)); GUILayout.Label("Preview Area", AutoLODEditorUtility.titleStyle); EditorGUILayout.Separator(); EditorGUILayout.BeginHorizontal(); GUILayout.Label("Reduction Rate"); float tmpPreviewReductionRate = EditorGUILayout.Slider(previewReductionRate, 1f, 32f); if (tmpPreviewReductionRate != previewReductionRate) { previewReductionRate = tmpPreviewReductionRate; UpdatePreviewReductionRate(); } MeshDecimatorBackend tmpPreviewBackend = (MeshDecimatorBackend)EditorGUILayout.EnumPopup(previewBackend, GUILayout.Width(100)); if(tmpPreviewBackend != previewBackend) { previewBackend = tmpPreviewBackend; switch (previewBackend) { case MeshDecimatorBackend.HighQuality: previewMeshDecimator = new CQualityMeshDecimator(); break; case MeshDecimatorBackend.Fast: default: previewMeshDecimator = new CFastMeshDecimator(); break; } previewMeshDecimator.Initialize(); UpdatePreviewReductionRate(); } EditorGUILayout.EndHorizontal(); Rect preview3dRect = GUILayoutUtility.GetRect(1, 1, GUILayout.MaxWidth(Screen.width - 280), GUILayout.ExpandWidth(true), GUILayout.MaxHeight(Screen.width - 280), GUILayout.ExpandHeight(true)); EditorGUI.DrawRect(preview3dRect, Color.black); if (previewRender != null && previewRender.camera != null) { previewObj.SetActive(!wireframeOnly); previewRender.BeginStaticPreview(preview3dRect); previewRender.Render(true); EditorGUI.DrawPreviewTexture(preview3dRect, previewRender.EndStaticPreview()); EditorGUI.LabelField(preview3dRect, string.Format("Source faces: {0}\nTarget faces: {1}\nActual faces: {2}", previewSourceFaceCount, previewTargetFaceCount, previewActualFaceCount), EditorStyles.miniBoldLabel); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Wireframe Only", GUILayout.Width(100f)); wireframeOnly = EditorGUILayout.Toggle(wireframeOnly, GUILayout.Width(24f)); float tmpWireframeOpacity = EditorGUILayout.Slider(wireframeOpacity, 0f, 1f, GUILayout.ExpandWidth(true)); if (tmpWireframeOpacity != wireframeOpacity) { wireframeOpacity = tmpWireframeOpacity; wireframeMaterial.SetFloat("_Opacity", wireframeOpacity); } Color tmpColor = EditorGUILayout.ColorField(wireframeColor, GUILayout.Width(56)); if(tmpColor != wireframeColor) { wireframeColor = tmpColor; wireframeMaterial.SetColor("_Color", wireframeColor); } EditorGUILayout.EndHorizontal(); } if (Event.current.type == EventType.ScrollWheel && preview3dRect.Contains(Event.current.mousePosition)) { float delta = Event.current.delta.y; previewFov += delta / 5f; previewFov = Mathf.Clamp(previewFov, 1f, 90f); previewRender.camera.fieldOfView = previewFov; Event.current.Use(); } if (Event.current.type == EventType.MouseDrag && preview3dRect.Contains(Event.current.mousePosition)) { if (Event.current.button == 0) { preview3dRotation = Quaternion.Euler(-Event.current.delta.y / preview3dRect.height * previewFov / 30f * 180f, 0f, 0f) * preview3dRotation * Quaternion.Euler(0f, -Event.current.delta.x / preview3dRect.width * previewFov / 30f * 180f, 0f); previewObj.transform.rotation = preview3dRotation; previewObj.transform.position = preview3dRotation * previewPivotOffset; previewObjWireframe.transform.rotation = preview3dRotation; previewObjWireframe.transform.position = preview3dRotation * previewPivotOffset; previewObjWireframe.transform.position += previewRender.camera.transform.position * 0.01f; Event.current.Use(); } if (Event.current.button == 1) { previewLight3dRotation = Quaternion.Euler(-Event.current.delta.y / preview3dRect.height * previewFov / 30f * 180f, 0f, 0f) * previewLight3dRotation * Quaternion.Euler(0f, -Event.current.delta.x / preview3dRect.width * previewFov / 30f * 180f, 0f); previewRender.lights[0].transform.rotation = previewLight3dRotation; Event.current.Use(); } } EditorGUILayout.EndVertical(); } GUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); EditorGUILayout.Separator(); GUILayout.BeginHorizontal(); AutoLODEditorUtility.smallFont.normal.textColor = new Color(0.8f, 0.8f, 0.8f); if (GUILayout.Button(EditorGUIUtility.IconContent("_Help"), GUILayout.Height(20))) { Help.BrowseURL("https://leochaumartin.com/wiki/index.php/AutoLOD"); } if (GUILayout.Button(Resources.Load("discord"), GUILayout.Height(20), GUILayout.Width(24))) { Help.BrowseURL("https://discord.gg/kYwzdvAt8q"); } if (GUILayout.Button(Resources.Load("youtube"), GUILayout.Height(20), GUILayout.Width(24))) { Help.BrowseURL("https://www.youtube.com/channel/UCTGysKJUd9Njaxqju4-c6_w"); } if (GUILayout.Button(Resources.Load("twitter"), GUILayout.Height(20), GUILayout.Width(24))) { Help.BrowseURL("https://twitter.com/LeoChaumartin"); } if (GUILayout.Button(new GUIContent(Resources.Load("mail"), "support@leochaumartin.com"), GUILayout.Height(20), GUILayout.Width(24))) { Help.BrowseURL("mailto:chaumartinleo@gmail.com"); } GUILayout.FlexibleSpace(); GUILayout.BeginVertical(); GUILayout.Label("v5.5.0 ", AutoLODEditorUtility.smallFont, GUILayout.Height(8)); GUILayout.BeginHorizontal(); GUILayout.Label("© 2020-2025 ", AutoLODEditorUtility.smallFont, GUILayout.Height(8)); Rect logoRect = GUILayoutUtility.GetLastRect(); logoRect.x -= 42; logoRect.height = 15; logoRect.y -= 4; GUI.Label(logoRect, Resources.Load("Logo_AutoLOD")); GUILayout.EndHorizontal(); GUILayout.Label("Léo Chaumartin ", AutoLODEditorUtility.smallFont, GUILayout.Height(8)); GUILayout.EndVertical(); GUILayout.EndHorizontal(); EditorGUILayout.Separator(); } void ReportProgress(string message, float value) { string prefix = currentObjectName != "" ? currentObjectName + " - " : ""; EditorUtility.DisplayProgressBar("AutoLOD", prefix + message, value); } void ReportDecimationStatus(int iteration, int start, int current, int end) { if (end > start) { string message = "Blendshapes Interpolation: This process can take a long time"; ReportProgress(message, 1f); } else { double progress = 1.0 - (current - Mathf.Min(start, end)) / (double)Mathf.Abs(start - end); string message = "AutoLOD Decimation Process: " + ((int)(100 * progress)).ToString() + "%"; ReportProgress(message, (float)progress); } } RendererType GetRendererType(GameObject go) { if (go.GetComponent()) return RendererType.Unhandled; if (go.GetComponent()) return RendererType.Skinned; if (go.GetComponent()) { if (go.GetComponent()) return RendererType.Default; return RendererType.Unhandled; } return RendererType.Unknown; } private void ProcessMeshRenderer(AutoLODProperties properties, int count) { GameObject go = properties._target.gameObject; float currentProgress = 0f; Undo.RecordObject(go, "Source GameObject modified"); currentObjectName = go.name + "_LOD0"; ReportProgress("Initialization...", currentProgress); GameObject detailedMesh = (GameObject)Instantiate(go, go.transform.position, go.transform.rotation); Undo.RegisterCreatedObjectUndo(detailedMesh, "Created LOD0"); foreach (MonoBehaviour mb in detailedMesh.GetComponents()) DestroyImmediate(mb); // Preventing from children duplication Transform[] children = detailedMesh.GetComponentsInChildren(); foreach (Transform child in children) if (child.gameObject != detailedMesh) Undo.DestroyObjectImmediate(child.gameObject); GameObject parentGo = go; Undo.SetTransformParent(detailedMesh.transform, parentGo.transform, "LOD0 New Parent"); Undo.RecordObject(detailedMesh.transform, "LOD0 scale)"); detailedMesh.transform.localScale = Vector3.one; LODGroup lodGroup = Undo.AddComponent(parentGo); Undo.DestroyObjectImmediate(parentGo.GetComponent()); Undo.DestroyObjectImmediate(parentGo.GetComponent()); if (parentGo.GetComponent()) { Undo.DestroyObjectImmediate(parentGo.GetComponent()); } LOD[] lods = new LOD[properties._lodLevels]; UnityEngine.Mesh lodMesh; lodMesh = detailedMesh.GetComponent().sharedMesh; IMeshDecimator meshDecimator; switch (properties._backend) { case MeshDecimatorBackend.HighQuality: meshDecimator = new CQualityMeshDecimator(); break; case MeshDecimatorBackend.Fast: default: meshDecimator = new CFastMeshDecimator(); break; } meshDecimator.Initialize(); meshDecimator.statusReportInvoker += ReportDecimationStatus; //Using the existing mesh as LOD0 { LOD lod = new LOD { renderers = new Renderer[1] { detailedMesh.GetComponent() }, screenRelativeTransitionHeight = (properties._lodLevels == 1 ? properties._relativeHeightCulling : properties._performance) }; lods[0] = lod; Undo.RecordObject(detailedMesh, "LOD0 Optimized Name"); detailedMesh.name = go.name + "_LOD0"; } currentProgress = (float)(count * properties._lodLevels) / (float)(targets.Count * properties._lodLevels); for (int l = 1; l < properties._lodLevels; ++l) { currentObjectName = go.name + "_LOD" + l; GameObject clone = new GameObject(go.name + "_LOD" + l); Undo.RegisterCreatedObjectUndo(clone, "Created LOD0"); int triangleTargetCount = (int)(lodMesh.triangles.Length / (3 * properties._reductionRate)); lodMesh = meshDecimator.DecimateMesh(lodMesh, triangleTargetCount, false); lodMesh.name = go.name + "_LOD" + l; if (properties._flatShading) { AutoLODMeshUtility.Smooth2FlatShading(lodMesh); } AssetDatabase.CreateAsset(lodMesh, AssetDatabase.GenerateUniqueAssetPath("Assets/" + properties._filePath + "/" + go.name + "_LOD" + l.ToString() + ".asset")); clone.transform.parent = parentGo.transform; clone.transform.localPosition = Vector3.zero; clone.transform.localRotation = Quaternion.identity; clone.transform.localScale = Vector3.one; clone.AddComponent().sharedMesh = lodMesh; clone.AddComponent().sharedMaterials = detailedMesh.GetComponent().sharedMaterials; LOD lod = new LOD { renderers = new Renderer[1] { clone.GetComponent() }, }; if (l < properties._lodLevels - 1) { if (Mathf.Pow(properties._performance, l + 1) > properties._relativeHeightCulling) lod.screenRelativeTransitionHeight = Mathf.Pow(properties._performance, l + 1); else { lod.screenRelativeTransitionHeight = (lods[l - 1].screenRelativeTransitionHeight + properties._relativeHeightCulling) / 2f; } } else lod.screenRelativeTransitionHeight = properties._relativeHeightCulling; lods[l] = lod; currentProgress = (float)(count * properties._lodLevels + l) / (float)(targets.Count * properties._lodLevels); } AssetDatabase.SaveAssets(); lodGroup.SetLODs(lods); lodGroup.animateCrossFading = true; lodGroup.fadeMode = LODFadeMode.CrossFade; LODGroup.crossFadeAnimationDuration = 0.1f; } private void ProcessSkinnedMeshRenderer(AutoLODProperties properties, int count) { GameObject go = properties._target.gameObject; float currentProgress = 0f; Undo.RecordObject(go, "Source GameObject modified"); currentObjectName = go.name + "_LOD0"; ReportProgress("Initialization...", currentProgress); GameObject detailedMesh = (GameObject)Instantiate(go, go.transform.position, go.transform.rotation); foreach (MonoBehaviour mb in detailedMesh.GetComponents()) DestroyImmediate(mb); bool hasBlendshapes = go.GetComponent().sharedMesh.blendShapeCount > 0; detailedMesh.GetComponent().sharedMesh = go.GetComponent().sharedMesh; Undo.RegisterCreatedObjectUndo(detailedMesh, "Created LOD0"); // Preventing from children duplication Transform[] children = detailedMesh.GetComponentsInChildren(); foreach (Transform child in children) if (child.gameObject != detailedMesh) Undo.DestroyObjectImmediate(child.gameObject); GameObject parentGo = go; Undo.SetTransformParent(detailedMesh.transform, parentGo.transform, "LOD0 New Parent"); Undo.RecordObject(detailedMesh.transform, "LOD0 scale)"); detailedMesh.transform.localScale = Vector3.one; Transform bones = detailedMesh.GetComponent().rootBone; detailedMesh.GetComponent().rootBone = null; detailedMesh.GetComponent().rootBone = bones; LODGroup lodGroup = Undo.AddComponent(parentGo); Undo.DestroyObjectImmediate(parentGo.GetComponent()); bool hasCollider = false; if (parentGo.GetComponent()) { Undo.DestroyObjectImmediate(parentGo.GetComponent()); hasCollider = true; } LOD[] lods = new LOD[properties._lodLevels]; UnityEngine.Mesh lodMesh; lodMesh = detailedMesh.GetComponent().sharedMesh; Matrix4x4[] bindposes = lodMesh.bindposes; IMeshDecimator meshDecimator; switch (properties._backend) { case MeshDecimatorBackend.HighQuality: meshDecimator = new CQualityMeshDecimator(); break; case MeshDecimatorBackend.Fast: default: meshDecimator = new CFastMeshDecimator(); break; } meshDecimator.Initialize(); meshDecimator.statusReportInvoker += ReportDecimationStatus; //Using the existing mesh as LOD0 { LOD lod = new LOD { renderers = new Renderer[1] { detailedMesh.GetComponent() }, screenRelativeTransitionHeight = (properties._lodLevels == 1 ? properties._relativeHeightCulling : properties._performance) }; lods[0] = lod; Undo.RecordObject(detailedMesh, "LOD0 Optimized Name"); detailedMesh.name = go.name + "_LOD0"; } currentProgress = (float)(count * properties._lodLevels) / (float)(targets.Count * properties._lodLevels); for (int l = 1; l < properties._lodLevels; ++l) { currentObjectName = go.name + "_LOD" + l; GameObject clone = new GameObject(go.name + "_LOD" + l); Undo.RegisterCreatedObjectUndo(clone, "Created LOD0"); int triangleTargetCount = (int)(lodMesh.triangles.Length / (3 * properties._reductionRate)); lodMesh = meshDecimator.DecimateMesh(lodMesh, triangleTargetCount, hasBlendshapes); lodMesh.name = go.name + "_LOD" + l; if (properties._flatShading) { AutoLODMeshUtility.Smooth2FlatShading(lodMesh); } lodMesh.bindposes = bindposes; AssetDatabase.CreateAsset(lodMesh, AssetDatabase.GenerateUniqueAssetPath("Assets/" + properties._filePath + "/" + go.name + "_LOD" + l.ToString() + ".asset")); clone.transform.parent = parentGo.transform; clone.transform.localPosition = Vector3.zero; clone.transform.localRotation = Quaternion.identity; clone.transform.localScale = Vector3.one; clone.AddComponent().bones = detailedMesh.GetComponent().bones; clone.GetComponent().sharedMesh = lodMesh; clone.GetComponent().sharedMaterials = detailedMesh.GetComponent().sharedMaterials; if (hasCollider) { clone.AddComponent().sharedMesh = clone.GetComponent().sharedMesh; } LOD lod = new LOD { renderers = new Renderer[1] { clone.GetComponent() }, }; if (l < properties._lodLevels - 1) { if (Mathf.Pow(properties._performance, l + 1) > properties._relativeHeightCulling) lod.screenRelativeTransitionHeight = Mathf.Pow(properties._performance, l + 1); else { lod.screenRelativeTransitionHeight = (lods[l - 1].screenRelativeTransitionHeight + properties._relativeHeightCulling) / 2f; } } else lod.screenRelativeTransitionHeight = properties._relativeHeightCulling; lods[l] = lod; currentProgress = (float)(count * properties._lodLevels + l) / (float)(targets.Count * properties._lodLevels); } AssetDatabase.SaveAssets(); lodGroup.SetLODs(lods); lodGroup.animateCrossFading = true; lodGroup.fadeMode = LODFadeMode.CrossFade; LODGroup.crossFadeAnimationDuration = 0.1f; } public void GenerateLOD() { int count = 0; Undo.IncrementCurrentGroup(); Undo.SetCurrentGroupName("AutoLOD"); foreach (AutoLODProperties a in targets) { if (a._target == null) continue; if (a._target.gameObject.scene.name == null) { Debug.LogWarning(a._target.name + " is a Prefab. You must instantiate the prefab in a scene and work on the instance instead. Ignoring this object."); continue; } if (!a._customSettings) { a.Apply(commonSettings); } if (!Directory.Exists(Application.dataPath + "/" + a._filePath)) { Directory.CreateDirectory(Application.dataPath + "/" + a._filePath); } GameObject go = a._target.gameObject; RendererType meshType = GetRendererType(go); switch (meshType) { case RendererType.Default: { ProcessMeshRenderer(a, count); break; } case RendererType.Skinned: { ProcessSkinnedMeshRenderer(a, count); break; } case RendererType.Unhandled: { Debug.LogWarning(go.name + ": This renderer is not handled yet. Ignoring this object."); break; } case RendererType.Unknown: default: { Debug.LogWarning(go.name + ": No valid renderer found. Ignoring this object."); break; } } count++; } targets.Clear(); ResetPreviewObject(); EditorUtility.ClearProgressBar(); } } }