specular_tint/
specular_tint.rs1use std::f32::consts::PI;
4
5use bevy::{color::palettes::css::WHITE, core_pipeline::Skybox, prelude::*, render::view::Hdr};
6
7const ROTATION_SPEED: f32 = 0.005;
9const HUE_SHIFT_SPEED: f32 = 0.2;
11
12static SWITCH_TO_MAP_HELP_TEXT: &str = "Press Space to switch to a specular map";
13static SWITCH_TO_SOLID_TINT_HELP_TEXT: &str = "Press Space to switch to a solid specular tint";
14
15#[derive(Resource, Default)]
17struct AppStatus {
18 tint_type: TintType,
20 hue: f32,
22}
23
24#[derive(Resource)]
26struct AppAssets {
27 noise_texture: Handle<Image>,
29}
30
31impl FromWorld for AppAssets {
32 fn from_world(world: &mut World) -> Self {
33 let asset_server = world.resource::<AssetServer>();
34 Self {
35 noise_texture: asset_server.load("textures/AlphaNoise.png"),
36 }
37 }
38}
39
40#[derive(Clone, Copy, PartialEq, Default)]
42enum TintType {
43 #[default]
45 Solid,
46 Map,
48}
49
50fn main() {
52 App::new()
53 .add_plugins(DefaultPlugins.set(WindowPlugin {
54 primary_window: Some(Window {
55 title: "Bevy Specular Tint Example".into(),
56 ..default()
57 }),
58 ..default()
59 }))
60 .init_resource::<AppAssets>()
61 .init_resource::<AppStatus>()
62 .insert_resource(AmbientLight {
63 color: Color::BLACK,
64 brightness: 0.0,
65 ..default()
66 })
67 .add_systems(Startup, setup)
68 .add_systems(Update, rotate_camera)
69 .add_systems(Update, (toggle_specular_map, update_text).chain())
70 .add_systems(Update, shift_hue.after(toggle_specular_map))
71 .run();
72}
73
74fn setup(
76 mut commands: Commands,
77 asset_server: Res<AssetServer>,
78 app_status: Res<AppStatus>,
79 mut meshes: ResMut<Assets<Mesh>>,
80 mut standard_materials: ResMut<Assets<StandardMaterial>>,
81) {
82 commands.spawn((
84 Transform::from_xyz(-2.0, 0.0, 3.5).looking_at(Vec3::ZERO, Vec3::Y),
85 Hdr,
86 Camera3d::default(),
87 Skybox {
88 image: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
89 brightness: 3000.0,
90 ..default()
91 },
92 EnvironmentMapLight {
93 diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
94 specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
95 intensity: 25000.0,
98 ..default()
99 },
100 ));
101
102 commands.spawn((
104 Transform::from_rotation(Quat::from_rotation_x(PI * 0.5)),
105 Mesh3d(meshes.add(Sphere::default().mesh().uv(32, 18))),
106 MeshMaterial3d(standard_materials.add(StandardMaterial {
107 base_color: Color::BLACK,
110 reflectance: 1.0,
111 specular_tint: Color::hsva(app_status.hue, 1.0, 1.0, 1.0),
112 metallic: 0.0,
117 perceptual_roughness: 0.0,
118 ..default()
119 })),
120 ));
121
122 commands.spawn((
124 Node {
125 position_type: PositionType::Absolute,
126 bottom: px(12),
127 left: px(12),
128 ..default()
129 },
130 app_status.create_text(),
131 ));
132}
133
134fn rotate_camera(mut cameras: Query<&mut Transform, With<Camera3d>>) {
136 for mut camera_transform in cameras.iter_mut() {
137 camera_transform.translation =
138 Quat::from_rotation_y(ROTATION_SPEED) * camera_transform.translation;
139 camera_transform.look_at(Vec3::ZERO, Vec3::Y);
140 }
141}
142
143fn shift_hue(
145 mut app_status: ResMut<AppStatus>,
146 objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
147 mut standard_materials: ResMut<Assets<StandardMaterial>>,
148) {
149 if app_status.tint_type != TintType::Solid {
150 return;
151 }
152
153 app_status.hue += HUE_SHIFT_SPEED;
154
155 for material_handle in objects_with_materials.iter() {
156 let Some(material) = standard_materials.get_mut(material_handle) else {
157 continue;
158 };
159 material.specular_tint = Color::hsva(app_status.hue, 1.0, 1.0, 1.0);
160 }
161}
162
163impl AppStatus {
164 fn create_text(&self) -> Text {
166 let tint_map_help_text = match self.tint_type {
167 TintType::Solid => SWITCH_TO_MAP_HELP_TEXT,
168 TintType::Map => SWITCH_TO_SOLID_TINT_HELP_TEXT,
169 };
170
171 Text::new(tint_map_help_text)
172 }
173}
174
175fn toggle_specular_map(
178 keyboard: Res<ButtonInput<KeyCode>>,
179 mut app_status: ResMut<AppStatus>,
180 app_assets: Res<AppAssets>,
181 objects_with_materials: Query<&MeshMaterial3d<StandardMaterial>>,
182 mut standard_materials: ResMut<Assets<StandardMaterial>>,
183) {
184 if !keyboard.just_pressed(KeyCode::Space) {
185 return;
186 }
187
188 app_status.tint_type = match app_status.tint_type {
190 TintType::Solid => TintType::Map,
191 TintType::Map => TintType::Solid,
192 };
193
194 for material_handle in objects_with_materials.iter() {
195 let Some(material) = standard_materials.get_mut(material_handle) else {
196 continue;
197 };
198
199 match app_status.tint_type {
201 TintType::Solid => {
202 material.reflectance = 1.0;
203 material.specular_tint_texture = None;
204 }
205 TintType::Map => {
206 material.reflectance = 2.0;
209 material.specular_tint = WHITE.into();
212 material.specular_tint_texture = Some(app_assets.noise_texture.clone());
213 }
214 };
215 }
216}
217
218fn update_text(mut text_query: Query<&mut Text>, app_status: Res<AppStatus>) {
221 for mut text in text_query.iter_mut() {
222 *text = app_status.create_text();
223 }
224}