Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 0.2.0 - 2026-03-11
### Added
- LOD fade mode support (None, CrossFade, SpeedTree) to reduce popping artifacts during LOD transitions
- Animate cross-fading toggle for smooth automated LOD blending
- `LODGroupHelper.InvalidateCache()` method for manual cache refresh after external LODGroup changes
- Fade mode settings exposed in Preferences UI, per-model LODData overrides, and LODData inspector

### Fixed
- Consistent `ForceLOD(0)/ForceLOD(-1)` initialization pattern in `ModelImporterLODGenerator.CreateLODGroup`
- Null renderer filtering when building LOD arrays for LODGroup setup in `ModelImporterLODGenerator`
- Null safety for renderer arrays in `Extensions.HasLODChain()`
- Null safety for LOD arrays and renderer arrays in `LODGroupExtensions.SetRenderersEnabled`

## 0.1.2 - 2024-05-23
- Adds option to use same material for all LODs
- More Instalod integration fixes
Expand Down
2 changes: 2 additions & 0 deletions Editor/AutoLOD.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ static public void GenerateLODs(GameObject go)
lodGroup.ForceLOD(0);
lodGroup.SetLODs(lods.ToArray());
lodGroup.RecalculateBounds();
lodGroup.fadeMode = autoLODSettingsData.FadeMode;
lodGroup.animateCrossFading = autoLODSettingsData.AnimateCrossFading;
lodGroup.ForceLOD(-1);

var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
Expand Down
25 changes: 25 additions & 0 deletions Editor/AutoLODSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static void DisplayPreferencesGUI()
GenerateLODsOnImportGUI();
SaveAssetsGUI();
SameMaterialLODsGUI();
FadeModeGUI();
UseSceneLODGUI();
HierarchyTypeGUI();
ParentNameGUI();
Expand Down Expand Up @@ -180,6 +181,30 @@ static void SameMaterialLODsGUI()
autoLODSettingsData.UseSameMaterialForLODs = useSameMaterial;
}

static void FadeModeGUI()
{
var label = new GUIContent("LOD Fade Mode", "Controls how LOD levels transition. "
+ "None uses hard cuts between LODs. CrossFade enables blending between LOD levels "
+ "to reduce popping artifacts. SpeedTree is optimized for SpeedTree assets.");

EditorGUI.BeginChangeCheck();
var fadeModeValue = (LODFadeMode)EditorGUILayout.EnumPopup(label, autoLODSettingsData.FadeMode);
if (EditorGUI.EndChangeCheck())
autoLODSettingsData.FadeMode = fadeModeValue;

if (autoLODSettingsData.FadeMode != LODFadeMode.None)
{
EditorGUI.indentLevel++;
var animateLabel = new GUIContent("Animate Cross-Fading", "When enabled, Unity will automatically "
+ "animate the transition between LOD levels over time for smoother visual results.");
EditorGUI.BeginChangeCheck();
var animate = EditorGUILayout.Toggle(animateLabel, autoLODSettingsData.AnimateCrossFading);
if (EditorGUI.EndChangeCheck())
autoLODSettingsData.AnimateCrossFading = animate;
EditorGUI.indentLevel--;
}
}

static void UseSceneLODGUI()
{
var label = new GUIContent("Scene LOD", "Enable Hierarchical LOD (HLOD) support for scenes, "
Expand Down
2 changes: 1 addition & 1 deletion Editor/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static bool HasLODChain(this LODGroup lodGroup)
for (var l = 1; l < lods.Length; l++)
{
var lod = lods[l];
if (lod.renderers.Length > 0)
if (lod.renderers != null && lod.renderers.Length > 0)
{
return true;
}
Expand Down
2 changes: 2 additions & 0 deletions Editor/LODDataEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public override void OnInspectorGUI()
m_ImportSettings.FindPropertyRelative("initialLODMaxPolyCount").intValue = autoLODSettingsData.InitialLODMaxPolyCount;
m_ImportSettings.FindPropertyRelative("hierarchyType").enumValueIndex = (int)autoLODSettingsData.HierarchyType;
m_ImportSettings.FindPropertyRelative("parentName").stringValue = autoLODSettingsData.ParentName;
m_ImportSettings.FindPropertyRelative("fadeMode").enumValueIndex = (int)autoLODSettingsData.FadeMode;
m_ImportSettings.FindPropertyRelative("animateCrossFading").boolValue = autoLODSettingsData.AnimateCrossFading;
}

if (settingsOverridden)
Expand Down
12 changes: 11 additions & 1 deletion Editor/ModelImporterLODGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -464,12 +464,20 @@ void CreateLODGroup(GameObject go, LODData lodData)

for (int i = 0; i <= maxLODFound; i++)
{
var lod = new LOD { renderers = lodData[i], screenRelativeTransitionHeight = GetScreenPercentage(i, maxLODFound, importerLODLevels) };
var renderers = lodData[i];
if (renderers != null)
renderers = renderers.Where(r => r != null).ToArray();

var lod = new LOD { renderers = renderers ?? Array.Empty<Renderer>(), screenRelativeTransitionHeight = GetScreenPercentage(i, maxLODFound, importerLODLevels) };
lods.Add(lod);
}

lodGroup.ForceLOD(0);
lodGroup.SetLODs(lods.ToArray());
lodGroup.RecalculateBounds();
lodGroup.fadeMode = autoLODSettingsData.FadeMode;
lodGroup.animateCrossFading = autoLODSettingsData.AnimateCrossFading;
lodGroup.ForceLOD(-1);

SyncImporterLODLevels(importerRef, importerLODLevels, lods);

Expand Down Expand Up @@ -610,6 +618,8 @@ internal static LODData GetLODData(string assetPath)
importSettings.initialLODMaxPolyCount = autoLODSettingsData.InitialLODMaxPolyCount;
importSettings.hierarchyType = autoLODSettingsData.HierarchyType;
importSettings.parentName = autoLODSettingsData.ParentName;
importSettings.fadeMode = autoLODSettingsData.FadeMode;
importSettings.animateCrossFading = autoLODSettingsData.AnimateCrossFading;
}

return lodData;
Expand Down
22 changes: 22 additions & 0 deletions Runtime/AutoLODSettingsData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class AutoLODSettingsData : ScriptableObject
[SerializeField] private bool showVolumeBounds = false;
[SerializeField] private int maxLOD = AutoLODConst.k_DefaultMaxLOD;
[SerializeField] private bool useSameMaterialForLODs = false;
[SerializeField] private LODFadeMode fadeMode = LODFadeMode.None;
[SerializeField] private bool animateCrossFading = false;

[SerializeField] private List<Type> meshSimplifiers;
[SerializeField] private List<Type> batchers;
Expand Down Expand Up @@ -369,6 +371,26 @@ public bool UseSameMaterialForLODs
}
}

public LODFadeMode FadeMode
{
get => fadeMode;
set
{
fadeMode = value;
OnSettingsUpdated?.Invoke();
}
}

public bool AnimateCrossFading
{
get => animateCrossFading;
set
{
animateCrossFading = value;
OnSettingsUpdated?.Invoke();
}
}

public IPreferences SimplifierPreferences
{
get => simplifierPreferences;
Expand Down
6 changes: 6 additions & 0 deletions Runtime/Extensions/LODGroupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ public static void SetEnabled(this LODGroupHelper lodGroupHelper, bool enabled)

static void SetRenderersEnabled(LOD[] lods, bool enabled)
{
if (lods == null)
return;

for (var i = 0; i < lods.Length; i++)
{
var lod = lods[i];

var renderers = lod.renderers;
if (renderers == null)
continue;

foreach (var r in renderers)
{
if (r)
Expand Down
12 changes: 12 additions & 0 deletions Runtime/LODGroupHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ public int maxLOD
}
}

/// <summary>
/// Invalidates all cached LODGroup data, forcing it to be re-fetched on next access.
/// Call this after modifying the LODGroup's LODs, transform, or size externally.
/// </summary>
public void InvalidateCache()
{
m_LODs = null;
m_ReferencePoint = null;
m_WorldSpaceSize = null;
m_MaxLOD = null;
}
Comment on lines +69 to +79

[SerializeField] LODGroup m_LODGroup;

LOD[] m_LODs;
Expand Down
3 changes: 3 additions & 0 deletions Runtime/LODImportSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using UnityEngine;
using UnityEngine.Serialization;

namespace Unity.AutoLOD
Expand All @@ -13,5 +14,7 @@ public class LODImportSettings
public int initialLODMaxPolyCount = Int32.MaxValue;
public LODHierarchyType hierarchyType = LODHierarchyType.ChildOfSource;
public string parentName = String.Empty;
public LODFadeMode fadeMode = LODFadeMode.None;
public bool animateCrossFading = false;
}
}
4 changes: 4 additions & 0 deletions Runtime/LODVolume_Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ public IEnumerator GenerateHLOD(bool propagateUpwards = true)

lod.renderers = hlodRoot.GetComponentsInChildren<Renderer>(false);
lodGroup.SetLODs(new LOD[] { lod });
lodGroup.fadeMode = AutoLODSettingsData.Instance.FadeMode;
lodGroup.animateCrossFading = AutoLODSettingsData.Instance.AnimateCrossFading;

if (propagateUpwards)
{
Expand Down Expand Up @@ -270,6 +272,8 @@ void GenerateLODs()
lodGroup.ForceLOD(0);
lodGroup.SetLODs(lods.ToArray());
lodGroup.RecalculateBounds();
lodGroup.fadeMode = AutoLODSettingsData.Instance.FadeMode;
lodGroup.animateCrossFading = AutoLODSettingsData.Instance.AnimateCrossFading;
lodGroup.ForceLOD(-1);

var prefab = PrefabUtility.GetCorrespondingObjectFromSource(go);
Expand Down