How to implement LOD handling?

Unity definitely lacks an integrated LOD handling system. It is a bit surprising that this feature has not been included yet because it is a standard in any “serious” game engine.
Scripting a LOD system is a cheap and low performance option at best but it seems that this is the only one currently available.

Some people have described other scripted LOD systems on the forums but I wonder if they are practicable. (UniLOD is not available on iPhone).

Basically, the two solutions described on the forums are:

  1. Add triggers around objects checking for the camera to switch to a higher LOD as the camera gets closer
    This solution seems a bit heavy on collision and script handling. Wouldn’t the processing of such collisions and script by the CPU kill any performance gain on the GPU?

Would it be better to add triggers to the camera itself, checking if objects are getting closer?

  1. Create several linked cameras, for example three cameras for three LODs (high, med and low). The near and far clip planes of those camera would be set so that each one renders only a portion of the visible scene:
    a. The far camera renders the more distant objects with the lowest LOD
    b. The med camera renders mid distance objects with the medium LOD
    c. The close camera renders close objects with the highest LOD

This second solution is interesting but leads to many questions:

  • What about objects crossing the far clip plane of one camera and the near of another. Will they be rendered twice?
  • What about objects without LOD? Is it necessary to add a fourth camera with a “full” frustrum covering the entire visible area just for non LOD objects?
  • The culling is performed just before rendering after all other tasks have been performed, including animation. What about skinned characters? If they have three LOD, all three LOD will be animated, roughly tripling the cost of animation and skinning, no?
  • Transparent faces are supposed to be rendered after all fully opaque faces have been rendered. In the case of three (or four) combined cameras, how will this work?
  • Will the integrity of the zbuffer be preserved?
  • What about full screen post processing effect, notably depth of field, will they still work with such a multi layered render?

Eventually, those solutions also have a main drawback. The choice of the correct LOD shall not be made only based on the distance from the object to the camera but on the actual size of the rendered object on screen. Indeed, even if the camera is very far from an object ( thus leading to choosing the lowest LOD because supposedly the object will be tiny on screen), it can in fact appear “big” on screen if the camera has a long focal length.

Is there any other (and better) solution to implement a LOD system in Unity?

I posted pretty much what you are asking for the other day… :wink:

http://forum.unity3d.com/threads/78137-is-there-a-way-to-quot-distance-quot-cull-on-everything-except-the-terrain

Simple LOD script.

If an object is greater than X distance from camera, use Y mesh, and do that a few times for however many levels you want. Works, works fast, works nice. I used to have one knocking about that I swore by but can’t remember where it is now. I think I originally got it from the wiki though and last I remember, it worked fine in 3.0 and should in 3.2 as it didn’t do anything fancy, simply replaced one mesh with another if it was more than a certain distance away. The various LOD’s I made quickly via polygoncruncher and Lightwave, which retained the UV’s.

Edit, ok found it. Was/is on the wiki though far as I remember, don’t know who originally made it.

var highLod : Mesh;
var medLod : Mesh;
var lowLod : Mesh;
var bBoard : Mesh;
var distance_high = 10.0;
var distance_med = 30.0;
var distance_low = 80.0;

function Update ()
{
  var campos = Camera.main.transform.position;
  var meshFilter : MeshFilter = GetComponent(MeshFilter);
  if ((transform.position-campos).sqrMagnitude < distance_high*distance_high)
  {
    meshFilter.sharedMesh = highLod;
  }
  else if ((transform.position-campos).sqrMagnitude < distance_med*distance_med)
  {
    meshFilter.sharedMesh = medLod;
  }
  
  else if ((transform.position-campos).sqrMagnitude < distance_med*distance_low)
  {
    meshFilter.sharedMesh = lowLod;
  }
  else
  {
    meshFilter.sharedMesh = bBoard;
  }
}

I think that it would be much easier than what you thought :slight_smile:

if you look at the script that Frank posted is pretty clear that all that you wanna do is to set 3 different models at different detail level and then gotta work on the distance to find a good balance.

A more complex way would be to make models that are hybrid, (like a model that has more details on one side but not on the other, so you can use that one facing the camera from the higher detail side, as transition model), to avoid the abrupt effect of replacing a boxy model with a more detailed one…or you could make a script that morph the low level object into the higher one…so it really depends from your models and type of game.

I am sure that Unity guys are planning to add a LOD system, but to me it can be done easily with a script, in the meantime that they work on it :wink:

So you mean that having all and every game objects of my scene calculating their distance to the camera at every frame will not be heavy on the CPU?

@darshie76 the abrupt change of LOD models is not really a problem.
My first work as a computer artist in the videogame industry was on “Motoracer” on PSone back in 1996. We had three LOD, the lowest being only to quads, one with a side render of the motorcycle and the other with a rear render of the motorcycle. And we were switching to that low model very close to the camera but no one ever noticed it.
The only tricky part was the pilot. It was a skinned character for the high model, but even the medium model had a fixed pilot. so we had a specific animation when the motorcycle was getting away from the camera to put the pilot in the exact same position as in the medium model. Thus switching from high to medium was invisible.

@Frank Oz: thanks for the script. I had found if of course but I really thought that I could not expect acceptable performances using this one.

Nope, shouldn’t tax the system much at all, I’ve never noticed a problem except a slight jolt when a LOT of objects change LOD levels at once, and you’ll not have that happen in actual use (I’m taking really really high poly and large number of them, as a stress test and nobody would ever dream of going to that level in an actual game).

Also because it’s replacing meshes with different ones, you get to use different materials too, like using simple materials for distant objects, and ones with parallax and normal maps for close up ones. The collision shapes stay the same (you could probably edit it to change those too) so things moving around them wouldn’t suddenly become trapped if you move further away.

Providing the individual meshes UV coords stay the same, it wont harm the lightmaps they might be using either.

Lastly, works just fine with Occlusion Culling, Camera distance culling and AFAIK Layer culling. Throw all this at it and you’re going to have a very optimized map, even if it requires a bit more work. Though something like polygoncruncher makes creating LOD levels a breeze.

Oh before I forget, I think also you should be able to edit the distances with the script on a per object basis too. So make the larger objects switch LOD at greater distances than the smaller ones, and if you felt like it, as is shown what I would do with it, the farthest version would become a super simple billboard which would use a special shader which would then smoothly fade out rather than become clipped. Which I needed to get around certain issues with fog that I never liked.

As far as I know, in order to do LOD you’d have to calculate distances for objects somehow anyway, so whether you’re doing it or the engine is doing it isn’t that important. What’s more important is the technique you use for it; brute-force calculating every frame isn’t really ideal. For one thing, it would be better if you turned off the calculations when the mesh isn’t visible (it’s irrelevant how many polygons a mesh is if you can’t even see it). Also, even when visible, doing it every frame generally isn’t necessary; use InvokeRepeating to do it maybe 4 times per second or something, depending on how fast-moving your game is. One a more fine-tuned level, I believe it’s more efficient to compare single elements instead of using sqrMagnitude. i.e., first just check .x, and if that’s over a certain amount you can skip the rest.

Edit: as far as that script goes, declaring “var campos = Camera.main.transform.position;
var meshFilter : MeshFilter = GetComponent(MeshFilter);” every frame is quite inefficient. The camera position should be taken from a central script, instead of all objects getting its transform component. Along those lines, doing GetComponent(MeshFilter) every frame makes no sense when it can be done once at startup.

–Eric

I use method two, with two LOD’s with the HI quality camera extending to 150 and LO quality extending from 150 - 900.

I’ve tried the calculating distance method before and while it works, trust me, calculating distance is an expensive equation and can slow your game down really quickly.

If you want to test it, attach the distance script to a prefab and scatter about 50 of them in the scene and compare the frame-rate with camera culling, it will be night and day…

*EDIT:
And to answer your questions about method two:

  • What about objects crossing the far clip plane of one camera and the near of another. Will they be rendered twice?
    (Yes, they will be rendered twice, but since the low LOD geometry will be negligible, it is not an issue.)
  • What about objects without LOD? Is it necessary to add a fourth camera with a “full” frustrum covering the entire visible area just for non LOD objects?
    You could, but I just add them to a (HI+LO) layer included on both cameras.
  • The culling is performed just before rendering after all other tasks have been performed, including animation. What about skinned characters? If they have three LOD, all three LOD will be animated, roughly tripling the cost of animation and skinning, no?
    I would put your skinned characters on the HI level only, but that’s my opinion and the way I do it. Also, activate / deactivate skinned characters with triggers, because culling doesn’t “deactivate” them, it only hides them.
  • Transparent faces are supposed to be rendered after all fully opaque faces have been rendered. In the case of three (or four) combined cameras, how will this work?
    Haven’t had any issues with transparent objects.
  • Will the integrity of the zbuffer be preserved?
    What do you need it to be preserved for?
  • What about full screen post processing effect, notably depth of field, will they still work with such a multi layered render?
    If the effect is added to both cameras, it will work. Or you can write a custom shader that writes these camera passes to render targets and processes them in the end in one pass.

I have tried having the camera doing a raycast every X seconds (kinda like a laser scanner) to see what are the object in the field of vision, and the objects would return a value of 0, 1 or 2 based upon the “area” where they have to switch LOD; in this way you don’t have to calculate what is outside the field of vision, that just don’t get rendered using the hi count meshes.

This of course works for few objects, but if you have many objects I guess that the overload would be too much and it will just sit trying to figure out what to do :stuck_out_tongue: Otherwise if you call each object in the field of vision and ask them their relative position from the camera you could get something better in terms of performances, but still is an expensive operation when you deal with many objects

@Deckard: I see often in commercial games how ugly is to see a mountain skip from a few tris to a nice detailed mountain with textures and normal maps and so on; in your case is easier since the relative speed is high, so is hard to catch some particular shifts in the LOD, but if you play a game like an MMORPG and you try to change LOD to the distant mountains, you gotta come up with something to minimize the shift between LOD’s, otherwise there is no way that the player will not notice it :wink:

Good points about changing the material; but even better to make a billboard with just the render of the object, that points always at the player so he does not notice the fact that is a 2d plane and not a solid 3d object (i think that Unity does something similar with trees, right?).

The LOD (like the occlusion culling ) are one of these things that if you make it right it gives you a lot of satisfaction :slight_smile:

I don’t understand that considering the OnBecameVisible and OnBecameInvisible functions already tell you this.

–Eric

I’ve tried the camera-based LODing and while it works for some things, it makes post processing effects a nightmare to manage and introduces a lot of other problems.

The method I use to do LODing is similar to that posted by Frank, but tries to be a bit smarter about what it checks for to redice overhead as much as possible, it also spreads out the (already minimal) load from frame to frame.

I use OnBecameVisible and OnBecameInvisible to maintain a list of visible objects with a mesh component then use a co-routine to check the distance/LOD of one object per frame then yield until the next update. Using sqrMagnitude avoids needing to do any expensive square roots. For example:

xyz1 = camera position.
xyz2 = object position.

Squared distance = (x2-x1) + (y2-y1) + (z2-z1)

pre-square your LOD distances for the conditionals and you have a complete LOD solution that costs five simple arithimitic operations and a handful of floating point conditionals (depending on how many LOD levels you have) per frame.

You’re not going to get any faster than that and it avoids all the nasties introduced by using multiple cameras.

This works perfectly for human driven games (first/third person). I also do a LOD check when an object becomes visible. For racing games or strategy games where the camera can change location very rapidly simply yeild after 5/6 checks, or scale it by camera velocity (no need to check aggressively if the camera isn’t moving/rotating).

The only thing I don’t like about this method is that it’s totally frame rate dependant which is always bad in real-time systems. I plan to implement a weighted limit that checks more objects per update if the overall framerate is lower as a more aggressive check at very low frame-rates should (in theory) pay off by switching out detail of the (presumably) more numerous distant objects quicker. At the moment my game runs at 200 odd fps and only has at most about 500 objects, but I’ve stressed it with up to 2000 units (I’m making an RTS) with 5 checks per frame and it worked like a charm. The overhead in the profiler for the LOD checks was about 0.02ms

As the OP alluded to, that approach doesn’t account for the apparent size of the object. It may work ok in some cases, but if you have any type of ‘zoom’ feature in your game, it’s not really suitable.

Also, one way to improve the ‘distance threshold’ method is to add a bit of hysteresis to avoid repeated changes in level of detail when the object is near a distance threshold.

As has been noted, there are many ways the LOD check can be optimized (it definitely shouldn’t be necessary to calculate the distance for each object on each update).

Use prefabs for different lod levels and place em in different layers and use plane clipping and layer support in camera system.
A) Its easy
B) Unity handles all the internal computation, because it has to anyway
C) Greater control over lodded obejcts.

D) This question is asked and answered some many times, that unity developers should really really include something to handle this internally.

@Cameron : thanks for describing several ways to optimize the distance calculation method. A part of my problem is that I am creating an action-adventure game with several cameras. The game keeps switching from one camera to another every 1-8 seconds. So it would be difficult to use time slicing and distribute the distance calculation over time for optimization.

@tarragon: I am not sure to fully understand what you are saying. Do you mean that I can assign the different LODs of a given object to different layers and then assign user defined near and far clipping planes to each layer, all this within one and only one camera?

There are too main drawbacks to this option:

  1. it does not take into account the actual onscreen size of the object
  2. the LOD changing distance will be the same for all objects, meaning that the biggest and the smallest objects will all switch to a lower resolution mesh at the same distance

I totally agree that the handling of LOD should be a built in feature

I explained this technique(dremora came up with it, i think) poorly, sorry.

You can clip(limit) camera, to display only objects in given range. You can OR cameras. You can set different layer(show only objects in that layer) for each camera.

Use camera layer cull distance to have different culling distances for each layer so you can have small objects culled sooner than large ones. It’s effectively the same as the multiple camera trick except it wont mess up post effects because you’re only using one camera

Nobody mentioned yet that the camera layer culling will make the object appear or disappear suddenly and based on its pivot. Fine for small objects, not for anything larger. At least that’s what it did last time I used camera culling (wanted to use it for custom large clumps of grass assuming it would behave like the camera distance cull but on a more individual basis, it didn’t). Not tried it since earlier versions though, fixed in 3.2 maybe?

I’m sorry but I still don’t understand. If I am correct, the camera layer cull distance only defines a far clip plane where objects of a given layer stop being rendered.
How exactly can you make an object switch to a lower resolution mesh at a given distance using this function? You would at least need a near clip plane parameter.

Layer culling is only really useful for small objects like grass, for larger objects like buildings you’d use one of the other methods mentioned already that tests for distance from the camera and then turns the renderer on/off for the relevant models.

You could if you wanted to, have an object with 2 models on all the time and just put the high detail model over the low detail one so that it hides the low detail one when it’s not being culled. No scripting would be involved but maybe not the effect you wanted.