How to do frustum culling with instancedMesh?

I’m really confused about how frustumCulling is supposed to work with InstancedMeshes. My initial assumption was that I could manually compute a bounding sphere that surrounds all of the instances, but this produces bizarre results - it seems to behave as through my bounding sphere is always located at the origin.

My program uses large numbers of instance meshes, but they are all tightly clustered - that is, each instance mesh represents a set of objects that are near to each other. (Essentially the world is divided into cells, such that every instancedMesh is contained within the bounds of a single cell). Having frustumCulling enabled makes a huge improvement to my frame-rate, but it also produces weird artifacts where object disappear at unexpected times.

If InstancedMesh is not capable of being frustumCulled, is there some other way I could implement a customized view culling algorithm?

1 Like

Related to this, is there any way to do frustum culling on groups? I know the bounding sphere of all the objects in the group.

1 Like

three.js disables default frustum culling for instanced meshes, since it doesn’t compute the extent of the batch currently. But if you know the world bounds of each cell, you can create a THREE.Box3 representing that cell and just check the camera frustum against it each frame, constructing a THREE.Frustum from the camera’s projection matrix and intersecting it against the box. Set mesh.visible = false if you don’t want a cell to render, and you’ve got a custom culling system.

(Mostly for future readers, since you’re already using cells) It’s not possible do frustum culling of individual instances within an InstancedMesh, unless you want to do some clever rearranging of the instances dynamically, and this might or might not be worth it. Organizing instances into cells, as described above, is probably a better approach.

4 Likes

Unfortunately, the problem with setting mesh.visible = false is that the shadow map uses a different frustum than the camera. Worse, because I have portals, I’d have to recalculate the visibility for every portal pass.

What would be ideal is a custom callback, similar to onBeforeRender, that lets the object decide whether it should be rendered or not.

Just a follow-up on this: I’ve been doing a bit more experimenting with this, and the tantalizing aspect of it is that it almost works. That is, if I make a bunch of InstancedMeshes, each with a bounding sphere that surrounds all the instances, it works most of the time.

In fact, if I make the sphere a little too small, so that objects disappear before they leave the view frustum, 90% of the objects appear and disappear exactly when I would expect them to.

Unfortunately about 10% of the objects behave as if the bounding sphere was offset by some distance from where it should be. Moreover, these “errors” are different depending on how the bounding sphere is used - so for example, if raycasting, or rendering shadows, the set of erroneously culled objects is different than the set of erroneously culled objects seen from the main camera.

At this point, I am tempted to make a subclass of Scene and override the ‘traverseVisible’ method to implement object-based (rather than geometry-based) culling, but it looks like it is going to require a lot of hacking to get this to work.

Turns out it wasn’t that hard:

import { Sphere } from 'three';

export interface CullableObject {
  boundingSphere?: Sphere;
  cullable?: boolean;
}

const _sphere = new Sphere();

// Patch Frustum to allow culling at object level.
const prevImpl = Frustum.prototype.intersectsObject;
Frustum.prototype.intersectsObject = function (object: Object3D) {
  const cullable = object as unknown as CullableObject;
  if (cullable.boundingSphere) {
    _sphere.copy( cullable.boundingSphere ).applyMatrix4( object.matrixWorld );
    return this.intersectsSphere(_sphere);
  }
  return prevImpl.call(this, object);
};
4 Likes

Cool! Can you explain some more how this code works?

Normally Frustum doesn’t check whether the Object3D has a boundingSphere property, it checks whether the geometry buffer associated with that object does. My change monkey-patches Frustum to also check for a boundingSphere property on the object itself. You can then add a bounding sphere to any Object3D subclass, but you are responsible for computing the position and size of that sphere, it won’t do it automatically.

To be honest, I think this functionality should be added to three.js, but I haven’t had the energy to fight that battle.

1 Like

Your post is a great motivation and starting point. I will try to provide an implementation in Introduce intersectsFrustum so that objects can override it by OndrejSpanel · Pull Request #25525 · mrdoob/three.js · GitHub