Skip to main content

compressed_image_saver/
compressed_image_saver.rs

1//! Demonstrates compressing textures and generating mipmaps using `CompressedImageSaver`.
2
3use bevy::{
4    camera::Hdr,
5    light::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
6    mesh::SphereKind,
7    prelude::*,
8};
9use std::{
10    fs,
11    io::{self, Read},
12    path::Path,
13};
14
15fn main() {
16    download_assets().expect("failed to download GroundSand005 textures");
17
18    App::new()
19        .add_plugins(DefaultPlugins.set(AssetPlugin {
20            mode: AssetMode::Processed,
21            ..default()
22        }))
23        .insert_resource(DirectionalLightShadowMap { size: 4096 })
24        .add_systems(Startup, spawn_scene)
25        .add_systems(Update, rotate)
26        .run();
27}
28
29fn spawn_scene(
30    asset_server: Res<AssetServer>,
31    mut meshes: ResMut<Assets<Mesh>>,
32    mut commands: Commands,
33) {
34    // See .meta files at https://github.com/bevyengine/bevy_asset_files/tree/main/GroundSand005
35    let orm = asset_server.load("textures/GroundSand005/GroundSand005_ORM_2K.png");
36    let sphere_material = StandardMaterial {
37        base_color_texture: Some(
38            asset_server.load("textures/GroundSand005/GroundSand005_COL_2K.jpg"),
39        ),
40        perceptual_roughness: 1.0,
41        metallic_roughness_texture: Some(orm.clone()),
42        normal_map_texture: Some(
43            asset_server.load("textures/GroundSand005/GroundSand005_NRM_2K.jpg"),
44        ),
45        occlusion_texture: Some(orm),
46        parallax_mapping_method: ParallaxMappingMethod::Relief { max_steps: 4 },
47        depth_map: Some(asset_server.load("textures/GroundSand005/GroundSand005_DISP_2K.jpg")),
48        ..Default::default()
49    };
50
51    let diffuse_env_map = asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2");
52    let specular_env_map = asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2");
53
54    let floor_mesh = meshes.add(Circle::new(4.0).mesh().resolution(128).build());
55
56    let sphere_mesh = meshes.add(
57        Sphere::new(1.0)
58            .mesh()
59            .kind(SphereKind::Ico { subdivisions: 50 })
60            .build()
61            .with_generated_tangents()
62            .unwrap(),
63    );
64
65    commands.spawn_scene_list(bsn_list! {
66        Mesh3d(floor_mesh)
67        MeshMaterial3d::<StandardMaterial>(asset_value(Color::WHITE))
68        Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
69        --
70        Mesh3d(sphere_mesh)
71        MeshMaterial3d::<StandardMaterial>(asset_value(sphere_material))
72        Transform::from_xyz(0.0, 1.0, 0.0)
73        Rotating
74        --
75        DirectionalLight {
76            illuminance: 7300.0,
77            shadow_maps_enabled: true,
78        }
79        Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y)
80        CascadeShadowConfigBuilder {
81            num_cascades: 1,
82            maximum_distance: 20.0,
83            ..default()
84        }.build()
85        --
86        Camera3d
87        Hdr
88        Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y)
89        EnvironmentMapLight {
90            diffuse_map: diffuse_env_map,
91            specular_map: specular_env_map,
92            intensity: 1200.0,
93        }
94    });
95}
96
97#[derive(Component, Default, Clone)]
98struct Rotating;
99
100fn rotate(mut query: Query<&mut Transform, With<Rotating>>, time: Res<Time>) {
101    for mut transform in &mut query {
102        transform.rotate_y(time.delta_secs() * 0.5);
103    }
104}
105
106// HACK: this function isn't relevant to asset processing in general.
107// We download the source textures at runtime rather than checking them
108// into the repo because they're large files we don't want to store in
109// version control. Asset processing needs to iterate through the contents of
110// an asset source to discover files to process, and the web asset source
111// doesn't support iterating through its contents, so we can't simply point
112// the processor at a remote URL and have to fetch the files to a local
113// directory first.
114fn download_assets() -> io::Result<()> {
115    let asset_files = &[
116        "GroundSand005_COL_2K.jpg",
117        "GroundSand005_COL_2K.jpg.meta",
118        "GroundSand005_NRM_2K.jpg",
119        "GroundSand005_NRM_2K.jpg.meta",
120        "GroundSand005_DISP_2K.jpg",
121        "GroundSand005_DISP_2K.jpg.meta",
122        "GroundSand005_ORM_2K.png",
123        "GroundSand005_ORM_2K.png.meta",
124    ];
125
126    let dir = Path::new("assets/textures/GroundSand005");
127    fs::create_dir_all(dir)?;
128
129    for file in asset_files {
130        let dest = dir.join(file);
131        if dest.exists() {
132            continue;
133        }
134
135        let url =
136            format!("https://github.com/bevyengine/bevy_asset_files/raw/main/GroundSand005/{file}");
137        info!("Downloading {url}");
138
139        let mut response = ureq::get(&url)
140            .call()
141            .map_err(|e| io::Error::other(e.to_string()))?;
142        let mut bytes = Vec::new();
143        response.body_mut().as_reader().read_to_end(&mut bytes)?;
144        fs::write(&dest, &bytes)?;
145    }
146
147    Ok(())
148}