//! Demonstrates specular tints and maps. use std::{ f32::consts::PI, fmt::{self, Formatter}, }; use bevy::{ camera::Hdr, color::palettes::css::WHITE, feathers::{theme::UiTheme, FeathersPlugins}, light::Skybox, prelude::*, ui_widgets::{radio_self_update, ValueChange}, }; use radio::{feathers_option_buttons, main_ui_node_scene, RadioButtonOptionValue}; #[path = "../helpers/radio.rs"] mod radio; #[path = "../helpers/theme.rs"] mod theme; /// The camera rotation speed in radians per frame. const ROTATION_SPEED: f32 = 0.005; /// The rate at which the specular tint hue changes in degrees per frame. const HUE_SHIFT_SPEED: f32 = 0.2; /// The current settings the user has chosen. #[derive(Resource, Default)] struct AppStatus { /// The type of tint (solid or texture map). tint_type: TintType, /// The hue of the solid tint in radians. hue: f32, } /// Assets needed by the demo. #[derive(Resource)] struct AppAssets { /// A color tileable 3D noise texture. noise_texture: Handle, } impl FromWorld for AppAssets { fn from_world(world: &mut World) -> Self { let asset_server = world.resource::(); Self { noise_texture: asset_server.load("textures/AlphaNoise.png"), } } } /// The type of specular tint that the user has selected. #[derive(Clone, Copy, PartialEq, Default, Debug)] enum TintType { /// A solid color. #[default] Solid, /// A Perlin noise texture. Map, } impl fmt::Display for TintType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match *self { TintType::Map => f.write_str("MAP"), TintType::Solid => f.write_str("SOLID"), } } } /// The entry point. fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Bevy Specular Tint Example".into(), ..default() }), ..default() })) .add_plugins(FeathersPlugins) .insert_resource(UiTheme(theme::basic_example_theme(Color::WHITE))) .init_resource::() .init_resource::() .insert_resource(GlobalAmbientLight { color: Color::BLACK, brightness: 0.0, ..default() }) .add_systems(Startup, setup) .add_systems(Update, rotate_camera) .add_observer(toggle_specular_map) .add_observer(radio_self_update) .run(); } /// Creates the scene. fn setup( mut commands: Commands, asset_server: Res, app_status: Res, mut meshes: ResMut>, mut standard_materials: ResMut>, ) { // Spawns a camera. commands.spawn(( Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y), Hdr, Camera3d::default(), Skybox { image: Some(asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2")), brightness: 3000.0, ..default() }, EnvironmentMapLight { diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"), specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"), // We want relatively high intensity here in order for the specular // tint to show up well. intensity: 25000.0, ..default() }, )); // Spawn the sphere. commands.spawn(( Transform::from_rotation(Quat::from_rotation_x(PI * 0.5)), Mesh3d(meshes.add(Sphere::default().mesh().uv(32, 18))), MeshMaterial3d(standard_materials.add(StandardMaterial { // We want only reflected specular light here, so we set the base // color as black. base_color: Color::BLACK, reflectance: 1.0, specular_tint: Color::hsva(app_status.hue, 1.0, 1.0, 1.0), // The object must not be metallic, or else the reflectance is // ignored per the Filament spec: // // metallic: 0.0, perceptual_roughness: 0.0, ..default() })), )); spawn_buttons(&mut commands); } /// Rotates the camera a bit every frame. fn rotate_camera(mut cameras: Query<&mut Transform, With>) { for mut camera_transform in cameras.iter_mut() { camera_transform.translation = Quat::from_rotation_y(ROTATION_SPEED) * camera_transform.translation; camera_transform.look_at(Vec3::ZERO, Vec3::Y); } } /// Alters the hue of the solid color a bit every frame. fn shift_hue( mut app_status: ResMut, objects_with_materials: Query<&MeshMaterial3d>, mut standard_materials: ResMut>, ) { if app_status.tint_type != TintType::Solid { return; } app_status.hue += HUE_SHIFT_SPEED; for material_handle in objects_with_materials.iter() { let Some(mut material) = standard_materials.get_mut(material_handle) else { continue; }; material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0); } } /// Spawns the radio buttons in the bottom left corner of the screen. fn spawn_buttons(commands: &mut Commands) { commands.spawn_scene(bsn! { @main_ui_node_scene() Children [ @feathers_option_buttons( "Toggle specular tint", &[ (TintType::Solid, "SOLID"), (TintType::Map, "MAP"), ], 0, ) ] }); } /// Changes the specular tint to a solid color or map when the user checks /// a radio button. fn toggle_specular_map( event: On>, new_value_query: Query<&RadioButtonOptionValue>, mut app_status: ResMut, app_assets: Res, objects_with_materials: Query<&MeshMaterial3d>, mut standard_materials: ResMut>, ) { let Ok(RadioButtonOptionValue(selection)) = new_value_query.get(event.value) else { return; }; // Set the specular map value in `AppStatus`. app_status.tint_type = *selection; for material_handle in objects_with_materials.iter() { let Some(mut material) = standard_materials.get_mut(material_handle) else { continue; }; // Adjust the tint type. match app_status.tint_type { TintType::Solid => { material.reflectance = 1.0; material.specular_tint_texture = None; } TintType::Map => { // Set reflectance to 2.0 to spread out the map's reflectance // range from the default [0.0, 0.5] to [0.0, 1.0]. material.reflectance = 2.0; // As the tint map is multiplied by the tint color, we set the // latter to white so that only the map has an effect. material.specular_tint = WHITE.into(); material.specular_tint_texture = Some(app_assets.noise_texture.clone()); } }; } shift_hue(app_status, objects_with_materials, standard_materials); }