creat |
typo & make headlessuserculling feel less like an advertisement |
||
| Line 3: | Line 3: | ||
== Why cull? == | == Why cull? == | ||
As worlds get large, assets become | As worlds get large, assets become plentiful, and more users join a world, there will be things that are immediately relevant to a user's experience and things that are irrelevant to a user's experience. In general, things that are far away from a user, obscured by walls, separated via rooms, or out of a user's view range are not relevant to a user's immediate experience. If calculations or rendering was done on these irrelevant objects, then excess work is being done for little to no gain, and performance can overall suffer due to the extra work. | ||
By culling things that have no relevance to the user's current experience, the user's computer does not need to perform extra work on those things, which can slightly or significantly improve performance depending on the complexity of a world. | By culling things that have no relevance to the user's current experience, the user's computer does not need to perform extra work on those things, which can slightly or significantly improve performance depending on the complexity of a world. | ||
| Line 19: | Line 19: | ||
=== Skinned meshes === | === Skinned meshes === | ||
Skinned meshes have a few extra considerations due to the bones being able to deform the visual of the mesh. The <code>BoundsComputeMethod</code> field on a [[Component: | Skinned meshes have a few extra considerations due to the bones being able to deform the visual of the mesh. The <code>BoundsComputeMethod</code> field on a [[Component:SkinnedMeshRenderer|SkinnedMeshRenderer]] provides a few different options for how the bounds of a skinned mesh should be calculated, and thus when it should be culled: | ||
* <code>Static</code> is a very cheap method based on the mesh alone. This does not require realtime computation, so ideally use this if possible. | * <code>Static</code> is a very cheap method based on the mesh alone. This does not require realtime computation, so ideally use this if possible. | ||
| Line 48: | Line 48: | ||
* [[CJ Avatar Culling]]: <code>resrec:///U-Crusher/R-800E5546BCDFE9CB8E385FDBE91A391FEBFADB15CB072EFC8864F011C58EC5AA</code> | * [[CJ Avatar Culling]]: <code>resrec:///U-Crusher/R-800E5546BCDFE9CB8E385FDBE91A391FEBFADB15CB072EFC8864F011C58EC5AA</code> | ||
* [https://github.com/Raidriar796/HeadlessUserCulling HeadlessUserCulling] (by Raidriar) | * [https://github.com/Raidriar796/HeadlessUserCulling HeadlessUserCulling] (by Raidriar) | ||
** | ** No setup in-world, exists as part of the host client that gets spawned out on hosting a world. | ||
** | ** Highly recommended if culling is done on a headless host, as it provides a few extra niceties for multi-session hosting. | ||
** As of writing, only does distance-based culling, not any nearest-x-user culling | ** As of writing, only does distance-based culling, not any nearest-x-user culling | ||
[[Category:Optimization]] | [[Category:Optimization]] | ||
Latest revision as of 18:11, 11 December 2025
Culling refers to not processing or rendering specific parts of a world to improve performance. Culling can be done in a rendering setting or a more general CPU setting, with the former having a little bit of a built-in implementation in Resonite already.
Why cull?
As worlds get large, assets become plentiful, and more users join a world, there will be things that are immediately relevant to a user's experience and things that are irrelevant to a user's experience. In general, things that are far away from a user, obscured by walls, separated via rooms, or out of a user's view range are not relevant to a user's immediate experience. If calculations or rendering was done on these irrelevant objects, then excess work is being done for little to no gain, and performance can overall suffer due to the extra work.
By culling things that have no relevance to the user's current experience, the user's computer does not need to perform extra work on those things, which can slightly or significantly improve performance depending on the complexity of a world.
Built-in culling
Resonite currently performs a form of frustrum culling such that objects that are outside the field of view are usually not rendered. An exception to this is when a shadow of the object is within the view, in which case the object will need to be rendered to cast the shadow even if it's outside the view frustrum.
This frustrum culling is only done for rendering and not for any world items, meaning that ProtoFlux and components are unaffected by this.
There is, of course, some work required to calculate the bounds required for the culling to function, but this is only done once for static meshes, making the complexity of a static mesh irrelevant for culling to function.
To take advantage of this culling, it may be desirable to break up large meshes where not every part of the mesh will need to be visible at once, such as sprawling indoor environments. On the other hand, if many small meshes are expected to almost always be visible at the same time, it may be desirable to combine them into one mesh to reduce the amount of frustrum calculations that need to be done.
Skinned meshes
Skinned meshes have a few extra considerations due to the bones being able to deform the visual of the mesh. The BoundsComputeMethod field on a SkinnedMeshRenderer provides a few different options for how the bounds of a skinned mesh should be calculated, and thus when it should be culled:
Staticis a very cheap method based on the mesh alone. This does not require realtime computation, so ideally use this if possible.FastDisjointRootApproximatefirst merges all bones into disjoint groups (any overlapping bones are merged into a single one) to reduce overall number of bones, then uses those to approximate the bounds in realtime. This is the fastest realtime method and is recommended if parts of a mesh are being culled when usingStatic.MediumPerBoneApproximatecomputes mesh bounds from bounds of every single bone. This is more accurate, but also much slower.SlowRealtimeAccurateuses the actual transformed geometry for bounds calculation, requiring the entire mesh to be processed every frame. This is extremely heavy in comparison, but it will respect things like blendshapes in addition to bones.Proxyis slightly different from the others, but also potentially very cheap. It relies on the bounding box calculated for another SkinnedMeshRenderer referenced in theProxyBoundsSourcefield. This is useful in cases where you have a large main mesh and you need the visiblity of smaller meshes to be linked to it.
World-based culling
Outside of what Resonite has built in, culling systems can be implemented in worlds to disable certain parts of the world that are irrelvant to the user's current state. By doing so, less meshes will be used for frustrum culling and less work will be done on ProtoFlux and components.
The specific way that world-based culling systems are implemented can vary wildly. However, the most straightforward way to cull parts of a world is by using Slot spatial variables to define regions of a world, then use a sampler on the user's view position to determine what region a user is in. From there, you can hook up whatever you want to be culled to the region that a user is in. This can be combined with parenting the local user under a slot whenever they change regions, allowing users in far-away or occluded parts of the map to be culled as part of the world.
Specific culling considerations for collider components
Avoid performing manual culling of colliders in such a way that they are activated/deactivated very often. Collider performance impact works differently than for rendered meshes; performance costs for colliders are already heavily optimized as they are only checked when they're relevant. Toggling them on and off regularly can disrupt this under-the-hood optimization process and may even be more expensive.
User-based culling
In addition to world-based culling, there exist culling systems that can disable the avatars of users who are far away or limit the number of users that are visible at once. This can alleviate pressure on a per-session basis without needing any special setup in the world.
There is no perfect user culling system. All of them have some disadvantage or oddity due to a current lack of flexibility in Resonite to make something that works consistently, for every avatar, and in a reliable and simple way. As such, an overview of existing culling systems will be provided here:
- Cutout impostor system (by Kulza & Cyro):
resrec:///U-Kulza/R-6083c855-deb7-48d6-846c-77a4531f657e- Requires setup by placing a template slot in the world's CommonAvatarBuilder
- sctanf's culling system:
resrec:///U-sctanf/R-88d5a6ed-074f-497c-9496-ae9e6007e866 - CJ Avatar Culling:
resrec:///U-Crusher/R-800E5546BCDFE9CB8E385FDBE91A391FEBFADB15CB072EFC8864F011C58EC5AA - HeadlessUserCulling (by Raidriar)
- No setup in-world, exists as part of the host client that gets spawned out on hosting a world.
- Highly recommended if culling is done on a headless host, as it provides a few extra niceties for multi-session hosting.
- As of writing, only does distance-based culling, not any nearest-x-user culling