Unity - LOD management for character objects


Unity has no built-in persona LOD management

Referring to a thread on the Unity forum: Level of Detail management (LOD)

Although Unity does not have built-in character LOD management
insert image description here

But we can switch the grid according to the distance ourselves
insert image description here


You can write your own script to control it by distance

Direct reference: https://pastebin.com/clone/K1S4Zjh0

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

//[ExecuteInEditMode]
public class CharacterLOD : MonoBehaviour
{
    
    
    public Mesh LOD0;
    public Mesh LOD1;
    public Mesh LOD2;
    public Mesh LOD3;

    public List<Material> LOD_0_Materials;
    public List<Material> LOD_1_Materials;
    public List<Material> LOD_2_Materials;
    public List<Material> LOD_3_Materials;

    public float lod0Dist = 10f;
    public float lod1Dist = 20f;
    public float lod2Dist = 30f;
    public float lod3Dist = 50f;
    public float lodTimer = 0.3f;

    public Camera camera1;
    private bool foundCamera;
    public int currLOD;
    private float currTimer;
    

    public void Start()
    {
    
    
        GetCamera();
        currLOD = 0;
    }

    void Update()
    {
    
    
        if (foundCamera == false)
            GetCamera();

        //Update LODs Timer
        currTimer -= Time.deltaTime;
        if (currTimer <= 0.0f)
        {
    
    
            UpdateMeshLOD();

            currTimer = lodTimer; //Reset Timer
        }
    }

    public void UpdateMeshLOD()
    {
    
    
        if (camera1 == null) return;

        Vector3 camPos1 = camera1.transform.position;

        if ((transform.position - camPos1).sqrMagnitude < (lod0Dist * QualitySettings.lodBias) * (lod0Dist * QualitySettings.lodBias)) //LOD 0
        {
    
    
            if (currLOD != 0)
            {
    
    
                SetLowestLODMesh(QualitySettings.maximumLODLevel);
                SetLODMaterials(QualitySettings.maximumLODLevel);
                currLOD = 0;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod1Dist * QualitySettings.lodBias) * (lod1Dist * QualitySettings.lodBias)) //LOD 1
        {
    
    
            if (currLOD != 1)
            {
    
    
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 1);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 1));
                currLOD = 1;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod2Dist * QualitySettings.lodBias) * (lod2Dist * QualitySettings.lodBias)) //LOD 2
        {
    
    
            if (currLOD != 2)
            {
    
    
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 2);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 2));
                currLOD = 2;
                return;
            }
        }
        else if ((transform.position - camPos1).sqrMagnitude < (lod3Dist * QualitySettings.lodBias) * (lod3Dist * QualitySettings.lodBias)) //LOD 3
        {
    
    
            if (currLOD != 3)
            {
    
    
                SetLowestLODMesh(QualitySettings.maximumLODLevel + 3);
                SetLODMaterials(GetLowestLODMats(QualitySettings.maximumLODLevel + 3));
                currLOD = 3;
                return;
            }
        }
    }

    public void SetLODMaterials(int lod)
    {
    
    
        Material[] currMats;
        bool wasSuccess = false;

        switch (lod)
        {
    
    
            case 0: //LOD 0
                if (LOD_0_Materials.Count > 0)
                {
    
    
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_0_Materials.Count];
                    for (var x = 0; x < LOD_0_Materials.Count; x++)
                    {
    
    
                        currMats[x] = LOD_0_Materials[x];
                        if (LOD_0_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_0_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
    
    
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    } 
                }
                break;
            case 1:
                if (LOD_1_Materials.Count > 0)
                {
    
    
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_1_Materials.Count];
                    for (var x = 0; x < LOD_1_Materials.Count; x++)
                    {
    
    
                        currMats[x] = LOD_1_Materials[x];
                        if (LOD_1_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_1_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
    
    
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
            case 2:
                if (LOD_2_Materials.Count > 0)
                {
    
    
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_2_Materials.Count];
                    for (var x = 0; x < LOD_2_Materials.Count; x++)
                    {
    
    
                        currMats[x] = LOD_2_Materials[x];
                        if (LOD_2_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_2_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
    
    
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
            case 3:
                if (LOD_3_Materials.Count > 0)
                {
    
    
                    int existingMatsCount = 0;
                    currMats = new Material[LOD_3_Materials.Count];
                    for (var x = 0; x < LOD_3_Materials.Count; x++)
                    {
    
    
                        currMats[x] = LOD_3_Materials[x];
                        if (LOD_3_Materials[x] != null)
                            existingMatsCount++;
                    }
                    if (existingMatsCount / LOD_3_Materials.Count > 0.5f) //Atleast 50% of materials exist
                    {
    
    
                        GetComponent<Renderer>().sharedMaterials = currMats;
                        wasSuccess = true;
                    }
                }
                break;
        }

        if (wasSuccess)
            Debug.Log("[CharacterLOD] " + this.transform.root.name + " Swapped LOD Mats to: " + lod);
    }

    public int GetLowestLODMats(int desired)
    {
    
    
        if (desired >= 3)
        {
    
    
            if (LOD_3_Materials.Count > 0)
                return 3;
            if (LOD_2_Materials.Count > 0)
                return 2;
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 2)
        {
    
    
            if (LOD_2_Materials.Count > 0)
                return 2;
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 1)
        {
    
    
            if (LOD_1_Materials.Count > 0)
                return 1;
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        if (desired == 0)
        {
    
    
            if (LOD_0_Materials.Count > 0)
                return 0;
        }
        return 0;
    }

    public void SetLowestLODMesh(int desired)
    {
    
    
        SkinnedMeshRenderer mf1 = GetComponent<SkinnedMeshRenderer>();
        if (desired >= 3)
        {
    
    
            if (LOD3 != null)
                mf1.sharedMesh = LOD3;
            if (LOD2 != null)
                mf1.sharedMesh = LOD2;
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 2)
        {
    
    
            if (LOD2 != null)
                mf1.sharedMesh = LOD2;
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 1)
        {
    
    
            if (LOD1 != null)
                mf1.sharedMesh = LOD1;
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
        }
        if (desired == 0)
            if (LOD0 != null)
                mf1.sharedMesh = LOD0;
    }

    public void GetCamera()
    {
    
    
        try
        {
    
    
            camera1 = Camera.main;
            foundCamera = true;
        }
        catch (Exception e)
        {
    
    
            Debug.Log("[CharacterLOD] Couldn't find Main Camera: " + e.Message);
        }
    }
}

extension, and its problems

There will be some problems if the above is handled by distance.

For example, it is not very useful in FPS games.

Because the game will have a magnifying glass

The magnifier is usually adjusted FOV to deal with

If it is processed according to the screen height ratio when processing in LODGroup, there is no problem in this kind of production.

But when the LODGROUP method is not set to deal with the SkinnedMeshRenderer

Because the state of animation information inside the SkinnedMeshRenderer

So you can only write a script to switch the Mesh, just don't move the animation bone information.

Then match it with the value of FOV to convert it, so that the LOD of SknnedMeshRnederer can be displayed normally under different FOV sizes.


There is another problem, none of the above methods can switch the bone structure

Because if the bone structure of a model is very complex, it will also consume a lot of money.

But in theory, we can write another one: when switching the LOD again, you can synchronize the Animator state and AnimationClip.playTime of the previous LOD to the current LOD, but there is no guarantee that there will be jitter, it depends on the API package enough not good enough


References

Guess you like

Origin blog.csdn.net/linjf520/article/details/122116464
Recommended