Skip to content

Conversation

@donmccurdy
Copy link
Collaborator

@donmccurdy donmccurdy commented Apr 24, 2023

It's common practice for modelers to "apply" or "freeze" transforms, setting object origins to zero, which creates a problem for our default sort.

By sorting on the center of an object's bounding sphere, rather than its origin, we may improve the odds of drawing triangles in the intended order. This change fixes #25820, and could reduce overdraw more broadly. I did some light performance comparisons on larger scenes – after the bounding sphere is initially computed, I didn't see a significant difference in sorting one way or another.

I didn't find any authoritative sources on other engines, but random internet comments claim Unity and Unreal Engine sort by the bounding box center. I use the bounding sphere instead, simply because it's easier and more efficient to access its center than to compute the center of a THREE.Box3 instance.

@github-actions
Copy link

github-actions bot commented Apr 24, 2023

📦 Bundle size

Full ESM build, minified and gzipped.

Filesize dev Filesize PR Diff
634 kB (157.2 kB) 634.1 kB (157.2 kB) +74 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Filesize dev Filesize PR Diff
425.2 kB (103.1 kB) 425.2 kB (103.1 kB) +74 B

@donmccurdy donmccurdy marked this pull request as ready for review April 24, 2023 04:51
@mrdoob mrdoob added this to the r152 milestone Apr 24, 2023
@mrdoob mrdoob merged commit fab4ed4 into mrdoob:dev Apr 24, 2023
@donmccurdy donmccurdy deleted the feat/sort-bounding-sphere branch April 24, 2023 11:10
@donmccurdy
Copy link
Collaborator Author

donmccurdy commented Apr 24, 2023

One other effect to note – with this change, it's more likely that sort order will change as the camera moves. That's really the point of sorting by z-depth from the camera in the first place! But the sort is likely more discriminative now, so it's something to be aware of, if a previously-stable sort is now view dependent.

sunag added a commit to sunag/three.js that referenced this pull request Apr 25, 2023
sunag added a commit that referenced this pull request Apr 25, 2023
* EnvironmentNode: Fix flipY after improve positionWorldDirection

* WebGPUBackground: Fix clip background

* WebGPURenderer: Added update from #25913

* WebGPURenderer: Improve vertex format support.

* Example webgpu_backdrop: Fix space

* Added webgpu_loader_gltf_compressed example

* cleanup
@hybridherbst
Copy link
Contributor

hybridherbst commented May 3, 2023

I hadn't seen this change. This will break intent for a lot of models, quite to the contrary to what the issue description states people are aware that pivot position influences render order as it does in any engine. Three will now differ from this and make it impossible to actually control render order on purpose.

Please revert this change. It makes it impossible to intentionally control order of transparent objects. Let me know if you need example models that now break.

Unity and Unreal sort by pivot (so, by origin, not by bounds).

@donmccurdy
Copy link
Collaborator Author

donmccurdy commented May 3, 2023

I searched various discussion threads and found (non-authoritative) claims that Unreal, Unity, and Babylon.js all sort by center of bounds. I couldn't find claims they sort by pivot point. I don't have proof or authoritative references here, and it would be great to have that — if you have more information about Unity and Unreal here, could you share it?

@donmccurdy
Copy link
Collaborator Author

donmccurdy commented May 3, 2023

Comment by a Babylon.js developer, 2021 — "the default front to back sorting is using the bounding box center as the sorting key".

Comment by a Senior Software Engineer at Unity, 2015 — "The same way all transparent objects are distance-sorted in unity, which is simply using the center of the AABB (I just checked the code for this, and it surprised me a bit actually.. I would have expected some consideration of box size and closest point to the camera, but it is purely the centre of the box)".

@mrdoob
Copy link
Owner

mrdoob commented May 4, 2023

Would be nice to have a debug GLB file that shows how engines sorts objects.

@hybridherbst
Copy link
Contributor

@donmccurdy I'll double check!
@mrdoob I have some (all for bug repros), will see what I can share / how to make it obvious what happens

@bergden
Copy link
Contributor

bergden commented May 22, 2023

simply because it's easier and more efficient to access its center than to compute the center of a THREE.Box3 instance

This doesn't sound like a good reason to do something similar-ish to other engines
Moreover - computing the boundingSphere contains within it the same process for computing the boundingBox
https://github.com/mrdoob/three.js/blob/dev/src/core/BufferGeometry.js#L381
(which is very curious by itself - why not codeshare? Maybe even make the boundingBox a bonus artifact of the computeBoundingSphere)

If it's possible to make this change coherent with all the references you mentioned with 8 arithmetic operations I think it's worthwhile

@donmccurdy
Copy link
Collaborator Author

donmccurdy commented May 22, 2023

Much of what three.js (or any renderer) does is finding acceptable approximations for exact but costly calculations. In this case, both are approximations. Consider that the additional function calls and arithmetic operations have overhead per object in the scene, and that there may be many, many objects.

There would be a stronger case for using the AABB center instead of the sphere center if we had either of: (a) real-world examples where this choice matters, or (b) performance benchmarks showing they are similarly fast for large (1000+ object) scenes.

@bergden
Copy link
Contributor

bergden commented May 22, 2023

As far as I read the code I linked to, computing the boundingBox is 2 times faster than the boundingSphere in regards to the number of vertices
So computing the box and then the center is much less work for geometries with over 8 vertices

That being said, I think the better argument here is the one you made that it's better to have the same behavior as other engines¹ - and in that regards the boundingSphere's is probably closer but also quite different than the boundingBox' center

(b): https://jsfiddle.net/ayn8gus0/2/, https://jsfiddle.net/pgt13c5k/12/
Screenshot 2023-05-22 at 18 56 48
Screenshot 2023-05-22 at 18 56 42
Feel free to play with the size of the geometries² and their count, the order of execution also seems to matter so I have 2 fiddles
To minimize effects they might have on one another I put the timeouts but they can be removed and the results are pretty much the same
Adding the getCenter() has minimal effect on the timing in my tests: https://jsfiddle.net/pgt13c5k/13/

Generating a counter example should be simple but iff (b) is incorrect (computeBoundingSphere is slower than computeBoundingBox + getCenter) I think implementing the same logic as other engines is preferred

--- footnotes
¹ I dislike the idea of sorting according to boundingAnything's center because it incurs hidden performance costs and even objects instantiations which are not really to be expected. In the past where I had to initialize the boundingSphere/Box to some other approximation before raycasting to sidestep these hidden costs which made the first frame very computationally heavy for a big scene

² I'm pretty sure the morphAttributesPosition would have the leading effect in the computation times but I'm not sure how to easily generate an example of this, but again, the computation of boundingBox is done within the computation of the boundingSphere so the same argument applies for the morph stuff as well

@donmccurdy
Copy link
Collaborator Author

donmccurdy commented May 22, 2023

Ah sorry for miscommunication here, but the cost of computing the bounds is not the issue. To my understanding we compute both shapes anyway for frustum culling. That is a one-time initialization cost, unrelated to this PR.

What I'm concerned about is the per frame cost of extracting the center from our pre-computed bounds. If we're doing that at 60 FPS for 1,000 objects, 1–2 milliseconds of overhead per frame would matter.

@bergden
Copy link
Contributor

bergden commented May 22, 2023

Ah, oh, thanks for clarifying.
For rendering both are not calculated as far as I know, only for raycasting (and I think for that only the sphere is)

I think it's a side effect of this PR to consider even if only for the first frame (encountered in the wild)

And regarding the box vs. sphere - I think I'm becoming convinced the center is the same according to the current implementation - the additional pass seems only related to the radius

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.

GLTFLoader: Order of nodes in hierarchy is non-deterministic in some cases when compression is used

4 participants