阶段性完成
This commit is contained in:
936
Assets/OtherPlugins/AutoLOD/Scripts/Editor/AutoLOD.cs
Normal file
936
Assets/OtherPlugins/AutoLOD/Scripts/Editor/AutoLOD.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/OtherPlugins/AutoLOD/Scripts/Editor/AutoLOD.cs.meta
Normal file
11
Assets/OtherPlugins/AutoLOD/Scripts/Editor/AutoLOD.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c194be614a08f74e848f458a757fb3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c49bfea621307845a9964f2b6912307
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99508fb1fb1e5af44b3b6b9851020529
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
164
Assets/OtherPlugins/AutoLOD/Scripts/Editor/LODGroupDecimator.cs
Normal file
164
Assets/OtherPlugins/AutoLOD/Scripts/Editor/LODGroupDecimator.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 483017ca8090a8347afc9252e90025e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user