bevy_image_export 0.15.1

Bevy plugin for rendering image sequences
Documentation
mod common;

use crate::common::graceperiod::{GracePeriodPlugin, GracefulFrameCount};
use bevy::{
    camera::RenderTarget,
    core_pipeline::tonemapping::Tonemapping,
    prelude::*,
    render::{
        render_resource::{
            Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
        },
        view::Hdr,
        RenderPlugin,
    },
};
use bevy_image_export::{ImageExport, ImageExportPlugin, ImageExportSettings, ImageExportSource};
use std::f32::consts::PI;

const WIDTH: u32 = 768;
const HEIGHT: u32 = 768;

fn main() {
    let export_plugin = ImageExportPlugin::default();
    let export_threads = export_plugin.threads.clone();

    App::new()
        .add_plugins((
            DefaultPlugins
                .set(WindowPlugin {
                    primary_window: Some(Window {
                        resolution: (WIDTH, HEIGHT).into(),
                        ..default()
                    }),
                    ..default()
                })
                .set(RenderPlugin {
                    synchronous_pipeline_compilation: true,
                    ..default()
                }),
            GracePeriodPlugin::default(),
            export_plugin,
        ))
        .add_systems(Startup, setup_scene)
        .add_systems(Update, (setup_camera, update).chain())
        .run();

    export_threads.finish();
}

fn setup_scene(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.insert_resource(AmbientLight {
        brightness: 0.0,
        ..default()
    });

    commands.spawn((
        PointLight {
            intensity: 100_000_000.0,
            ..default()
        },
        Moving,
    ));

    commands.spawn((
        Mesh3d(meshes.add(Mesh::from(Sphere { radius: 1.0 }))),
        MeshMaterial3d(materials.add(Color::srgb(1.0, 0.75, 0.5))),
    ));
}

fn setup_camera(
    mut commands: Commands,
    mut images: ResMut<Assets<Image>>,
    mut exporter_sources: ResMut<Assets<ImageExportSource>>,
    frame_count: Res<GracefulFrameCount>,
) {
    if frame_count.frame() != 1 {
        return;
    }

    let output_texture_handle = {
        let size = Extent3d {
            width: WIDTH,
            height: HEIGHT,
            ..default()
        };
        let mut export_texture = Image {
            texture_descriptor: TextureDescriptor {
                label: None,
                size,
                dimension: TextureDimension::D2,
                format: TextureFormat::Rgba32Float,
                mip_level_count: 1,
                sample_count: 1,
                usage: TextureUsages::COPY_DST
                    | TextureUsages::COPY_SRC
                    | TextureUsages::RENDER_ATTACHMENT,
                view_formats: &[],
            },
            ..default()
        };
        export_texture.resize(size);

        images.add(export_texture)
    };

    let tonemapping = Tonemapping::None;
    commands.spawn((
        Camera3d::default(),
        Hdr,
        Camera {
            clear_color: ClearColorConfig::Custom(Color::BLACK),
            ..default()
        },
        Transform::from_translation(4.2 * Vec3::Z),
        tonemapping,
        children![(
            Camera3d::default(),
            Hdr,
            Camera {
                target: RenderTarget::Image(output_texture_handle.clone().into()),
                ..default()
            },
            tonemapping,
        )],
    ));

    commands.spawn((
        ImageExport(exporter_sources.add(output_texture_handle)),
        ImageExportSettings {
            extension: "exr".into(),
            ..default()
        },
    ));
}

#[derive(Component)]
struct Moving;
fn update(
    mut transforms: Query<&mut Transform, With<Moving>>,
    frame_count: Res<GracefulFrameCount>,
) {
    let frame = frame_count.frame().wrapping_sub(1);
    let theta = frame as f32 * 0.25 * PI;
    for mut transform in &mut transforms {
        transform.translation = 10.0 * Vec3::new(theta.sin(), theta.cos(), 0.5);
    }
}