LOD + Instancing

Hey, hope you’re doing well :raised_hand::blush:

Recently I’ve read in a vegetation presentation about using LOD for grass:

In my mind, patches of grass are a perfect object to instance, due to their repetitiveness, but It’s unclear to me how to combine the two techniques :thinking:

Has anyone tried implementing level of detail alongside instancing in three.js? I mean you could probably add InstancedMeshes to LOD, but Im thinking about LODing instances of one mesh, for example:

  • 2 levels of detail variations of a Tree model are loaded :christmas_tree::evergreen_tree:
  • Tree is instanced across the scene to reduce draw calls
  • Tree instances 50m away from the camera are rendered with the lower detail version of the model

However, if Im correct (and I certainly might not be), instancing is done on the GPU level - you just tell gpu, the geometry is meant to be repeated by the count and transforms provided as per-instance attributes.
Meanwhile LOD is an application-level technique. I only decide what geometry gets send, and I cannot control which or how many vertices I want to run in the vertex shader.

Is this a limitation of WebGL, and therefore Horizon devs have access to tools which make it possible? Or am I missing something, for example patches of grass being instanced in smaller areas, allowing many Instanced LODs?

Then again, Babylon.js seems to support LODing Instances, although their architecture is quite different - mainly LOD and Mesh are not conceptually separated (can add LODs to any mesh) and instances are abstracted into actual objects within the engine.
Example: BabylonJS - Mesh LOD & Instances - Tutorialspoint
Docs: https://doc.babylonjs.com/how_to/how_to_use_instances (LOD is mentioned at the end)
Demo: https://www.babylonjs-playground.com/#0720FC#10

Have you ever encountered this issue? If yes, how did you solve it? What techniques are used for rendering such large, open areas? What are some resources you can point me to, to help me understand it better?

Thanks for reading :sparkling_heart:

1 Like

LODs in Babylon.js does work with instanced meshes (class named InstancedMesh) but this is not the same thing than the InstancedMesh class of Threejs. Instanced meshes of Threejs are more like what is called “thin instances” in Babylon.js (which don’t support LODs).

In Babylon.js, the InstancedMesh class makes a number of things to manage a list of visible instances and to use the right LOD for each one based on the distance to the camera.

I don’t think there’s the equivalent of Babylon.js InstancedMesh in Threejs, but I don’t know the library well enough to be sure.

The triangle range count can’t be changed per instance or such, but there isn’t really an issue in doing the same offline prepared in 3 buffers.

I wouldn’t care about LOD for grass too much, grass is a relatively small asset that will quickly go subpixel, what i do for dense auto-distributed assets like grass is using a 9 tile chunk formation around the camera. I initially faded it out in distance but figured just letting it dissolve by going subpixel looks even better. However it depends on if you can make use of MSAA where things will remain visible longer.

grass

The most work of grass goes into it’s texture, if stylized like in my case or not, the terrain material color in distance basically should match up with your closer grass, if this is the case it will appear like endless grass without using further LOD levels. If your ground material is different but still has grass on it LOD will make more sense, but only to a certain distance and density.

I’m using the same technique as a third LOD for forests, single trees become way to insignificant to be drawn even as a impostor, their mass basically is the only reason they still fill out pixels on the screen which basically will be just some average colors of the asset anymore.

Impostors is a topic for itself, each have cons and pros that can be more a matter of the asset. This here is the volume hull impostor technique i made as alternative to full volume impostors, it takes up significant less memory at higher resolutions, and serves an accurate original representation, as the worst visual issue you can get with LOD levels is visible popping which is hard to avoid with geometry levels.

When the axis helper appears the impostor is rendered, which is only a box:

Yes THREE doesn’t perform culling or such for the InstancedMesh class, it is basically just to make using instancing at all easier as before it required a bit complex shader editing in order to work with all features like shadows.

1 Like

It’s a nice presentation. For instancing - you only get repetition of geometry per shader, no more. Which means that same geometry can be rendered multiple times using the SAME shader.

If you want to LOD your vegetation in the same way as described in the paper, you need to have several draw calls, at least one per shader. Beyond this - it’s a matter of what you want to represent your vegetation clusters as, you could potentially have geometry be a single quad, and just instance that, and have more instances or less per cluster, but that would require a lot of CPU work.

A better option, if you want instancing is to have separate instance group per lod level. This would require custom culling code for three.js though.

I have frustum culling for instanced geometry in meep, it was relatively easy to implement. You have have a look at my earlier demo for the results as well.

All in all, it’s about driving shader complexity down, driving down number of polygons, and finally number of draw calls.

I would not get too obsessed with number of draw calls, having 3 draw calls per foliage type for LODs is a relatively trivial amount in my estimation.

1 Like