Note:
I need to edit this note when I get the chance, so some things might not be up to date:
SubView
, since I misunderstood what it was for previously and the names mismatch here.Currently, Bevy Camera
s are kind of a leaky abstraction: multi-camera setups require a lot of manual fiddling with viewports, and post-processing artifacts are common.
I'm proposing to remove Camera
from its role as a fundamental rendering primitive and introduce a few new concepts to fill in the gap: Compositor
s and View
s.
Camera
There's a conflict within Camera
that I think is the source of a lot of our issues: cameras are conceptually configured fully individually, with their own ordering index and viewport, rendering effects, even potentially a custom render graph, but we currently try really hard to share intermediate textures between cameras.
As a result, a lot of the compositing logic leaks into the rest of the graph: each camera has to recalculate projection matrices, pass through viewports, worry whether or not to clear the main textures or not, and whether or not to apply tonemapping.
Viewports are annoying to set up. Part of my rationale for doing "top down" layout from an external entity is that it opens up the possibility of nicer layout APIs in the future. If each camera stays in charge of choosing its own viewport, I don't see a good way forward there.
Currently, UI hierarchies render themselves to the entity referenced by their UiTargetCamera
component. This Camera
is expected to render the UI by calling the UI render graph at the appropriate point in its render graph. This is kind of a weird relationshipโ I think UI should be the same fundamental "renders to a render target" thing under the hood as cameras, and not require any special handling.
Camera
s are no longer the primitive for rendering to a render target. They don't choose their own viewports, either. But, in return, they get full control over their own rendering flow and intermediate textures.
A few new primitives:
Compositor
: manages layout and ordering for its child View
s, and is where a RenderTarget
is actually selected.
View
: a child of a Compositor
, which it requests a viewport from. Currently this is stupid simple, and keeps the same Camera-SubView
API we have now. Each one has the responsibility of rendering to its assigned viewport once per frame, but the exact method is for each view to decide. Views
can be cameras, UI canvases, or anything that needs a render graph and writes to a final output texture.
Some pros:
RenderTarget
lets us respond to most changes in realtime, and to simplify camera_system
CompositedViews
relationshipViewTarget
(now ViewTextures
)ViewTarget
implies a properly setup compositor and render target, all of its methods can be infallible! (physical_target_size
, logical_viewport_rect
, etc). Contrast this with the same methods on Camera
, which all return Option<...>
currently.Camera
should make it a lot nicer to abstract into its own crate:bevy_ui
can depend on bevy_render
rather than bevy_camera
Some cons:
The compositor gives each view a TextureView
and Viewport
for the view target. We can just read back from this at the start of the graph!
Each camera gets to decide on its own how to write to the view target, but we can provide a "standard" node to blit/copy from the main intermediate texture to the ViewTarget
.
Camera
be?If we remove all the bits of camera that actually drive rendering, what's the use of keeping Camera
around?
Well, it's still useful for all the stuff that needs the physical metaphor of a camera! Projection transforms, the concept of space (#[require(Transform)]
), and all the machinery for visibility checking, etc. We can also get more opinionated about the "standard" feel of what a camera does in the future, since it wouldn't be as fundamental to rendering. Like, I'm imagining once we have render-graphs-as-schedules, I want to provide some common system sets so third party crates can integrate with more than the built-in render graph.
I still want to keep Camera
around and have it stay important, but I think the more we want to clean the renderer up and solve bugs with compositing, its scope needs to be smaller than it is now.
There's a lot of more esoteric ways to composite rendering effects, for example with world-space render targets (think portals or mirrors), stencil-based compositing, or interleaving 2d and 3d elements in a transform hierarchy. While I think the model proposed here is a dramatic improvement over what we have currently, we should consider the direction we want to take moving forward.
Texture copies can be expensive, so we want to minimize them where possible. It should always be possible to eliminate them entirely in constrained circumstances (no per-camera post-processing, all cameras have same settings). Currently, the way we do this is through sharing camera results through ordering and intermediate textures, which is incredibly bug-prone, and I don't believe this is the way we should move forward (at least in the general case)
Bevy Window
s are already entities, so I don't think it's that much of a stretch to imagine modeling all render targets as entities, and using 1:1 relationships to prevent conflicts. We could also merge RenderTarget
/NormalizedRenderTarget
once we have Construct
Picking is pretty crucial to get right, but I don't have any experience with it really. If there's a part of this that might compromise bevy_picking
or could be made to better work with it, please let me know :)