private static Mesh SimplifyMesh(Mesh mesh, float quality, SimplificationOptions options) { var meshSimplifier = new MeshSimplifier(); meshSimplifier.SimplificationOptions = options; meshSimplifier.Initialize(mesh); meshSimplifier.SimplifyMesh(quality); var simplifiedMesh = meshSimplifier.ToMesh(); simplifiedMesh.bindposes = mesh.bindposes; return(simplifiedMesh); }
private void Reset() { fadeMode = LODFadeMode.None; animateCrossFading = false; autoCollectRenderers = true; simplificationOptions = SimplificationOptions.Default; levels = new LODLevel[] { new LODLevel(0.5f, 1f) { CombineMeshes = false, CombineSubMeshes = false, SkinQuality = SkinQuality.Auto, ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, ReceiveShadows = true, SkinnedMotionVectors = true, LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.BlendProbes, }, new LODLevel(0.17f, 0.65f) { CombineMeshes = true, CombineSubMeshes = false, SkinQuality = SkinQuality.Auto, ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On, ReceiveShadows = true, SkinnedMotionVectors = true, LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.BlendProbes, ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Simple }, new LODLevel(0.02f, 0.4225f) { CombineMeshes = true, CombineSubMeshes = true, SkinQuality = SkinQuality.Bone2, ShadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off, ReceiveShadows = false, SkinnedMotionVectors = false, LightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off, ReflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off } }; }
private static Mesh SimplifyMesh(Mesh mesh, float quality, SimplificationOptions options) { var meshSimplifier = new MeshSimplifier(); meshSimplifier.PreserveBorderEdges = options.PreserveBorderEdges; meshSimplifier.PreserveUVSeamEdges = options.PreserveUVSeamEdges; meshSimplifier.PreserveUVFoldoverEdges = options.PreserveUVFoldoverEdges; meshSimplifier.EnableSmartLink = options.EnableSmartLink; meshSimplifier.VertexLinkDistance = options.VertexLinkDistance; meshSimplifier.MaxIterationCount = options.MaxIterationCount; meshSimplifier.Agressiveness = options.Agressiveness; meshSimplifier.Initialize(mesh); meshSimplifier.SimplifyMesh(quality); var simplifiedMesh = meshSimplifier.ToMesh(); simplifiedMesh.bindposes = mesh.bindposes; return(simplifiedMesh); }
/// <summary> /// Generates the LODs and sets up a LOD Group for the specified game object. /// </summary> /// <param name="gameObject">The game object to set up.</param> /// <param name="levels">The LOD levels to set up.</param> /// <param name="autoCollectRenderers">If the renderers under the game object and any children should be automatically collected. /// Enabling this will ignore any renderers defined under each LOD level.</param> /// <param name="simplificationOptions">The mesh simplification options.</param> /// <param name="saveAssetsPath">The path to where the generated assets should be saved. Can be null or empty to use the default path.</param> /// <returns>The generated LOD Group.</returns> public static LODGroup GenerateLODs(GameObject gameObject, LODLevel[] levels, bool autoCollectRenderers, SimplificationOptions simplificationOptions, string saveAssetsPath) { if (gameObject == null) { throw new System.ArgumentNullException(nameof(gameObject)); } else if (levels == null) { throw new System.ArgumentNullException(nameof(levels)); } var transform = gameObject.transform; var existingLodParent = transform.Find(LODParentGameObjectName); if (existingLodParent != null) { throw new System.InvalidOperationException("The game object already appears to have LODs. Please remove them first."); } var existingLodGroup = gameObject.GetComponent <LODGroup>(); if (existingLodGroup != null) { throw new System.InvalidOperationException("The game object already appears to have a LOD Group. Please remove it first."); } saveAssetsPath = ValidateSaveAssetsPath(saveAssetsPath); var lodParentGameObject = new GameObject(LODParentGameObjectName); var lodParent = lodParentGameObject.transform; ParentAndResetTransform(lodParent, transform); var lodGroup = gameObject.AddComponent <LODGroup>(); Renderer[] allRenderers = null; if (autoCollectRenderers) { // Collect all enabled renderers under the game object allRenderers = GetChildRenderersForLOD(gameObject); } var renderersToDisable = new List <Renderer>((allRenderers != null ? allRenderers.Length : 10)); var lods = new LOD[levels.Length]; for (int levelIndex = 0; levelIndex < levels.Length; levelIndex++) { var level = levels[levelIndex]; var levelGameObject = new GameObject(string.Format("Level{0:00}", levelIndex)); var levelTransform = levelGameObject.transform; ParentAndResetTransform(levelTransform, lodParent); Renderer[] originalLevelRenderers = allRenderers ?? level.Renderers; var levelRenderers = new List <Renderer>((originalLevelRenderers != null ? originalLevelRenderers.Length : 0)); if (originalLevelRenderers != null && originalLevelRenderers.Length > 0) { var meshRenderers = (from renderer in originalLevelRenderers where renderer.enabled && renderer as MeshRenderer != null select renderer as MeshRenderer).ToArray(); var skinnedMeshRenderers = (from renderer in originalLevelRenderers where renderer.enabled && renderer as SkinnedMeshRenderer != null select renderer as SkinnedMeshRenderer).ToArray(); StaticRenderer[] staticRenderers; SkinnedRenderer[] skinnedRenderers; if (level.CombineMeshes) { staticRenderers = CombineStaticMeshes(transform, levelIndex, meshRenderers); skinnedRenderers = CombineSkinnedMeshes(transform, levelIndex, skinnedMeshRenderers); } else { staticRenderers = GetStaticRenderers(meshRenderers); skinnedRenderers = GetSkinnedRenderers(skinnedMeshRenderers); } if (staticRenderers != null) { for (int rendererIndex = 0; rendererIndex < staticRenderers.Length; rendererIndex++) { var renderer = staticRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_static_{1}", rendererIndex, renderer.name); var levelRenderer = CreateLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, ref level); levelRenderers.Add(levelRenderer); } } if (skinnedRenderers != null) { for (int rendererIndex = 0; rendererIndex < skinnedRenderers.Length; rendererIndex++) { var renderer = skinnedRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_skinned_{1}", rendererIndex, renderer.name); var levelRenderer = CreateSkinnedLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, renderer.rootBone, renderer.bones, ref level); levelRenderers.Add(levelRenderer); } } } foreach (var renderer in originalLevelRenderers) { if (!renderersToDisable.Contains(renderer)) { renderersToDisable.Add(renderer); } } lods[levelIndex] = new LOD(level.ScreenRelativeTransitionHeight, levelRenderers.ToArray()); } CreateBackup(gameObject, renderersToDisable.ToArray()); foreach (var renderer in renderersToDisable) { renderer.enabled = false; } lodGroup.animateCrossFading = false; lodGroup.SetLODs(lods); return(lodGroup); }
/// <summary> /// Generates the LODs and sets up a LOD Group for the specified game object. /// </summary> /// <param name="gameObject">The game object to set up.</param> /// <param name="levels">The LOD levels to set up.</param> /// <param name="autoCollectRenderers">If the renderers under the game object and any children should be automatically collected. /// Enabling this will ignore any renderers defined under each LOD level.</param> /// <param name="simplificationOptions">The mesh simplification options.</param> /// <returns>The generated LOD Group.</returns> public static LODGroup GenerateLODs(GameObject gameObject, LODLevel[] levels, bool autoCollectRenderers, SimplificationOptions simplificationOptions) { return(GenerateLODs(gameObject, levels, autoCollectRenderers, simplificationOptions, null)); }
private static Renderer CreateLevelRenderer(GameObject gameObject, int levelIndex, ref LODLevel level, Transform levelTransform, int rendererIndex, RendererInfo renderer, ref SimplificationOptions simplificationOptions, string saveAssetsPath) { var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); #if UNITY_EDITOR SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, mesh.name, saveAssetsPath); #endif if (renderer.isNewMesh) { DestroyObject(renderer.mesh); } } if (renderer.isStatic) { string rendererName = string.Format("{0:000}_static_{1}", rendererIndex, renderer.name); return(CreateStaticLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, ref level)); } else { string rendererName = string.Format("{0:000}_skinned_{1}", rendererIndex, renderer.name); return(CreateSkinnedLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, renderer.rootBone, renderer.bones, ref level)); } }
/// <summary> /// Generates the LODs and sets up a LOD Group for the specified game object. /// </summary> /// <param name="gameObject">The game object to set up.</param> /// <param name="levels">The LOD levels to set up.</param> /// <param name="autoCollectRenderers">If the renderers under the game object and any children should be automatically collected. /// Enabling this will ignore any renderers defined under each LOD level.</param> /// <param name="simplificationOptions">The mesh simplification options.</param> /// <param name="saveAssetsPath">The path to where the generated assets should be saved. Can be null or empty to use the default path.</param> /// <returns>The generated LOD Group.</returns> public static LODGroup GenerateLODs(GameObject gameObject, LODLevel[] levels, bool autoCollectRenderers, SimplificationOptions simplificationOptions, string saveAssetsPath) { if (gameObject == null) { throw new System.ArgumentNullException(nameof(gameObject)); } else if (levels == null) { throw new System.ArgumentNullException(nameof(levels)); } var transform = gameObject.transform; var existingLodParent = transform.Find(LODParentGameObjectName); if (existingLodParent != null) { throw new System.InvalidOperationException("The game object already appears to have LODs. Please remove them first."); } var existingLodGroup = gameObject.GetComponent <LODGroup>(); if (existingLodGroup != null) { throw new System.InvalidOperationException("The game object already appears to have a LOD Group. Please remove it first."); } saveAssetsPath = ValidateSaveAssetsPath(saveAssetsPath); var lodParentGameObject = new GameObject(LODParentGameObjectName); var lodParent = lodParentGameObject.transform; ParentAndResetTransform(lodParent, transform); var lodGroup = gameObject.AddComponent <LODGroup>(); Renderer[] allRenderers = null; if (autoCollectRenderers) { // Collect all enabled renderers under the game object allRenderers = GetChildRenderersForLOD(gameObject); } var renderersToDisable = new List <Renderer>((allRenderers != null ? allRenderers.Length : 10)); var lods = new LOD[levels.Length]; // No need to get the Renderers again and again we just cache it for once var meshRenderers = (from renderer in allRenderers let meshFilter = renderer.transform.GetComponent <MeshFilter>() where renderer.enabled && renderer as MeshRenderer != null && meshFilter != null && meshFilter.sharedMesh != null select renderer as MeshRenderer).ToArray(); var skinnedMeshRenderers = (from renderer in allRenderers where renderer.enabled && renderer as SkinnedMeshRenderer != null && (renderer as SkinnedMeshRenderer).sharedMesh != null select renderer as SkinnedMeshRenderer).ToArray(); List <Mesh> meshesChangedToReadible = new List <Mesh>(); bool areAnyCombinedLevels = false; foreach (var lodLevel in levels) { if (lodLevel.CombineMeshes) { areAnyCombinedLevels = true; break; } } // If there is any Lod level with the Combine meshes option selected then // we enable Read/Write flag on the original meshes if (areAnyCombinedLevels) { foreach (var renderer in meshRenderers) { var meshFilter = renderer.GetComponent <MeshFilter>(); if (meshFilter == null || meshFilter.sharedMesh == null) { continue; } if (!meshFilter.sharedMesh.isReadable) { MeshUtils.ChangeMeshReadibility(meshFilter.sharedMesh, true, false); if (meshFilter.sharedMesh.isReadable) { meshesChangedToReadible.Add(meshFilter.sharedMesh); } } } foreach (var renderer in skinnedMeshRenderers) { if (renderer == null || renderer.sharedMesh == null) { continue; } if (!renderer.sharedMesh.isReadable) { MeshUtils.ChangeMeshReadibility(renderer.sharedMesh, true, false); if (renderer.sharedMesh.isReadable) { meshesChangedToReadible.Add(renderer.sharedMesh); } } } } System.Exception exception = null; try { for (int levelIndex = 0; levelIndex < levels.Length; levelIndex++) { var level = levels[levelIndex]; var levelGameObject = new GameObject(string.Format("Level{0:00}", levelIndex)); var levelTransform = levelGameObject.transform; ParentAndResetTransform(levelTransform, lodParent); Renderer[] originalLevelRenderers = allRenderers ?? level.Renderers; var levelRenderers = new List <Renderer>((originalLevelRenderers != null ? originalLevelRenderers.Length : 0)); if (originalLevelRenderers != null && originalLevelRenderers.Length > 0) { StaticRenderer[] staticRenderers; SkinnedRenderer[] skinnedRenderers; if (level.CombineMeshes) { staticRenderers = CombineStaticMeshes(transform, levelIndex, meshRenderers); skinnedRenderers = CombineSkinnedMeshes(transform, levelIndex, skinnedMeshRenderers); } else { staticRenderers = GetStaticRenderers(meshRenderers); skinnedRenderers = GetSkinnedRenderers(skinnedMeshRenderers); } if (staticRenderers != null) { for (int rendererIndex = 0; rendererIndex < staticRenderers.Length; rendererIndex++) { var renderer = staticRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_static_{1}", rendererIndex, renderer.name); var levelRenderer = CreateLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, ref level); levelRenderers.Add(levelRenderer); } } if (skinnedRenderers != null) { for (int rendererIndex = 0; rendererIndex < skinnedRenderers.Length; rendererIndex++) { var renderer = skinnedRenderers[rendererIndex]; var mesh = renderer.mesh; // Simplify the mesh if necessary if (level.Quality < 1f) { mesh = SimplifyMesh(mesh, level.Quality, simplificationOptions); SaveLODMeshAsset(mesh, gameObject.name, renderer.name, levelIndex, renderer.mesh.name, saveAssetsPath); if (renderer.isNewMesh) { DestroyObject(renderer.mesh); renderer.mesh = null; } } string rendererName = string.Format("{0:000}_skinned_{1}", rendererIndex, renderer.name); var levelRenderer = CreateSkinnedLevelRenderer(rendererName, levelTransform, renderer.transform, mesh, renderer.materials, renderer.rootBone, renderer.bones, ref level); levelRenderers.Add(levelRenderer); } } } foreach (var renderer in originalLevelRenderers) { if (!renderersToDisable.Contains(renderer)) { renderersToDisable.Add(renderer); } } lods[levelIndex] = new LOD(level.ScreenRelativeTransitionHeight, levelRenderers.ToArray()); } } catch (System.Exception ex) { exception = ex; } //Restore meshes Read/Write flag foreach (var mesh in meshesChangedToReadible) { Debug.LogWarning($"Mesh \"{mesh.name}\" was not readible so we marked it readible for the mesh combining process to complete and changed it back to non-readible after completion. This process can slow down LOD generation. You may want to mark this mesh Read/Write enabled in the model import settings, so that next time LOD generation on this model can be faster."); MeshUtils.ChangeMeshReadibility(mesh, false, false); } if (exception != null) { throw exception; } CreateBackup(gameObject, renderersToDisable.ToArray()); foreach (var renderer in renderersToDisable) { renderer.enabled = false; } lodGroup.animateCrossFading = false; lodGroup.SetLODs(lods); return(lodGroup); }