How do I setup LOD for geometry in Unity (that isnt terrain or trees)
For example, buildings, and dress-up objects…
I want the buildings to swap to simpler models when far away
Would I do this through my own scripts ?
For fade out… how could I make an object fade out and not render beyond a certain range ?
using UnityEngine;
[AddComponentMenu("Scripts/Misc/LevelOfDetail")]
[RequireComponent(typeof(MeshFilter))]
/*
*
* Script to swap out meshes based on distance (LOD)
* Rich Hudson 2009 - Kranky Boy Games
*
*/
public class LevelOfDetail : MonoBehaviour
{
private enum LOD_LEVEL { LOD0, LOD1, LOD2 };
public Mesh LOD0;
public Mesh LOD1;
public Mesh LOD2;
public float DistanceLOD1;
public float DistanceLOD2;
public float UpdateInterval = 2.0f;
private float currentTimeToInterval = 0.0f;
private LOD_LEVEL LOD = LOD_LEVEL.LOD2;
private void Start()
{
currentTimeToInterval = UpdateInterval;
}
private void Update()
{
if (currentTimeToInterval <= 0.0f)
{
//Mesh currentMesh = ((MeshFilter)GetComponent(typeof(MeshFilter))).mesh;
float distanceFromObject = Vector2.Distance(new Vector2
(GlobalManager.Player.transform.position.x, GlobalManager.Player.transform.position.z),
new Vector2(gameObject.transform.position.x, gameObject.transform.position.z));
if (distanceFromObject < DistanceLOD1 LOD != LOD_LEVEL.LOD0)
{
LOD = LOD_LEVEL.LOD0;
((MeshFilter)GetComponent(typeof(MeshFilter))).mesh = LOD0;
DebugConsole.Log("Switching to LOD0");
}
else if (distanceFromObject >= DistanceLOD1 distanceFromObject < DistanceLOD2 LOD != LOD_LEVEL.LOD1)
{
LOD = LOD_LEVEL.LOD1;
((MeshFilter)GetComponent(typeof(MeshFilter))).mesh = LOD1;
DebugConsole.Log("Switching to LOD1");
}
else if (distanceFromObject >= DistanceLOD2 LOD != LOD_LEVEL.LOD2)
{
LOD = LOD_LEVEL.LOD2;
((MeshFilter)GetComponent(typeof(MeshFilter))).mesh = LOD2;
DebugConsole.Log("Switching to LOD2");
}
//reset check timer
currentTimeToInterval = UpdateInterval;
}
else
{
currentTimeToInterval -= Time.deltaTime;
}
}
}
Remember to remove the debugging log calls. In case you are new, once you drop this on your game object to LOD - make sure you drag the LOD meshes onto the script, and set the distances. DistanceLOD1 is the switch point between LOD0 and 1, DistanceLOD2 is the switch point between LOD1 and LOD2.
If anyone sees any issues or has a better way I am all for learning as well.
some thoughts in return for your most generous help…
how about having a global flag… which only updates everytime the main camera/player moves more than X meters from last stored position.
That way, at the start of the script, you could have something like…
if !GlobalManager.Player.playerMoved
return;
also, you might want to set a random time offset to all the scripts… so they don’t all update and do the LOD check on the same frame every 2 seconds… to spread out the work.
some good ideas. Also something else to consider here - shaders! I had issues where LOD2 used a different shader and so it did not look right.
This happens because as we swap out mesh0 for mesh1 or mesh2 the SHADER will stay the same!! usually for LOD2 you want diffuse only or some such low weight shader.
So I am visiting this now - however since there is not way to assign unity built in shaders to scripts using the editor you have to assume diffuse for LOD2 and assume LOD1 has same shader as LOD0 - unless I am missing something. Please tell me I am.
Should set up a variable that you can assign a shader to use in the inspector, (I haven’t verified that it works, but no reason it shouldn’t), assigning it to the LOD level is on your plate right now
Any of you guys know if Aaron’s method of using a OnTriggerEnter, or my method of checking the distance from LOD to player IF the player is moving and only then every 2 sec or so, faster?
I think mine might be a bit more efficient…I know I know test it right?
What I do in practice is check distance in a while(true) loop, so every three seconds or so, do a distance test. If player is getting warm, turn on the trigger, so it capitalises on both methods. If its far away, deactivate the trigger.
The thing I like about using spheres is that you can see visually in the scene view where the triggers are. Its probably all a case of, “more than one way to skin a cat”, and whilst my method works, it may not suit those seeking the highest level of efficiency.
Interested to hear feedback, and also how one would accurately run such an efficiency test.
Cheers
AaronC
PS put those triggers on the ignore raycast layer to prevent invisible brick walls when raycasting etc.
I updated the above code and converted it to javascript.
I’ve made a few changes for performance:
Caching of the meshfilter&transform components.
Distributing the LODupdate at random for every instance of this script.
No more useless Update() calls due to a coroutine
Now checking the object-camera distance
#pragma strict
@AddComponentMenu("Scripts/Misc/LevelOfDetail")
@RequireComponent(MeshFilter)
enum LOD_LEVEL { LOD0, LOD1, LOD2 }
/*
* Script to swap out meshes based on distance (LOD)
* C# version by Rich Hudson 2009 - Kranky Boy Games
* Javascript conversion + optimizations by Leepo - M2H
*/
class LODmeshes extends MonoBehaviour {
public var lodMesh0 : Mesh;
public var lodMesh1 : Mesh;
public var lodMesh2 : Mesh;
public var distanceLOD1 : float;
public var distanceLOD2 : float;
public var updateInterval : float = 2;
private var currentLOD : LOD_LEVEL= LOD_LEVEL.LOD2;
private var meshFilter : MeshFilter;
private var thisTransform : Transform;
function Awake()
{
meshFilter = GetComponent(MeshFilter);
thisTransform = transform;
//Distribute load by checkingLOD for every LOD script at a different time
var startIn : float = Random.Range(0.0, updateInterval);
InvokeRepeating("CheckLOD", startIn, updateInterval);
}
function CheckLOD()
{
var distanceFromObject : float = Vector3.Distance(thisTransform.position, Camera.main.transform.position);
if (distanceFromObject < distanceLOD1 currentLOD != LOD_LEVEL.LOD0)
{
currentLOD = LOD_LEVEL.LOD0;
meshFilter.mesh = lodMesh0;
}
else if (distanceFromObject >= distanceLOD1 distanceFromObject < distanceLOD2 currentLOD != LOD_LEVEL.LOD1)
{
currentLOD = LOD_LEVEL.LOD1;
meshFilter.mesh = lodMesh1;
}
else if (distanceFromObject >= distanceLOD2 currentLOD != LOD_LEVEL.LOD2)
{
currentLOD = LOD_LEVEL.LOD2;
meshFilter.mesh = lodMesh2;
}
}
}
Great Addition Leepo. I had also changed my C# script to use the InvokeRepeating, but I also merged it with Aaron’s. I would post here but I have a base class with different derived classed based on the number of LOD models.
People can just use yours or Aaron’s =)
Was a good discussion which I am sure will benefit a few people.
One use I have for it is with the DEXSOFT’s tree’s until they redo them for Unity.
Hey everyone: the methods being discussed here probably work quite well, but they do end up resulting in a fair bit of scripting, and related overhead.
One method of LOD that doesn’t require any scripting is actually demonstrated in the 3D Platformer Tutorial:
Make a separate camera for each level of detail, and match their near and far clipping planes so that they form a complete frustum.
Attach all the cameras to the same parent (or just make them children of the first camera).
Set the cameras’ render layers to mutually exclusive layers, or sets of layers.
Set the depth for each camera so that the closest frustum is highest, and the furthest is lowest.
Set the furthest camera’s clear flags to “skybox” and all other cameras to “depth only”.
For each visible GameObject, place the models for each LOD of a given mesh under a common parent.
Put each LOD mesh on a layer that will be shown by the corresponding camera.
This way all the culling and swapping is done by Unity’s frustum culling, and you never have to touch a script. If you want something to disappear when far away, simply don’t put a model on the corresponding layer.
Not sure I have thought your idea through Daniel, but in order to use the camera method, wouldn’t you need LOD models (the same amount as well) for every model or they will not show up?
It seems that even though the LOD meshes are being switched out the draw calls do not drop, but rather stays exactly the same as LOD0.
I might be wrong here, but doesn’t this defeat the whole purpose of the LOD script? To lower tris and draw calls by replacing a high detailed mesh with a lower detailed mesh based on distance?
Second Issue:
In the video you can see that the LOD1 loses its textures or rather it just references it mesh with out its textures… dunno.
Any insight on how I might get my draws calls to drop and preserve textures on the LOD1 and LOD2 meshes would really be helpful right now.