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