mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.51k stars 35.37k forks source link

BatchedMesh without a transform array #29036

Open nkallen opened 3 months ago

nkallen commented 3 months ago

Description

As mentioned previously, we are using BatchedMesh extensively. However, we don't actually use the transform arrays. I would like to make that optional. For our use cases, it keeps the code simpler, it uses less VRAM, and it has a (small) effect on rasterization performance.

Solution

Something like:

            batchingMatrix: IS_BATCHEDMESH && object._matricesTexture !== null,
            batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null,

and

                p_uniforms.setOptional( _gl, object, 'batchingTexture' );
                if ( objects._matricesTexture !== null ) {

                    p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures );

                }

                p_uniforms.setOptional( _gl, object, 'batchingIdTexture' );
                if ( object._indirectTexture !== null ) {

                    p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures );

                }

Alternatives

I'm not sure

Additional context

No response

nkallen commented 2 months ago

@gkjohnson - hi, what are your thoughts on this? thx!

gkjohnson commented 2 months ago

I assume you're using you're overriding the material to use your own transform matrix texture, then?

nkallen commented 2 months ago

Hi,

For our specific needs we don't need to transform the objects so we set the matrix to the identity matrix:

https://github.com/mrdoob/three.js/pull/29037/commits/0377329cc67f1fcbe88bb09fa6e713bd3239d6df#diff-4338b6782db1d7497b33f9f329fb919968b56c15fc0bccea80e78135855692e9R4

In a CAD program, usually there is a distinction between the raw bodies and the assembly (instances of the bodies with transforms) and in our case we are not working with assemblies and so there is no need for a transform (for the static part of the scene) beyond the identity matrix.

gkjohnson commented 2 months ago

What's the reason for using BatchedMesh, then, instead of packing everything into a BufferGeometry? Are you using the implicit gl_DrawID for something else? Or using frustum culling / sorting?

nkallen commented 2 months ago

Hi,

So we have large scenes and everything is packed into one buffer. We want to keep draw calls to a minimum. But our scenes are dynamic: the buffer has objects being added and removed frequently (as the user creates/updates/destroys them) and the objects have visibility attributes as the user shows/hides objects or goes into "isolation" mode. (Below is a picture of a scene with one hidden object; the circular outline of the invisible object (sphere) is shown just for explanatory purposes)

Screen Shot 2024-08-07 at 09 34 29

Because we add and remove objects from the buffer, and the objects are not of uniform size in bytes, the buffer is sparsely populated. For that reason alone, we get a big performance benefit from using multidraw, rather than threejs's (geometry) groups (which are individual draw calls). The visibility modifiers (shown/hidden/isolated objects) also make the drawn ranges even sparser.

I will skip over how multiple materials are handled for brevity. But I'll also attach this picture here to note that the user's current topology selection (faces in the picture below) are also handled with multidraw. These are even smaller ranges in the buffer, and of course the users selection is a very sparse selection of ranges.

Screen Shot 2024-08-07 at 09 25 27

Every frame we take the list of currently visible objects, frustum cull them, sort transparent objects, then compute the ranges of the buffer we need to render. We then send that to multidraw using the _multiDrawCounts, _multiDrawStarts, etc.

We do intend to use the transform matrix / texture in a couple special cases and we do in fact already use gl_DrawId in one case. In the attached image below, the user's currently selected objects have a yellow outline; which is generated by doing edge detection on objects that have a random color assigned by a gl_DrawId. (the random color is not to be confused with the green and red here, which are user assigned materials and not used for edge detection).

Screen Shot 2024-08-07 at 09 21 26
gkjohnson commented 2 months ago

Thanks - short version is you use BatchedMesh without setting matrices for the following reasons:

nkallen commented 2 months ago

Hi, yes, sorry brevity is not one of my strengths. You understand correctly. My tldr version is simply that batching (multidraw) is a very flexible way to manage large (dynamic) scenes. With very few changes, the current implementation of BatchedMesh can offer users a ton of performance and flexibility.

nkallen commented 2 months ago

OK there was a bit of churn in the PRs yesterday, but the latest one should work. The old examples continue to work, and this new codepath is used in my latest beta