Generate LOD (Solved)

Hi,

LOD is a nice feature to have. Some of you may say it makes no sense for the model itself, but i disagree with this.

It makes sense if (as i am doing) you are displaying huge models of steel constructions (some about 8 million triangles) which have a lot of tiny bolts (screws) to display.



From a certain distance, these bolts are not visible that much but have lots of triangles which slow things down.

What i would like to have is a LOD control which does 2 things:

  • beneath distance x display replacement geometry (for example the boundary object only)
  • beneath distance y do not display object at all



    As i am programmatically creating the geometry i have the original and the replacement geometry as Geometry objects.

    My question is, how do i create a Mesh with these two geometries for the different LOD levels?

    Which classes should i use and ist the task of not displaying an object at all after a certain distance possible?



    I could not find documentation about implementing LOD dynamically, all examples assume import of ogre models with LOD pregenerated.

    Would be glad if some of you could provide some hints where to start here.

A geometry has different LOD levels and a mesh per level. You can either generate the meshes and assign them or you can create a Control that handles that based on specific parameters like model type and camera location.

Wait a minute, you mean a geometry has several meshes for each level … i thought a mesh has several geometries for each level.

Do you have some code snippets on how that assignment is to be made?

The LOD levels are essentially just index buffers. When you switch to a lower level you reduce triangles by using a smaller (reduced) index buffer. In your case its probably best to just hide the bolts when you get too far. It will be significantly more efficient than rendering them when far away.

Aaah … ok, here was my fault. I always thought a different lod level could be a completely different geometry.

Already had some idea this could be just another index buffer after looking at GeometryBatchFactory.makeLod() but i had not fully understand the method.

Thank you for clarification.



What i have thought about now is to write my own controller which replaces the geometry or hides it completely.

The idea is, the controller stores the original object, removes it from parent node and adds a replacement geometry to the node, having the controller object (storing the old object) assigned again.

Do you think this would be the right aproach?



About hiding an object:

Is there some flag to be used to have a object still attached to the branch but removed from the render queue?

As i understand controllers, they are only executed on objects which are attached to rootNode. Just removing the object would mean it never comes back, if my assumption is right on this point.



Sorry for my lack of knowledge regarding this topic, but this is the first time i use jme3 (my jme2 knowledge is a bit rusty too) and LOD as well as controllers. It takes some time to understand the whole logic behind it.

Just figured it out, Controller works like a charm.



I have created a DistanceLodController which is added to the Node containing all geometries for one item (may be more than one). At DISTANCE_SIMPLIFIED the real geometries are detached from node and simplified boundary geometry is computed (only once and saved to array) and attached to node. At DISTANCE_HIDE all geometries are detached from Node which is left empty.



Great job with jme3, once you get the logic things are really easy … i’m truly impressed.



For all of you interested, here the code for my DistanceLodControl:

[java]

import java.util.ArrayList;



import com.jme3.bounding.BoundingBox;

import com.jme3.bounding.BoundingVolume;

import com.jme3.renderer.Camera;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.control.AbstractControl;

import com.jme3.scene.control.Control;

import com.jme3.scene.shape.Box;



public class DistanceLodControl extends AbstractControl

{

private ArrayList<Geometry> realGeometries;

private ArrayList<Geometry> simplifiedGeometries;



private static final float DISTANCE_SIMPLIFY = 10;

private static final float DISTANCE_HIDE = 20;



private boolean simplified;

private boolean hidden;



private int geomSize;



private BoundingVolume lastBound;



@Override

public Control cloneForSpatial(Spatial spatial)

{

return null;

}



@Override

protected void controlRender(RenderManager rm, ViewPort vp)

{

Camera cam = vp.getCamera();

BoundingVolume bv = this.spatial.getWorldBound();



// if node geometries are hidden, bound is null, so take last available

// before hiding

if (bv == null)

{

bv = this.lastBound;

}

else

{

this.lastBound = bv;

}



float distance = bv.distanceTo(cam.getLocation());



if (distance > DistanceLodControl.DISTANCE_HIDE)

{

if (!this.hidden)

{

for (int i = 0; i < this.geomSize; i++)

{

if (this.simplified)

{

this.simplifiedGeometries.get(i).removeFromParent();

}

else

{

this.realGeometries.get(i).removeFromParent();

}

}



this.spatial.updateGeometricState();



this.hidden = true;

this.simplified = false;

}

}

else if (distance > DistanceLodControl.DISTANCE_SIMPLIFY)

{

if (!this.simplified)

{

if (this.simplifiedGeometries == null)

{

// no simplified geometries available, compute them



this.simplifiedGeometries = new ArrayList<Geometry>();



for (int i = 0; i < this.geomSize; i++)

{

Geometry geom = this.realGeometries.get(i);

geom.getMesh().updateBound();

BoundingBox bounding_box = (BoundingBox)geom.getMesh().getBound();



Geometry simplified_geom = new Geometry(geom.getName(), new Box(bounding_box.getXExtent(),

bounding_box.getYExtent(), bounding_box.getZExtent()));

simplified_geom.setLocalTranslation(bounding_box.getCenter());

simplified_geom.setMaterial(geom.getMaterial());



this.simplifiedGeometries.add(simplified_geom);

}

}



for (int i = 0; i < this.geomSize; i++)

{

this.realGeometries.get(i).removeFromParent();

((Node)this.spatial).attachChild(this.simplifiedGeometries.get(i));

}



this.spatial.updateGeometricState();



this.simplified = true;

this.hidden = false;

}

}

else

{

if (this.hidden)

{

for (int i = 0; i < this.geomSize; i++)

{

((Node)this.spatial).attachChild(this.realGeometries.get(i));

}

}

else if (this.simplified)

{

for (int i = 0; i < this.geomSize; i++)

{

this.simplifiedGeometries.get(i).removeFromParent();

((Node)this.spatial).attachChild(this.realGeometries.get(i));

}

}



this.spatial.updateGeometricState();



this.simplified = false;

this.hidden = false;

}



}



@Override

protected void controlUpdate(float tpf)

{

// NOOP

}



@Override

public void setSpatial(Spatial spatial)

{

if (!(spatial instanceof Node))

{

throw new IllegalArgumentException(“distance lod can only be attached to nodes”);

}



super.setSpatial(spatial);



this.realGeometries = new ArrayList<Geometry>();

for (Spatial child : ((Node)spatial).getChildren())

{

if (child instanceof Geometry)

{

this.realGeometries.add((Geometry)child);

}

}



this.geomSize = this.realGeometries.size();

}



}

[/java]

3 Likes

You should probably do the changes in the SceneGraph in the update() method, not in render(), normally render() should not modify the scenegraph, if at all then only the cam position.

Thanks for the hint, will do so, but where do i get the viewport camera from in controlUpdate method as there is no Viewport object given?

give it in the construcot of your controll as parameter?

Did something slightly different, saving the cam object to a static variable, so it is accessible from my whole application.

Works fine.



Thank you all for helping.

Just a question about this. You say that control’s like these should be done on update() and not render(). But the BillboardControl.java in jme3 today does model rotation on render and not update. Could this create any artifacts or have any performance impact?



Btw, this culling control that dm2222 has created is a good candidate for the jme3 API imo, as it allows complete Node culling at any given distance while still having a longer view frustrum.



But at the same time I guess having many controls for every node around can have a major performance impact.

As a sidenote it strikes me that all Billboard controls for typical grass or distant LODs should share their billboard calculations. As it is now you have to create a BillboardControl for each Node that should be facing the camera, which means a lot of unnecessary calculation. If you could assume that each node used as a billboard has no rotation before, all nodes should simply get the rotation coords to face the camera. I know it wont be 100% precise as their rotation towards the viewer is a bit different based on where they are in your view for them to be facing directly you, but I doubt that would be very noticeable as they just have to face the plane of the camera. It might even look more natural as you are strafing since that wont rotate the billboards as you pass them.