Camera
Displaying a camera’s output inside a UI node is done through a render target: you render a
scene into an image, then use that image as the texture of a UI node. Lunex helps with the sizing —
the UiEmbedding component makes the rendered texture resize with the node’s Dimension.
The pipeline looks like this:
- Create an empty render texture (the “canvas”).
- Spawn a scene camera that renders your scene into the canvas.
- Spawn a UI node that displays the canvas as a sprite.
The canvas
// Create the canvas texture
let canvas = images.add(Image::clear_render_texture());
Image::clear_render_texture() creates a transparent image with the correct texture usages for
rendering into it.
The scene camera
A regular Camera3d that renders into the canvas instead of the window. The order: -1 makes it
render before the UI composition camera, so the canvas is always up to date:
commands.spawn((
Camera3d::default(),
// Render into the canvas instead of the window
RenderTarget::Image(canvas.clone().into()),
Camera {
clear_color: ClearColorConfig::Custom(Color::srgba(0.0, 0.0, 0.0, 0.5)),
order: -1,
..Default::default()
},
Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
));
The UI node
Spawn the canvas as a node with a Sprite, marked with UiEmbedding so the texture resizes
with the node:
// Spawn the composition camera
commands.spawn((
Camera2d,
// Configure it as UI source
UiSourceCamera::<0>,
// Set the camera location to capture spawned sprites
Transform::from_translation(Vec3::Z * 1000.0),
// Set the render layers to only see the canvas
RenderLayers::from_layers(&[1]),
));
// Compose the secondary canvas camera infront of composition camera
commands.spawn((
UiLayoutRoot::new_2d(),
UiFetchFromCamera::<0>,
)).with_children(|ui| {
// Plane with 3D camera canvas, 16:9 aspect ratio
ui.spawn((
UiLayout::solid().size((16.0, 9.0)).scaling(Scaling::Fit).pack(),
Sprite::from_image(canvas.clone()),
UiEmbedding,
RenderLayers::from_layers(&[1]),
));
});
Note
Both the UI and its source camera live on render layer
1here — this keeps the composition camera from seeing anything else in your scene. TheSolidlayout withScaling::Fitkeeps the canvas at a fixed 16:9 aspect ratio no matter the window size.
Pixelated canvas
For a retro pixelated look, render the scene into a low-resolution canvas and upscale it with nearest-neighbor sampling:
// Use nearest-neighbor sampling so upscaled pixels stay sharp
app.add_plugins(DefaultPlugins.build().set(ImagePlugin::default_nearest()));
Then create the canvas with a fixed size instead of using Image::clear_render_texture():
fn virtual_texture(width: u32, height: u32) -> Image {
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages};
use bevy::asset::RenderAssetUsages;
let mut image = Image::new_fill(
Extent3d {
width,
height,
..Default::default()
},
TextureDimension::D2,
&[0, 0, 0, 0],
TextureFormat::Bgra8UnormSrgb,
RenderAssetUsages::default(),
);
image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
image
}
Tip
Disable anti-aliasing on the scene camera with
Msaa::Offfor that raw pixelated look. The full working example lives inexamples/pixelated_dualcamerain the repository.