LOD groups for objects inside a fbx (Blender to Unity)

Hi everyone,

I have created a model with Blender that is composed by different static objects. Some of these objects need to have LOD because of high number of polygons. To simplify my case, I have created a simple model to show you. It is an fbx (created with Blender, as I said) that has the following tree when a prefab is created importing it:
ModelExample
Cylinder
Cylinder_LOD0
Cylinder_LOD1
Sphere
Sphere_LOD0
Sphere_LOD1

The problem is that Unity, automatically creates a LOD Group for “ModelExample” and it is working for the whole model at same time. If camera is close to ModelExample then Cylinder_LOD0 and Sphere_LOD0 are showed, and when the camera is far to ModelExample, Cylinder_LOD1 and Sphere_LOD1 are showed, both at same time.

I can solve the problem manually removing LOD Group from ModelExample and I creating one LOD Group for Cylinder and another for Sphere, adding manually also meshes LOD0 and LOD1 for each level.

For this simple model is not a problem to do it manullay, but it is annoying in a model with a lot of objects that need to have LOD.

Is there any way to do it automatically? Maybe I’m missing some nomenclature, project option or an option when I exported the model from Blender, but I was searching for a solution and I don’t find anything.

Thanks in advance

I’m having the same issue, did you ever find a solution?

I’m in this problem as well…

I guess we have 2 options:

  1. mighty do all by hand once
  2. try to automate the U3D shitty LOD import handling with an asset pre or postprocessor…

For my own part, il will head the 1) because i’ll handle everything, even if it’s the heavy way…

I basically gave up on the entire concept. For my building I just exported some door prefabs for instance and manually place them all over in the editor. It’s very annoying having to basically do the work twice, but otherwise anything I try and create gets ruined on import.

finally, i made an mesh importer post processor that works like a charm :smile:

My only needs are that LOD1+ objects are parented to LOD0…

That’s great, so far I’ve exported to (many) individual .fbx files in Blender. So I step through all the objects in the Blender scene, make low-poly copies of them, name them and export them right away. But when there are too many objects… Your solution is elegant!

can you share how you did this?

Here’s a quick script to do it.

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;

public class LODGroupFixer : EditorWindow
{
    [MenuItem("Tools/Fix LOD Groups")]
    public static void ShowWindow()
    {
        GetWindow<LODGroupFixer>("LOD Group Fixer");
    }

    void OnGUI()
    {
        if (GUILayout.Button("Fix Selected Objects LOD Groups"))
        {
            FixLODGroups();
        }
    }

    [MenuItem("GameObject/Fix LOD Groups", false, 0)]
    static void FixLODGroups()
    {
        foreach (GameObject selected in Selection.gameObjects)
        {
            FixLODGroupForObject(selected);
        }
    }

    static void FixLODGroupForObject(GameObject parentObject)
    {
        // Find all LOD meshes under this parent
        Dictionary<string, List<Renderer>> lodMeshes = new Dictionary<string, List<Renderer>>();
        
        // Collect all renderers and group by base name
        Renderer[] allRenderers = parentObject.GetComponentsInChildren<Renderer>();
        
        foreach (Renderer renderer in allRenderers)
        {
            string objectName = renderer.gameObject.name;
            
            // Extract base name (remove LOD suffix)
            string baseName = System.Text.RegularExpressions.Regex.Replace(objectName, @"_LOD\d+$", "");
            
            if (!lodMeshes.ContainsKey(baseName))
                lodMeshes[baseName] = new List<Renderer>();
                
            lodMeshes[baseName].Add(renderer);
        }

        // Create LOD Groups for each set
        foreach (var lodSet in lodMeshes)
        {
            if (lodSet.Value.Count > 1)
            {
                CreateLODGroup(lodSet.Value, lodSet.Key);
            }
        }
    }

    static void CreateLODGroup(List<Renderer> renderers, string baseName)
    {
        // Sort by LOD level (assumes naming convention with _LOD0, _LOD1, etc.)
        renderers.Sort((a, b) => 
        {
            int lodA = ExtractLODLevel(a.gameObject.name);
            int lodB = ExtractLODLevel(b.gameObject.name);
            return lodA.CompareTo(lodB);
        });

        // Find common parent or use first renderer's parent
        Transform parent = FindCommonParent(renderers);
        
        if (parent == null) return;

        // Remove existing LOD Group
        LODGroup existingLOD = parent.GetComponent<LODGroup>();
        if (existingLOD != null)
            DestroyImmediate(existingLOD);

        // Create new LOD Group
        LODGroup lodGroup = parent.gameObject.AddComponent<LODGroup>();
        
        // Create LOD levels
        LOD[] lods = new LOD[renderers.Count];
        
        for (int i = 0; i < renderers.Count; i++)
        {
            float screenRelativeHeight = CalculateLODHeight(i, renderers.Count);
            lods[i] = new LOD(screenRelativeHeight, new Renderer[] { renderers[i] });
        }
        
        lodGroup.SetLODs(lods);
        lodGroup.RecalculateBounds();
        
        Debug.Log($"Created LOD Group for {baseName} with {renderers.Count} levels");
    }

    static int ExtractLODLevel(string gameObjectName)
    {
        var match = System.Text.RegularExpressions.Regex.Match(gameObjectName, @"_LOD(\d+)$");
        return match.Success ? int.Parse(match.Groups[1].Value) : 0;
    }

    static Transform FindCommonParent(List<Renderer> renderers)
    {
        if (renderers.Count == 0) return null;
        
        Transform commonParent = renderers[0].transform.parent;
        foreach (var renderer in renderers)
        {
            if (renderer.transform.parent != commonParent)
            {
                // If not all share same parent, use the highest common parent
                return FindHighestCommonParent(renderers);
            }
        }
        return commonParent;
    }

    static Transform FindHighestCommonParent(List<Renderer> renderers)
    {
        if (renderers.Count == 0) return null;
        
        List<Transform> parents = new List<Transform>();
        foreach (var renderer in renderers)
        {
            parents.Add(renderer.transform.parent);
        }
        
        // This is simplified - you might want more sophisticated common parent finding
        return parents[0];
    }

    static float CalculateLODHeight(int lodLevel, int totalLODs)
    {
        // Customize these values based on your needs
        switch (lodLevel)
        {
            case 0: return 0.5f; // LOD0: 50% screen height
            case 1: return 0.3f; // LOD1: 30% screen height  
            case 2: return 0.15f; // LOD2: 15% screen height
            case 3: return 0.05f; // LOD3: 5% screen height
            default: return 1.0f / (lodLevel + 2);
        }
    }
}