阶段性完成

This commit is contained in:
SoulliesOfficial
2025-12-08 05:27:53 -05:00
parent ef7b479712
commit f7af60351b
8770 changed files with 15637030 additions and 208354 deletions

View File

@@ -0,0 +1,936 @@
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<AutoLODProperties> 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<Texture>("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<Texture>("Icon_AutoLOD"));
win.minSize = new Vector2(284, 380);
AutoLODProperties newObject = CreateInstance<AutoLODProperties>();
newObject._target = rend;
if (win.targets == null)
win.targets = new List<AutoLODProperties>();
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<MeshFilter>();
previewObj.AddComponent<MeshRenderer>();
previewObj.transform.position = Vector3.zero;
previewObj.transform.rotation = Quaternion.identity;
previewObjWireframe = new GameObject();
previewObjWireframe.hideFlags = HideFlags.HideAndDontSave;
previewObjWireframe.AddComponent<MeshFilter>();
previewObjWireframe.AddComponent<MeshRenderer>();
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<MeshFilter>().sharedMesh.triangles.Length / 3;
previewTargetFaceCount = Mathf.RoundToInt(previewSourceFaceCount / previewReductionRate);
UnityEngine.Mesh decimatedMesh = previewMeshDecimator.DecimateMesh(source.GetComponent<MeshFilter>().sharedMesh, previewTargetFaceCount, false);
previewActualFaceCount = decimatedMesh.triangles.Length / 3;
previewObj.GetComponent<MeshFilter>().sharedMesh = decimatedMesh;
previewObjWireframe.GetComponent<MeshFilter>().sharedMesh = decimatedMesh;
}
break;
case RendererType.Skinned:
{
previewSourceFaceCount = source.GetComponent<SkinnedMeshRenderer>().sharedMesh.triangles.Length / 3;
previewTargetFaceCount = Mathf.RoundToInt(previewSourceFaceCount / previewReductionRate);
UnityEngine.Mesh decimatedMesh = previewMeshDecimator.DecimateMesh(source.GetComponent<SkinnedMeshRenderer>().sharedMesh, previewTargetFaceCount, false);
previewActualFaceCount = decimatedMesh.triangles.Length / 3;
previewObj.GetComponent<MeshFilter>().sharedMesh = decimatedMesh;
previewObjWireframe.GetComponent<MeshFilter>().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<MeshFilter>().sharedMesh = source.GetComponent<MeshFilter>().sharedMesh;
Material[] sharedMaterials = new Material[source.GetComponent<MeshRenderer>().sharedMaterials.Length];
Material[] sharedWireframeMaterials = new Material[source.GetComponent<MeshRenderer>().sharedMaterials.Length];
for (int i = 0; i < sharedMaterials.Length; ++i)
{
sharedMaterials[i] = source.GetComponent<MeshRenderer>().sharedMaterials[i];
sharedWireframeMaterials[i] = wireframeMaterial;
}
previewObj.GetComponent<MeshRenderer>().sharedMaterials = sharedMaterials;
previewObjWireframe.GetComponent<MeshRenderer>().sharedMaterials = sharedWireframeMaterials;
previewRender.camera.transform.position = Vector3.back * source.GetComponent<MeshFilter>().sharedMesh.bounds.size.magnitude * 2;
previewRender.camera.transform.LookAt(Vector3.zero);
previewPivotOffset = -source.GetComponent<MeshFilter>().sharedMesh.bounds.center;
}
break;
case RendererType.Skinned:
{
previewObj.GetComponent<MeshFilter>().sharedMesh = source.GetComponent<SkinnedMeshRenderer>().sharedMesh;
Material[] sharedMaterials = new Material[source.GetComponent<SkinnedMeshRenderer>().sharedMaterials.Length];
Material[] sharedWireframeMaterials = new Material[source.GetComponent<SkinnedMeshRenderer>().sharedMaterials.Length];
for (int i = 0; i < sharedMaterials.Length; ++i)
{
sharedMaterials[i] = source.GetComponent<SkinnedMeshRenderer>().sharedMaterials[i];
sharedWireframeMaterials[i] = wireframeMaterial;
}
previewObj.GetComponent<MeshRenderer>().sharedMaterials = sharedMaterials;
previewObjWireframe.GetComponent<MeshRenderer>().sharedMaterials = sharedWireframeMaterials;
previewRender.camera.transform.position = Vector3.back * source.GetComponent<SkinnedMeshRenderer>().sharedMesh.bounds.size.magnitude * 2;
previewRender.camera.transform.LookAt(Vector3.zero);
previewPivotOffset = -source.GetComponent<SkinnedMeshRenderer>().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<MeshFilter>().sharedMesh = null;
previewObj.GetComponent<MeshRenderer>().sharedMaterial = null;
previewObjWireframe.GetComponent<MeshFilter>().sharedMesh = null;
previewObjWireframe.GetComponent<MeshRenderer>().sharedMaterial = null;
}
void OnGUI()
{
if (targets == null)
targets = new List<AutoLODProperties>();
if (commonSettings == null)
commonSettings = CreateInstance<AutoLODProperties>();
AutoLODEditorUtility.InitializeStyle();
EditorGUI.DrawRect(new Rect(0, 0, Screen.width, Screen.height), new Color(0f, 0f, 0f, 0.156f));
GUILayout.Label(Resources.Load<Texture>("Logo_AutoLOD"), AutoLODEditorUtility.centeredStyle, GUILayout.Height(32f));
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
SerializedProperty property;
List<int> indexesToRemove = new List<int>();
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<AutoLODProperties>());
}
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<Renderer>() != 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<Renderer>();
found = true;
break;
}
}
if (!found)
{
targets.Add(CreateInstance<AutoLODProperties>());
targets[targets.Count - 1]._target = (DragAndDrop.objectReferences[i] as GameObject).GetComponent<Renderer>();
}
}
}
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<AutoLODProperties>();
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<Texture>("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<Texture>("discord"), GUILayout.Height(20), GUILayout.Width(24)))
{
Help.BrowseURL("https://discord.gg/kYwzdvAt8q");
}
if (GUILayout.Button(Resources.Load<Texture>("youtube"), GUILayout.Height(20), GUILayout.Width(24)))
{
Help.BrowseURL("https://www.youtube.com/channel/UCTGysKJUd9Njaxqju4-c6_w");
}
if (GUILayout.Button(Resources.Load<Texture>("twitter"), GUILayout.Height(20), GUILayout.Width(24)))
{
Help.BrowseURL("https://twitter.com/LeoChaumartin");
}
if (GUILayout.Button(new GUIContent(Resources.Load<Texture>("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<Texture>("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<ReflectionProbe>())
return RendererType.Unhandled;
if (go.GetComponent<SkinnedMeshRenderer>())
return RendererType.Skinned;
if (go.GetComponent<Renderer>())
{
if (go.GetComponent<MeshFilter>())
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<MonoBehaviour>())
DestroyImmediate(mb);
// Preventing from children duplication
Transform[] children = detailedMesh.GetComponentsInChildren<Transform>();
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<LODGroup>(parentGo);
Undo.DestroyObjectImmediate(parentGo.GetComponent<MeshFilter>());
Undo.DestroyObjectImmediate(parentGo.GetComponent<MeshRenderer>());
if (parentGo.GetComponent<Collider>())
{
Undo.DestroyObjectImmediate(parentGo.GetComponent<Collider>());
}
LOD[] lods = new LOD[properties._lodLevels];
UnityEngine.Mesh lodMesh;
lodMesh = detailedMesh.GetComponent<MeshFilter>().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<Renderer>() },
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<MeshFilter>().sharedMesh = lodMesh;
clone.AddComponent<MeshRenderer>().sharedMaterials = detailedMesh.GetComponent<Renderer>().sharedMaterials;
LOD lod = new LOD
{
renderers = new Renderer[1] { clone.GetComponent<Renderer>() },
};
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<MonoBehaviour>())
DestroyImmediate(mb);
bool hasBlendshapes = go.GetComponent<SkinnedMeshRenderer>().sharedMesh.blendShapeCount > 0;
detailedMesh.GetComponent<SkinnedMeshRenderer>().sharedMesh = go.GetComponent<SkinnedMeshRenderer>().sharedMesh;
Undo.RegisterCreatedObjectUndo(detailedMesh, "Created LOD0");
// Preventing from children duplication
Transform[] children = detailedMesh.GetComponentsInChildren<Transform>();
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<SkinnedMeshRenderer>().rootBone;
detailedMesh.GetComponent<SkinnedMeshRenderer>().rootBone = null;
detailedMesh.GetComponent<SkinnedMeshRenderer>().rootBone = bones;
LODGroup lodGroup = Undo.AddComponent<LODGroup>(parentGo);
Undo.DestroyObjectImmediate(parentGo.GetComponent<SkinnedMeshRenderer>());
bool hasCollider = false;
if (parentGo.GetComponent<Collider>())
{
Undo.DestroyObjectImmediate(parentGo.GetComponent<Collider>());
hasCollider = true;
}
LOD[] lods = new LOD[properties._lodLevels];
UnityEngine.Mesh lodMesh;
lodMesh = detailedMesh.GetComponent<SkinnedMeshRenderer>().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<SkinnedMeshRenderer>() },
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<SkinnedMeshRenderer>().bones = detailedMesh.GetComponent<SkinnedMeshRenderer>().bones;
clone.GetComponent<SkinnedMeshRenderer>().sharedMesh = lodMesh;
clone.GetComponent<SkinnedMeshRenderer>().sharedMaterials = detailedMesh.GetComponent<SkinnedMeshRenderer>().sharedMaterials;
if (hasCollider)
{
clone.AddComponent<MeshCollider>().sharedMesh = clone.GetComponent<SkinnedMeshRenderer>().sharedMesh;
}
LOD lod = new LOD
{
renderers = new Renderer[1] { clone.GetComponent<Renderer>() },
};
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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c194be614a08f74e848f458a757fb3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,233 @@
using UnityEngine;
using UnityEditor;
using AutoLOD.MeshDecimator;
using System.IO;
using MathNet.Numerics;
using UnityEditor.PackageManager;
namespace AutoLOD.Utilities
{
public static class AutoLODEditorUtility
{
public static GUIStyle centeredStyle;
public static GUIStyle titleStyle;
public static GUIStyle pathCreatedStyle;
public static GUIStyle pathInputStyle;
public static GUIStyle subHeaderStyle;
public static GUIStyle dropBoxStyle;
public static GUIStyle smallFont;
public static Color[] LodColors = new Color[9] {
new Color(0.23f, 0.27f, 0.12f),
new Color(0.18f, 0.21f, 0.26f),
new Color(0.16f, 0.25f, 0.29f),
new Color(0.25f, 0.14f, 0.12f),
new Color(0.20f, 0.18f, 0.25f),
new Color(0.32f, 0.22f, 0.11f),
new Color(0.35f, 0.32f, 0.04f),
new Color(0.32f, 0.27f, 0.12f),
new Color(0.32f, 0f, 0f)
};
static AutoLODEditorUtility()
{
InitializeStyle();
}
public static void InitializeStyle()
{
if (centeredStyle == null)
{
centeredStyle = new GUIStyle(EditorStyles.boldLabel)
{
alignment = TextAnchor.MiddleCenter,
stretchWidth = true
};
}
if (titleStyle == null)
{
titleStyle = new GUIStyle(EditorStyles.boldLabel)
{
alignment = TextAnchor.MiddleLeft
};
}
if (pathCreatedStyle == null)
{
pathCreatedStyle = new GUIStyle(EditorStyles.label);
pathCreatedStyle.normal.textColor = Color.green;
pathCreatedStyle.fontSize = 10;
pathCreatedStyle.alignment = TextAnchor.MiddleCenter;
}
if (pathInputStyle == null)
{
pathInputStyle = new GUIStyle(EditorStyles.textField);
pathInputStyle.focused.textColor = pathInputStyle.normal.textColor;
pathInputStyle.hover.textColor = pathInputStyle.normal.textColor;
}
if (subHeaderStyle == null)
{
subHeaderStyle = new GUIStyle(EditorStyles.foldoutHeader);
subHeaderStyle.fontStyle = FontStyle.Normal;
subHeaderStyle.stretchWidth = true;
}
if (dropBoxStyle == null)
{
dropBoxStyle = new GUIStyle(GUI.skin.box);
dropBoxStyle.alignment = TextAnchor.MiddleCenter;
dropBoxStyle.fontSize = 10;
dropBoxStyle.hover.textColor = Color.green;
}
if (smallFont == null)
smallFont = new GUIStyle
{
fontSize = 8,
alignment = TextAnchor.LowerRight
};
}
public static void DrawPropertiesPanel(AutoLODProperties properties, Editor editor, out bool needsRepaint)
{
needsRepaint = false;
pathInputStyle.normal.textColor = !Directory.Exists(Application.dataPath + "/" + properties._filePath) ? Color.yellow : Color.green;
pathInputStyle.hover.textColor = !Directory.Exists(Application.dataPath + "/" + properties._filePath) ? Color.yellow : Color.green;
pathInputStyle.focused.textColor = !Directory.Exists(Application.dataPath + "/" + properties._filePath) ? Color.yellow : Color.green;
SerializedProperty property;
properties._backend = (MeshDecimatorBackend)EditorGUILayout.EnumPopup("Backend", properties._backend);
property = editor.serializedObject.FindProperty("_lodLevels");
EditorGUILayout.IntSlider(property, 1, 8);
property = editor.serializedObject.FindProperty("_reductionRate");
EditorGUILayout.BeginHorizontal();
using (new EditorGUI.DisabledScope(properties._autoReductionRate))
{
EditorGUILayout.Slider(property, 1.1f, 6);
}
properties._autoReductionRate = GUILayout.Toggle(properties._autoReductionRate, "Auto", EditorStyles.miniButton, GUILayout.Width(50));
if (properties._autoReductionRate)
{
properties._reductionRate = 1f/properties._performance;
}
EditorGUILayout.EndHorizontal();
Rect lodPreviewRect = GUILayoutUtility.GetRect(0, 28, GUILayout.ExpandWidth(true));
EditorGUIUtility.AddCursorRect(lodPreviewRect, MouseCursor.ResizeHorizontal);
float fullWidth = lodPreviewRect.width;
Rect cullingRect = lodPreviewRect;
cullingRect.width = Mathf.Sqrt(properties._relativeHeightCulling) * fullWidth;
cullingRect.x = lodPreviewRect.x + fullWidth - cullingRect.width;
int controlId = GUIUtility.GetControlID(FocusType.Passive, lodPreviewRect);
Event e = Event.current;
switch (e.GetTypeForControl(controlId))
{
case EventType.MouseDown:
if (lodPreviewRect.Contains(e.mousePosition) && e.button == 0)
{
GUIUtility.hotControl = controlId;
e.Use();
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlId)
{
if (cullingRect.Contains(e.mousePosition))
{
float newWidth = Mathf.Clamp(cullingRect.width - e.delta.x, 0f, fullWidth);
float newCull = Mathf.Clamp(Mathf.Pow(newWidth / fullWidth, 2f), 0.001f, Mathf.Pow(properties._performance, properties._lodLevels));
if (!Mathf.Approximately(newCull, properties._relativeHeightCulling))
{
properties._relativeHeightCulling = newCull;
needsRepaint = true;
}
}
else
{
float newPerf = Mathf.Clamp(properties._performance - e.delta.x / fullWidth, 0.1f, 0.9f);
if (!Mathf.Approximately(newPerf, properties._performance))
{
properties._performance = newPerf;
properties._relativeHeightCulling = Mathf.Min(properties._relativeHeightCulling, Mathf.Pow(newPerf, properties._lodLevels));
needsRepaint = true;
}
}
e.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlId)
{
GUIUtility.hotControl = 0;
e.Use();
}
break;
}
EditorGUI.DrawRect(lodPreviewRect, LodColors[0]);
EditorGUI.LabelField(lodPreviewRect, "LOD 0" + "\n100%", EditorStyles.miniBoldLabel);
float ratio = Mathf.Sqrt(properties._performance);
float xInit = lodPreviewRect.x;
lodPreviewRect.width *= ratio;
lodPreviewRect.x = xInit + (1f - ratio) * fullWidth;
for (int lvl = 1; lvl < properties._lodLevels; ++lvl)
{
Color lodColor = LodColors[lvl];
EditorGUI.DrawRect(lodPreviewRect, lodColor);
EditorGUI.LabelField(lodPreviewRect, "LOD " + lvl + "\n" + (100f * Mathf.Pow(properties._performance, lvl)).ToString("#.") + "%", EditorStyles.miniBoldLabel);
lodPreviewRect.x += (1f - ratio) * lodPreviewRect.width;
lodPreviewRect.width *= ratio;
}
EditorGUI.DrawRect(cullingRect, LodColors[8]);
EditorGUI.LabelField(cullingRect, "Culled" + "\n" + (100f * properties._relativeHeightCulling).ToString("#.0") + "%", EditorStyles.miniBoldLabel);
EditorGUILayout.Separator();
if (properties._backend == MeshDecimatorBackend.Fast)
{
property = editor.serializedObject.FindProperty("_flatShading");
EditorGUILayout.PropertyField(property, new GUIContent("Flat shading", "The Fast backend can't detect sharp edges and will break the flat shading effect. This option can force the output to be flat shaded."));
}
else
{
properties._flatShading = false;
}
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Save LOD meshes to Assets/", GUILayout.Width(168));
properties._filePath = EditorGUILayout.TextField(properties._filePath, pathInputStyle);
if (GUILayout.Button(EditorGUIUtility.IconContent("Folder Icon"), GUILayout.Width(24), GUILayout.Height(18)))
{
GUI.FocusControl(null);
string absPath = EditorUtility.SaveFolderPanel("Save Path", "Assets/" + properties._filePath, "Assets/" + properties._filePath);
if (absPath.Contains(Application.dataPath))
{
properties._filePath = absPath.Substring(Application.dataPath.Length);
if (properties._filePath.StartsWith("/"))
properties._filePath = properties._filePath.Substring(1);
}
else
{
if (absPath != "")
Debug.LogWarning("Invalid path: " + absPath + ". Please save the file under the Assets/ folder");
}
}
EditorGUILayout.EndHorizontal();
if (!Directory.Exists(Application.dataPath + "/" + properties._filePath))
{
EditorGUILayout.LabelField("The path will be created.", pathCreatedStyle);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c49bfea621307845a9964f2b6912307
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
namespace AutoLOD
{
// Create a new type of Settings Asset.
class AutoLODSettings : ScriptableObject
{
public const string autolodSettingsPath = "Assets/AutoLOD/Editor/AutoLODSettings.asset";
#pragma warning disable 0414
[SerializeField]
private string autolodDefaultExportFolder;
#pragma warning restore 0414
internal static AutoLODSettings GetOrCreateSettings()
{
var settings = AssetDatabase.LoadAssetAtPath<AutoLODSettings>(autolodSettingsPath);
if (settings == null)
{
settings = ScriptableObject.CreateInstance<AutoLODSettings>();
settings.autolodDefaultExportFolder = "Assets/AutoLOD/Generated";
AssetDatabase.CreateAsset(settings, autolodSettingsPath);
AssetDatabase.SaveAssets();
}
return settings;
}
internal static SerializedObject GetSerializedSettings()
{
return new SerializedObject(GetOrCreateSettings());
}
}
// Register a SettingsProvider using IMGUI for the drawing framework:
static class AutoLODSettingsIMGUIRegister
{
[SettingsProvider]
public static SettingsProvider CreateAutoLODSettingsProvider()
{
// First parameter is the path in the Settings window.
// Second parameter is the scope of this setting: it only appears in the Project Settings window.
var provider = new SettingsProvider("Preferences/AutoLOD", SettingsScope.User)
{
// By default the last token of the path is used as display name if no label is provided.
label = "AutoLOD Settings",
// Create the SettingsProvider and initialize its drawing (IMGUI) function in place:
guiHandler = (searchContext) =>
{
var settings = AutoLODSettings.GetSerializedSettings();
string currentValue = EditorPrefs.GetString("autolodDefaultExportFolder", "Assets/AutoLOD/Generated");
SerializedProperty pathProperty = settings.FindProperty("autolodDefaultExportFolder");
EditorGUILayout.PropertyField(pathProperty, new GUIContent("Default export folder"));
if (pathProperty.stringValue != currentValue)
{
EditorPrefs.SetString("autolodDefaultExportFolder", pathProperty.stringValue);
}
settings.ApplyModifiedPropertiesWithoutUndo();
},
// Populate the search keywords to enable smart search filtering and label highlighting:
keywords = new HashSet<string>(new[] { "Default export folder" })
};
return provider;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99508fb1fb1e5af44b3b6b9851020529
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,164 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
namespace AutoLOD.MeshDecimator.Utils
{
public class LODGroupDecimator
{
[MenuItem("CONTEXT/LODGroup/Generate Missing LODs/AutoLOD (Fast)")]
public static void ContextMenu(MenuCommand command)
{
LODGroup lodGroup = (LODGroup)command.context;
LODGroupSolvability solvability = LODGroupSolver.GetLODGroupSolvability(lodGroup);
switch (solvability)
{
case LODGroupSolvability.Compatible:
ProcessLODGroup(lodGroup, MeshDecimatorBackend.Fast);
break;
case LODGroupSolvability.Empty:
Debug.LogWarning("[AutoLOD] Empty LODGroup. Please add LOD levels and/or renderers in LOD 0.");
break;
case LODGroupSolvability.Incompatible:
default:
Debug.LogWarning("[AutoLOD] Incompatible LODGroup. All LODs must have either no renderers or exactly the same number of renderers as LOD0.");
break;
}
}
[MenuItem("CONTEXT/LODGroup/Generate Missing LODs/AutoLOD (HQ)")]
public static void ContextMenuHQ(MenuCommand command)
{
LODGroup lodGroup = (LODGroup)command.context;
LODGroupSolvability solvability = LODGroupSolver.GetLODGroupSolvability(lodGroup);
switch (solvability)
{
case LODGroupSolvability.Compatible:
ProcessLODGroup(lodGroup, MeshDecimatorBackend.HighQuality);
break;
case LODGroupSolvability.Empty:
Debug.LogWarning("[AutoLOD] Empty LODGroup. Please add LOD levels and/or renderers in LOD 0.");
break;
case LODGroupSolvability.Incompatible:
default:
Debug.LogWarning("[AutoLOD] Incompatible LODGroup. All LODs must have either no renderers or exactly the same number of renderers as LOD0.");
break;
}
}
static void ReportProgress(string message, float value)
{
EditorUtility.DisplayProgressBar("AutoLOD", message, value);
}
static 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);
}
}
private static void ProcessLODGroup(LODGroup lodGroup, MeshDecimatorBackend backend = MeshDecimatorBackend.Fast)
{
Undo.RecordObject(lodGroup, "Generating Missing LODs");
IMeshDecimator meshDecimator;
switch (backend)
{
case MeshDecimatorBackend.HighQuality:
meshDecimator = new CQualityMeshDecimator();
break;
case MeshDecimatorBackend.Fast:
default:
meshDecimator = new CFastMeshDecimator();
break;
}
meshDecimator.Initialize();
meshDecimator.statusReportInvoker += ReportDecimationStatus;
LOD[] lods = lodGroup.GetLODs();
Renderer[] referenceRenderers = lods[0].renderers;
for(int i = 1; i < lods.Length; ++i)
{
LOD lod = lods[i];
if (lod.renderers != null && lod.renderers.Length > 0)
continue;
float ratio = lods[i-1].screenRelativeTransitionHeight;
float reductionRate = 1f / ratio;
Renderer[] decimatedRenderers = new Renderer[referenceRenderers.Length];
int rendererIndex = 0;
foreach(Renderer referenceRenderer in referenceRenderers)
{
Transform commonParent = referenceRenderer.transform.parent;
if (commonParent == null)
commonParent = referenceRenderer.transform;
Mesh sourceMesh;
if (referenceRenderer.GetType() == typeof(SkinnedMeshRenderer))
{
sourceMesh = (referenceRenderer as SkinnedMeshRenderer).sharedMesh;
}
else
{
if(referenceRenderer.GetComponent<MeshFilter>() == null)
{
Debug.LogError("[AutoLOD] Ignoring " + referenceRenderer.name + " which has no Mesh Filter nor SkinnedMeshRenderer");
continue;
}
sourceMesh = referenceRenderer.GetComponent<MeshFilter>().sharedMesh;
}
Mesh decimatedMesh = meshDecimator.DecimateMesh(sourceMesh, Mathf.RoundToInt(sourceMesh.triangles.Length / (3 * reductionRate)), sourceMesh.blendShapeCount > 1);
string savePath = (EditorPrefs.GetString("autolodDefaultExportFolder", "Assets/AutoLOD/Generated")).Replace("//", "/");
if (savePath.StartsWith("Assets/"))
savePath = savePath.Substring(7);
if (!Directory.Exists(Application.dataPath + "/" + savePath))
{
Directory.CreateDirectory(Application.dataPath + "/" + savePath);
}
string lodName = referenceRenderer.gameObject.name + "_LOD" + i;
AssetDatabase.CreateAsset(decimatedMesh, AssetDatabase.GenerateUniqueAssetPath("Assets/" + savePath + "/" + lodName + ".asset"));
GameObject clone = new GameObject(lodName);
Undo.RegisterCreatedObjectUndo(clone, "Created LOD" + i);
clone.transform.SetParent(commonParent);
clone.transform.position = referenceRenderer.transform.position;
clone.transform.rotation = referenceRenderer.transform.rotation;
clone.transform.localScale = referenceRenderer.transform.localScale;
if (referenceRenderer.GetType() == typeof(SkinnedMeshRenderer))
{
SkinnedMeshRenderer renderer = clone.AddComponent<SkinnedMeshRenderer>();
renderer.bones = referenceRenderer.GetComponent<SkinnedMeshRenderer>().bones;
decimatedMesh.bindposes = sourceMesh.bindposes;
renderer.sharedMesh = decimatedMesh;
renderer.sharedMaterials = referenceRenderer.GetComponent<SkinnedMeshRenderer>().sharedMaterials;
}
else
{
MeshFilter filter = clone.AddComponent<MeshFilter>();
MeshRenderer renderer = clone.AddComponent<MeshRenderer>();
filter.sharedMesh = decimatedMesh;
renderer.sharedMaterials = referenceRenderer.GetComponent<MeshRenderer>().sharedMaterials;
}
decimatedRenderers[rendererIndex++] = clone.GetComponent<Renderer>();
}
lod.renderers = decimatedRenderers;
lods[i] = lod;
}
lodGroup.SetLODs(lods);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 483017ca8090a8347afc9252e90025e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: