Article directory
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
But we can switch the grid according to the distance ourselves
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
- Creating LOD Group for character
- Level of Detail management (LOD)
- Unity automatically generates LOD, and the use of LOD for humanoid characters solves the problem of animation playback - this method is not very good, there is still motion jitter when the character LOD is switched, but the LOD model generation plug-in inside can be used