Skip to content

Add BatchedMesh LOD and BVH example page #31239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 14, 2025

Conversation

agargaro
Copy link
Contributor

@agargaro agargaro commented Jun 7, 2025

Description

Hi! I've created an example that demonstrates how to manage LODs for BatchedMesh geometries, along with BVH acceleration for raycasting and frustum culling.

It's built using my @three.ez/batched-mesh-extensions library together with three-mesh-bvh by @gkjohnson.

@mrdoob Unlike the version published in X, here I also added raycasting and GUI.

You can check out the live demo here: https://agargaro.github.io/three.js/examples/webgl_batch_lod_bvh.html

Is that okay or should I create a different type of example?

@cmhhelgeson
Copy link
Contributor

cmhhelgeson commented Jun 7, 2025

Description

Hi! I've created an example that demonstrates how to manage LODs for BatchedMesh geometries, along with BVH acceleration for raycasting and frustum culling.

It's built using my @three.ez/batched-mesh-extensions library together with three-mesh-bvh by @gkjohnson.

@mrdoob Unlike the version published in X, here I also added raycasting and GUI.

You can check out the live demo here: https://agargaro.github.io/three.js/examples/webgl_batch_lod_bvh.html

Is that okay or should I create a different type of example?

This is really cool.

I think it would be neat if there was some way to see what the LODS closer to the horizon look like. Maybe a boolean settings like 'staticLOD' or 'freezeDistanceCalculation' that maintains the last stored distance from each mesh to the camera, such that when you move towards the back of the scene you can more clearly see how the LODs detail changes based on distance. Maybe my eyes are just bad and I just can't see the LODs for the farthest objects : )

@agargaro
Copy link
Contributor Author

agargaro commented Jun 8, 2025

I think it would be neat if there was some way to see what the LODS closer to the horizon look like. Maybe a boolean settings like 'staticLOD' or 'freezeDistanceCalculation' that maintains the last stored distance from each mesh to the camera, such that when you move towards the back of the scene you can more clearly see how the LODs detail changes based on distance. Maybe my eyes are just bad and I just can't see the LODs for the farthest objects : )

I could override the onBeforeRender method with an empty function, to avoid recalculation of the multidraw data. However, this would also disable frustum culling, resulting frozen.

@agargaro
Copy link
Contributor Author

agargaro commented Jun 8, 2025

I've added the freeze flag with latest commit.

@Mugen87 Mugen87 added this to the r178 milestone Jun 12, 2025
@Mugen87 Mugen87 merged commit e345468 into mrdoob:dev Jun 14, 2025
11 checks passed
@WestLangley
Copy link
Collaborator

WestLangley commented Jun 14, 2025

I think the small value of the far plane is giving misleading results.

  1. increase the far plane by a factor of 10
  2. remove fog
  3. start demo
  4. rotate camera to top view
  5. zoom out, frame rate goes toward zero
  6. click freeze
  7. zoom in to see the LODs
  8. frame rate remains poor for some reason

iMac M1, Chrome 137.0.7151.120 and Safari 18.5 (20621.2.5.11.8) -- running full-screen

@agargaro agargaro deleted the batch-lod-bvh-example branch June 15, 2025 09:19
@agargaro
Copy link
Contributor Author

I think the small value of the far plane is giving misleading results.

  1. increase the far plane by a factor of 10
  2. remove fog
  3. start demo
  4. rotate camera to top view
  5. zoom out, frame rate goes toward zero
  6. click freeze
  7. zoom in to see the LODs
  8. frame rate remains poor for some reason

Yes, it's true, the performance becomes bad in that case.
I've updated the example by adding more information for clarity.

image

By increasing the far the instances to render become too many and there's already an opened issue showing that multidraw is not good for rendering many instances. We should use the instanced multidraw instead, but we should implement it in the core.

Also on the CPU side we have a slowdown due to the sort of instances (500k each frame) and frustum culling that loses the benefits of using BVH by having to filter all nodes.

I can modify the example if necessary, I can add LODs with fewer triangles, space the instances more between them to make them render less, scale them differently, or change the example completely.

Updated example here.

@gkjohnson
Copy link
Collaborator

gkjohnson commented Jun 18, 2025

frame rate remains poor for some reason

It seems to be the case that uploading even a not-so-large buffer to the GPU is causing framerate dips. It's not exactly clear to me why this is the case because the buffers are otherwise not that large. When following your steps the BatchedMesh is drawing (and therefore uploading an instruction buffer for) ~60,500 meshes which results in an FPS of 40 down from 120. However that's only 0.5 MB of data to upload for a draw (two Int32 buffers at 60,500 elements).

However when explicitly marking the "matricesTexture" as "needsUpdate" with a shorter far plane, forcing a full reupload of the texture every frame my framerate only dips to 100. That texture is 3.9MB, though (dimensions of ~492x492). It's really not clear to me where or why this bottleneck is here.

Perhaps @greggman or @kenrussell have some insight here? To boil it down: Why does uploading a pair of buffers totally 0.25MB for multidraw tank framerate so significantly (120 to 40 fps) while uploading a texture of size 32MB every frame does not (120 to 100 fps)? This is in WebGL2 on Chrome.

@kenrussell
Copy link

@gkjohnson what OS and GPU type is this slowdown happening on?

I have a feeling that the heuristics I added to ANGLE's Metal backend on top of @greggman 's glBufferSubData implementation are causing slowdowns. But #28776 sounds like it was reported on Windows, so any slowdown would be a different root cause. @vonture can hopefully help with ANGLE performance issues on D3D11.

Does replacing any bufferSubData calls with bufferData and maintaining CPU-side shadows improve the performance?

@WestLangley
Copy link
Collaborator

#31239 (comment) updated with OS / device info

@gkjohnson
Copy link
Collaborator

gkjohnson commented Jun 18, 2025

@kenrussell

I have a feeling that the heuristics I added to ANGLE's Metal backend on top of greggman 's glBufferSubData implementation are causing slowdowns.

I'm running on:

  • 2021 Apple M1 Pro Macbook
  • Version 137.0.7151.69 (Official Build) (arm64)

But I see the same slowdown on Safari Version 18.5 (20621.2.5.11.8).

Does replacing any bufferSubData calls with bufferData and maintaining CPU-side shadows improve the performance?

Can you elaborate on what you mean by this? Is this something you're suggesting to do in ANGLE or for WebGL calls? It doesn't look like we need to use either of these functions when calling extension.multiDrawArraysWEBGL.

@kenrussell
Copy link

@gkjohnson my thought was that if the app-level code (either in Three or in these demos) has one or more buffer objects that it's updating using bufferSubData calls, to try to avoid this by maintaining a CPU-side cache of the data in the buffer, double-buffering those buffers, and each frame, uploading the buffer's entire data using bufferData. That's worked around the performance issue I introduced in ANGLE's Metal backend for a couple of other apps.

However if the problem's happening on Windows too, then the root cause is probably different and this workaround probably won't work. (Or maybe ANGLE's D3D11 backend has the same performance issue.)

Apologies, I'm on leave through the end of July, and can't help in any significant way right now. Hope that @greggman or @vonture might be able to help. Please consider posting to the WebGL Dev List and, if you can provide a small test case showing the poor performance of the WEBGL_multi_draw extension, file a bug on anglebug.com .

@gkjohnson
Copy link
Collaborator

Thanks Ken -

my thought was that if the app-level code (either in Three or in these demos)

Yeah the Int32Array buffers are passed directly to the multi draw functions with the length of buffer to use, as required by the API. And the issue still occurs when passing the full length of the buffers in as the draw count.

if you can provide a small test case showing the poor performance of the WEBGL_multi_draw extension

cc @CodyJasonBennett is providing a small, WebGL-only demonstration of the poor performance when using large buffers with multidraw something you would be interested in helping to provide? I'd look into it but I don't think my raw WebGL API knowledge is quite as sharp. Specifically I think it's worth showing that the performance is better when uploading a significantly larger texture vs a smaller amount of buffer data for multidraw.

@CodyJasonBennett
Copy link
Contributor

Sure. I understand the issue is not updating a WebGLBuffer of the drawn geometry, but the typed arrays fed into WEBGL_multi_draw from the CPU? My understanding of BatchedMesh is that it keeps all vertex data resident and uses indirection with the multidraw API for any visibility change and instancing. In other words, it does not allow geometry to grow/shrink, and it's unexpected to update vertex data at runtime. LODs can be implemented with indices into the LOD0 vertices.

I have experiments where I stream geometry with WEBGL_multi_draw and change the bound range to juggle multiple update regions over frames or bind pooled buffers to similar effect, but I am happy to make a reduced example if that helps to track down performance regression here.

As for known issues, I am only aware of https://groups.google.com/g/webgl-dev-list/c/ChKpSHZNbI8/m/1oUaWiCBCQAJ?pli=1 where the CPU-side buffers can undergo an expensive type conversion in the browser, but we seem to pass the correct type to avoid it, reading the related commits.

@gkjohnson
Copy link
Collaborator

gkjohnson commented Jun 30, 2025

I understand the issue is not updating a WebGLBuffer of the drawn geometry, but the typed arrays fed into WEBGL_multi_draw from the CPU?

I've created a small demo here. BatchedMesh is passing Int32Arrays into the multiDrawArraysWebGL function to draw a geometry that does not change and is using textures to store transform data. That texture also does not change unless a transform is updated.

The core of the issue is that when drawing all 50,000 objects with no sorting or culling (meaning no extra work per frame) the framerate dips significantly (~55fps on my machine down from 120) and it's not clear why. The first assumption might be that uploading those multidraw buffers may be too large and slowing down the frame when uploading to the GPU. But if you draw a low amount of objects and upload a 500px x 500px floating point texture every frame, which is ~10x more memory, then my framerate remains at 120 so buffer size being the only issue doesn't line up.

So I think a minimal WebGL demonstration here would be showing that calling multiDrawArraysWebGL with buffers for 50,000 objects is slow but uploading a 500px texture with with texSubImage2D (I think that's what's being used here) every frame is fast.

Happy to talk about this more on Discord if needed.

@gkjohnson
Copy link
Collaborator

I'll post this to the WebGL dev list and angle later but I've created a reproduction showing multi-draws poor performance with a large amount of instances here. The repro shows that there is a drastic performance drop when drawing the same amount of instances with multi draw compared to drawArraysInstanced and that it cannot be attributed to the data being uploaded to the GPU due to the "firstsList" and "countsList" arrays.

@greggman
Copy link
Contributor

thanks for the repo!

https://issues.chromium.org/issues/437957104

@gkjohnson
Copy link
Collaborator

Thanks! @greggman @kenrussell - would you still like me to make a topic on the WebGL Dev List and make report an Angle bug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants