1use bevy::{
15 curve::cubic_splines::LinearSpline,
16 light::Skybox,
17 math::vec2,
18 post_process::auto_exposure::{
19 AutoExposure, AutoExposureCompensationCurve, AutoExposurePlugin,
20 },
21 prelude::*,
22 shape::Plane3d,
23};
24
25fn main() {
26 App::new()
27 .add_plugins(DefaultPlugins)
28 .add_plugins(AutoExposurePlugin)
29 .add_systems(Startup, setup)
30 .add_systems(Update, example_control_system)
31 .run();
32}
33
34fn setup(
35 mut commands: Commands,
36 mut meshes: ResMut<Assets<Mesh>>,
37 mut materials: ResMut<Assets<StandardMaterial>>,
38 mut compensation_curves: ResMut<Assets<AutoExposureCompensationCurve>>,
39 asset_server: Res<AssetServer>,
40) {
41 let metering_mask = asset_server.load("textures/basic_metering_mask.png");
42
43 commands.spawn((
44 Camera3d::default(),
45 Transform::from_xyz(1.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
46 AutoExposure {
47 metering_mask: metering_mask.clone(),
48 ..default()
49 },
50 Skybox {
51 image: Some(asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2")),
52 brightness: light_consts::lux::DIRECT_SUNLIGHT,
53 ..default()
54 },
55 ));
56
57 commands.insert_resource(ExampleResources {
58 basic_compensation_curve: compensation_curves.add(
59 AutoExposureCompensationCurve::from_curve(LinearSpline::new([
60 vec2(-4.0, -2.0),
61 vec2(0.0, 0.0),
62 vec2(2.0, 0.0),
63 vec2(4.0, 2.0),
64 ]))
65 .unwrap(),
66 ),
67 basic_metering_mask: metering_mask.clone(),
68 });
69
70 let plane = meshes.add(Mesh::from(
71 Plane3d {
72 normal: -Dir3::Z,
73 half_size: Vec2::new(2.0, 0.5),
74 }
75 .mesh(),
76 ));
77
78 for level in -1..=1 {
80 for side in [-Vec3::X, Vec3::X, -Vec3::Z, Vec3::Z] {
81 if level == 0 && Vec3::Z == side {
82 continue;
83 }
84
85 let height = Vec3::Y * level as f32;
86
87 commands.spawn((
88 Mesh3d(plane.clone()),
89 MeshMaterial3d(materials.add(StandardMaterial {
90 base_color: Color::srgb(
91 0.5 + side.x * 0.5,
92 0.75 - level as f32 * 0.25,
93 0.5 + side.z * 0.5,
94 ),
95 ..default()
96 })),
97 Transform::from_translation(side * 2.0 + height).looking_at(height, Vec3::Y),
98 ));
99 }
100 }
101
102 commands.insert_resource(GlobalAmbientLight {
103 color: Color::WHITE,
104 brightness: 0.0,
105 ..default()
106 });
107
108 commands.spawn((
109 PointLight {
110 intensity: 2000.0,
111 ..default()
112 },
113 Transform::from_xyz(0.0, 0.0, 0.0),
114 ));
115
116 commands.spawn((
117 ImageNode {
118 image: metering_mask,
119 ..default()
120 },
121 Node {
122 width: percent(100),
123 height: percent(100),
124 ..default()
125 },
126 ));
127
128 let text_font = TextFont::default();
129
130 commands.spawn((Text::new("Left / Right - Rotate Camera\nC - Toggle Compensation Curve\nM - Toggle Metering Mask\nV - Visualize Metering Mask"),
131 text_font.clone(), Node {
132 position_type: PositionType::Absolute,
133 top: px(12),
134 left: px(12),
135 ..default()
136 })
137 );
138
139 commands.spawn((
140 Text::default(),
141 text_font,
142 Node {
143 position_type: PositionType::Absolute,
144 top: px(12),
145 right: px(12),
146 ..default()
147 },
148 ExampleDisplay,
149 ));
150}
151
152#[derive(Component)]
153struct ExampleDisplay;
154
155#[derive(Resource)]
156struct ExampleResources {
157 basic_compensation_curve: Handle<AutoExposureCompensationCurve>,
158 basic_metering_mask: Handle<Image>,
159}
160
161fn example_control_system(
162 camera: Single<(&mut Transform, &mut AutoExposure), With<Camera3d>>,
163 mut display: Single<&mut Text, With<ExampleDisplay>>,
164 mut mask_image: Single<&mut Node, With<ImageNode>>,
165 time: Res<Time>,
166 input: Res<ButtonInput<KeyCode>>,
167 resources: Res<ExampleResources>,
168) {
169 let (mut camera_transform, mut auto_exposure) = camera.into_inner();
170
171 let rotation = if input.pressed(KeyCode::ArrowLeft) {
172 time.delta_secs()
173 } else if input.pressed(KeyCode::ArrowRight) {
174 -time.delta_secs()
175 } else {
176 0.0
177 };
178
179 camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(rotation));
180
181 if input.just_pressed(KeyCode::KeyC) {
182 auto_exposure.compensation_curve =
183 if auto_exposure.compensation_curve == resources.basic_compensation_curve {
184 Handle::default()
185 } else {
186 resources.basic_compensation_curve.clone()
187 };
188 }
189
190 if input.just_pressed(KeyCode::KeyM) {
191 auto_exposure.metering_mask =
192 if auto_exposure.metering_mask == resources.basic_metering_mask {
193 Handle::default()
194 } else {
195 resources.basic_metering_mask.clone()
196 };
197 }
198
199 mask_image.display = if input.pressed(KeyCode::KeyV) {
200 Display::Flex
201 } else {
202 Display::None
203 };
204
205 display.0 = format!(
206 "Compensation Curve: {}\nMetering Mask: {}",
207 if auto_exposure.compensation_curve == resources.basic_compensation_curve {
208 "Enabled"
209 } else {
210 "Disabled"
211 },
212 if auto_exposure.metering_mask == resources.basic_metering_mask {
213 "Enabled"
214 } else {
215 "Disabled"
216 },
217 );
218}