bevyengine / bevyengine/bevy

Camera scoped visibility

Open
#22,921 7 comments 1 reaction 0 assignees View on GitHub
A-Rendering C-Feature D-Complex S-Needs-Design S-Needs-Goal X-Needs-SME
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## What problem does this solve or what need does it fill?

Today, by default, a camera renders all entities in the world (unless users add extra filtering like render layers). This is a sensible default for the common case where the world represents a single scene or model. However, many applications need to present multiple independent models within a bevy world.

As a result, when multiple logical models coexist in a single world, the default behavior where a camera sees everything becomes problematic. Each camera or view should only render the entities that belong to its logical model or view context.

Use Cases:
- CAD or modeling apps with split view: one viewport shows the full car, another shows only the engine, a third shows a different configuration of the same car.
- Drawing sheets with multiple drawing views placed on a single canvas, where each view references different models or the same model with different presentation rules.
- UI composition: a 3D scene rendered by one camera, with a UI layer rendered by another camera on top of it. In Bevy this is currently handled through UI-specific mechanisms, but this is not a fully generic solution. A general camera-scoped visibility construct would allow the same underlying concept to be applied uniformly to UI, overlays, tools, and custom layers.
- Advanced view composition: overlay views, preview windows, mini-maps, debug views, or isolated sub-scenes that must coexist in the same world but remain logically separated in terms of visibility.

## What solution would you like?

I do not have a final design yet, and I would like feedback on possible approaches. One potential solution is to introduce an optional component on the view that defines an explicit entity scope.

For example, a component like `ViewEntities`/`ViewEntityScope`/`CameraEntityScope` (name TBD) that contains a `EntityHashSet` with entities that this view is allowed to consider.

Proposed behavior:
- If `ViewEntities` is not present, keep the current default behavior (camera can see all entities, even from other views).
- If `ViewEntities` is present but empty, the camera sees nothing.
- If `ViewEntities` is present and contains entities, it becomes the primary scope filter for visibility for this camera.

Integration expectations:
- `CheckVisibility` (and related visibility collection) should iterate over the scoped entities when the scope component is present, instead of iterating over all entities in the world.
- This can also improve performance by avoiding work on entities that are irrelevant for a given view.
- Other view-dependent subsystems (e.g., lights, shadows, reflection probes - anything that builds per-view visible sets) should also respect the same camera scope to keep behavior consistent.

Existing mechanisms such as render layers can remain and continue to work as additional filters, but applied only within the scoped set when `ViewEntities` is present. In other words: scope first, then inherited visibility, then render layers, etc.

Entity lifetime and maintenance of the relationship between views and entities is still an open design question. Keeping the camera scope automatically in sync with entity despawns would require additional coordination between entities and views. A fully automatic bidirectional relationship, where each entity knows which views reference it, would introduce a many-to-many structure and likely require extra per-entity storage as well as additional update costs during insertions and removals. Given these trade-offs, for an initial iteration it may be reasonable to treat the scope set as user-managed data. In that case, the responsibility for keeping the set up to date would lie with the caller. Systems consuming the scope would be expected to handle stale entries gracefully, for example by skipping entities that no longer exist. This keeps the core design simpler and avoids introducing additional per-entity overhead.

## What alternative(s) have you considered?

The primary alternative I considered is using `RenderLayer`s. In practice, they can serve as a temporary solution and they work reasonably well for a limited number of views. However, this approach does not scale.

If we consider scenarios with tens, hundreds, or even thousands of cameras or views, combined with large numbers of entities (hundreds of thousands or more), `RenderLayer` based filtering becomes problematic. The current model effectively requires evaluating visibility across all relevant views and all entities. Conceptually this leads to quadratic behavior with respect to the number of views and entities. While parts of this process can be parallelized, parallelism does not fundamentally change the scaling characteristics. In environments like wasm running in the browser, where execution is typically single-threaded, parallelization is not even an option.

A scoped per-camera entity set would instead narrow the working set up front, allowing visibility evaluation to operate in linear time per view over its scoped entities, rather than across the entire world.

Another concern is memory overhead. In the best case, `RenderLayer`s can fit into a fixed-size structure (`SmallVec<[u64;1]>`), but this limits the number of distinct layers. Supporting more layers implies heap allocation. Even in the fixed-size case, the component itself is relatively large (24 bytes in x64, 16 bytes in wasm32). If applied to large numbers of entities - potentially hundreds of thousands or millions - this results in significant memory usage. For one million entities, this alone can account for tens of megabytes. If dynamic allocation is involved, the overhead increases further.

In addition, `RenderLayer`s are typically copied into the render world, effectively duplicating the memory footprint for that data. This amplifies the overall cost.

I also explored implementing a custom visibility system or custom `check_visibility` logic on the application side. However, this approach runs into integration issues. Multiple subsystems, including lights and other per-view render logic, rely on render layers and shared visibility semantics. Computed view visibility alone is not sufficient to fully override this behavior. As a result, a purely user-side custom solution cannot fully replace or bypass the existing render layer based filtering without deeper engine integration.

For these reasons, while render layers can act as a workaround, they do not provide a scalable or sufficiently generic solution for large-scale multi-view scenarios.

## Additional context

First of all, I would like to thank the Bevy team and the community for the incredible work on the engine. Bevy is a very thoughtfully designed and ambitious project, and it is exciting to see how rapidly it evolves while maintaining a strong architectural direction.

This proposal is shared in that spirit. I am fully aware that visibility and rendering architecture are sensitive areas of the engine, and I would greatly appreciate feedback on whether this idea aligns with Bevy's long-term design goals.

If this direction makes sense from an architectural perspective and is considered valuable, I would be happy to help explore or prototype a possible implementation.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.