Skip to main content

render_to_texture/
render_to_texture.rs

1//! Shows how to render to a texture. Useful for mirrors, UI, or exporting images.
2
3use std::f32::consts::PI;
4
5use bevy::camera::RenderTarget;
6use bevy::{camera::visibility::RenderLayers, prelude::*, render::render_resource::TextureFormat};
7
8fn main() {
9    App::new()
10        .add_plugins(DefaultPlugins)
11        .add_systems(Startup, setup)
12        .add_systems(Update, (cube_rotator_system, rotator_system))
13        .run();
14}
15
16// Marks the first pass cube (rendered to a texture.)
17#[derive(Component)]
18struct FirstPassCube;
19
20// Marks the main pass cube, to which the texture is applied.
21#[derive(Component)]
22struct MainPassCube;
23
24fn setup(
25    mut commands: Commands,
26    mut meshes: ResMut<Assets<Mesh>>,
27    mut materials: ResMut<Assets<StandardMaterial>>,
28    mut images: ResMut<Assets<Image>>,
29) {
30    // This is the texture that will be rendered to.
31    let image = Image::new_target_texture(
32        512,
33        512,
34        TextureFormat::Rgba8Unorm,
35        Some(TextureFormat::Rgba8UnormSrgb),
36    );
37
38    let image_handle = images.add(image);
39
40    let cube_handle = meshes.add(Cuboid::new(4.0, 4.0, 4.0));
41    let cube_material_handle = materials.add(StandardMaterial {
42        base_color: Color::srgb(0.8, 0.7, 0.6),
43        reflectance: 0.02,
44        unlit: false,
45        ..default()
46    });
47
48    // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube.
49    let first_pass_layer = RenderLayers::layer(1);
50
51    // The cube that will be rendered to the texture.
52    commands.spawn((
53        Mesh3d(cube_handle),
54        MeshMaterial3d(cube_material_handle),
55        Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)),
56        FirstPassCube,
57        first_pass_layer.clone(),
58    ));
59
60    // Light
61    // NOTE: we add the light to both layers so it affects both the rendered-to-texture cube, and the cube on which we display the texture
62    // Setting the layer to RenderLayers::layer(0) would cause the main view to be lit, but the rendered-to-texture cube to be unlit.
63    // Setting the layer to RenderLayers::layer(1) would cause the rendered-to-texture cube to be lit, but the main view to be unlit.
64    commands.spawn((
65        PointLight::default(),
66        Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
67        RenderLayers::layer(0).with(1),
68    ));
69
70    commands.spawn((
71        Camera3d::default(),
72        Camera {
73            // render before the "main pass" camera
74            order: -1,
75            clear_color: Color::WHITE.into(),
76            ..default()
77        },
78        RenderTarget::Image(image_handle.clone().into()),
79        Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)).looking_at(Vec3::ZERO, Vec3::Y),
80        first_pass_layer,
81    ));
82
83    let cube_size = 4.0;
84    let cube_handle = meshes.add(Cuboid::new(cube_size, cube_size, cube_size));
85
86    // This material has the texture that has been rendered.
87    let material_handle = materials.add(StandardMaterial {
88        base_color_texture: Some(image_handle),
89        reflectance: 0.02,
90        unlit: false,
91        ..default()
92    });
93
94    // Main pass cube, with material containing the rendered first pass texture.
95    commands.spawn((
96        Mesh3d(cube_handle),
97        MeshMaterial3d(material_handle),
98        Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(-PI / 5.0)),
99        MainPassCube,
100    ));
101
102    // The main pass camera.
103    commands.spawn((
104        Camera3d::default(),
105        Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),
106    ));
107}
108
109/// Rotates the inner cube (first pass)
110fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
111    for mut transform in &mut query {
112        transform.rotate_x(1.5 * time.delta_secs());
113        transform.rotate_z(1.3 * time.delta_secs());
114    }
115}
116
117/// Rotates the outer cube (main pass)
118fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
119    for mut transform in &mut query {
120        transform.rotate_x(1.0 * time.delta_secs());
121        transform.rotate_y(0.7 * time.delta_secs());
122    }
123}