1use std::f32::consts::{FRAC_PI_2, PI};
19
20use bevy::{
21 asset::RenderAssetUsages,
22 camera::RenderTarget,
23 color::palettes::css::LIME,
24 core_pipeline::{prepass::DepthPrepass, schedule::Core3d, Core3dSystems},
25 image::{ImageCompareFunction, ImageSampler, ImageSamplerDescriptor},
26 math::ops::{acos, atan2, sin_cos},
27 prelude::*,
28 render::{
29 camera::ExtractedCamera,
30 extract_resource::{ExtractResource, ExtractResourcePlugin},
31 render_asset::RenderAssets,
32 render_resource::{
33 AsBindGroup, Extent3d, Origin3d, TexelCopyTextureInfo, TextureAspect, TextureDimension,
34 TextureFormat,
35 },
36 renderer::{RenderContext, ViewQuery},
37 texture::GpuImage,
38 view::ViewDepthStencilTexture,
39 RenderApp,
40 },
41 shader::ShaderRef,
42};
43
44#[derive(Component)]
46struct RotatingCube;
47
48#[derive(Clone, Debug, Asset, TypePath, AsBindGroup)]
52struct ShowDepthTextureMaterial {
53 #[texture(0, sample_type = "depth")]
55 #[sampler(1, sampler_type = "comparison")]
56 depth_texture: Option<Handle<Image>>,
57}
58
59#[derive(Clone, Resource)]
70struct DemoDepthTexture(Handle<Image>);
71
72#[derive(Clone, Copy, Debug)]
82struct SphericalCoordinates {
83 radius: f32,
85 inclination: f32,
87 azimuth: f32,
89}
90
91static SHADER_ASSET_PATH: &str = "shaders/show_depth_texture_material.wesl";
93
94const DEPTH_TEXTURE_SIZE: u32 = 256;
96
97const CAMERA_MOVEMENT_SPEED: f32 = 2.0;
99
100fn main() {
102 let mut app = App::new();
103
104 app.add_plugins(DefaultPlugins)
105 .add_plugins(MaterialPlugin::<ShowDepthTextureMaterial>::default())
106 .add_plugins(ExtractResourcePlugin::<DemoDepthTexture>::default())
107 .init_resource::<DemoDepthTexture>()
108 .add_systems(Startup, setup)
109 .add_systems(Update, rotate_cube)
110 .add_systems(Update, draw_camera_gizmo)
111 .add_systems(Update, move_camera);
112
113 let render_app = app
114 .get_sub_app_mut(RenderApp)
115 .expect("Render app should be present");
116
117 render_app.add_systems(
118 Core3d,
119 copy_depth_texture_system
120 .after(Core3dSystems::Prepass)
121 .before(Core3dSystems::MainPass),
122 );
123
124 app.run();
125}
126
127fn copy_depth_texture_system(
128 view: ViewQuery<(&ExtractedCamera, &ViewDepthStencilTexture)>,
129 demo_depth_texture: Option<Res<DemoDepthTexture>>,
130 image_assets: Res<RenderAssets<GpuImage>>,
131 mut ctx: RenderContext,
132) {
133 let Some(demo_depth_texture) = demo_depth_texture else {
134 return;
135 };
136
137 let (camera, depth_texture) = view.into_inner();
138
139 if camera.order >= 0 {
144 return;
145 }
146
147 let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else {
148 return;
149 };
150
151 let command_encoder = ctx.command_encoder();
152 command_encoder.push_debug_group("copy depth to demo texture");
153 command_encoder.copy_texture_to_texture(
154 TexelCopyTextureInfo {
157 texture: depth_texture.texture(),
158 mip_level: 0,
159 origin: Origin3d::default(),
160 aspect: TextureAspect::All,
161 },
162 TexelCopyTextureInfo {
163 texture: &demo_depth_image.texture,
164 mip_level: 0,
165 origin: Origin3d::default(),
166 aspect: TextureAspect::All,
167 },
168 Extent3d {
169 width: DEPTH_TEXTURE_SIZE,
170 height: DEPTH_TEXTURE_SIZE,
171 depth_or_array_layers: 1,
172 },
173 );
174 command_encoder.pop_debug_group();
175}
176
177fn setup(
179 mut commands: Commands,
180 mut meshes: ResMut<Assets<Mesh>>,
181 mut standard_materials: ResMut<Assets<StandardMaterial>>,
182 mut show_depth_texture_materials: ResMut<Assets<ShowDepthTextureMaterial>>,
183 demo_depth_texture: Res<DemoDepthTexture>,
184) {
185 spawn_rotating_cube(&mut commands, &mut meshes, &mut standard_materials);
186 spawn_plane(
187 &mut commands,
188 &mut meshes,
189 &mut show_depth_texture_materials,
190 &demo_depth_texture,
191 );
192 spawn_light(&mut commands);
193 spawn_depth_only_camera(&mut commands);
194 spawn_main_camera(&mut commands);
195 spawn_instructions(&mut commands);
196}
197
198fn spawn_rotating_cube(
200 commands: &mut Commands,
201 meshes: &mut Assets<Mesh>,
202 standard_materials: &mut Assets<StandardMaterial>,
203) {
204 let cube_handle = meshes.add(Cuboid::new(3.0, 3.0, 3.0));
205 let rotating_cube_material_handle = standard_materials.add(StandardMaterial {
206 base_color: Color::WHITE,
207 unlit: false,
208 ..default()
209 });
210 commands.spawn((
211 Mesh3d(cube_handle.clone()),
212 MeshMaterial3d(rotating_cube_material_handle),
213 Transform::IDENTITY,
214 RotatingCube,
215 ));
216}
217
218fn spawn_plane(
220 commands: &mut Commands,
221 meshes: &mut Assets<Mesh>,
222 show_depth_texture_materials: &mut Assets<ShowDepthTextureMaterial>,
223 demo_depth_texture: &DemoDepthTexture,
224) {
225 let plane_handle = meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(2.0)));
226 let show_depth_texture_material = show_depth_texture_materials.add(ShowDepthTextureMaterial {
227 depth_texture: Some(demo_depth_texture.0.clone()),
228 });
229 commands.spawn((
230 Mesh3d(plane_handle),
231 MeshMaterial3d(show_depth_texture_material),
232 Transform::from_xyz(10.0, 4.0, 0.0).with_scale(Vec3::splat(2.5)),
233 ));
234}
235
236fn spawn_light(commands: &mut Commands) {
238 commands.spawn((PointLight::default(), Transform::from_xyz(5.0, 6.0, 7.0)));
239}
240
241fn spawn_depth_only_camera(commands: &mut Commands) {
243 commands.spawn((
244 Camera3d::default(),
245 Transform::from_xyz(-4.0, -5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
246 Camera {
247 order: -1,
250 ..Camera::default()
251 },
252 RenderTarget::None {
254 size: UVec2::splat(DEPTH_TEXTURE_SIZE),
258 },
259 Msaa::Off,
263 DepthPrepass,
267 ));
268}
269
270fn spawn_main_camera(commands: &mut Commands) {
272 commands.spawn((
273 Camera3d::default(),
274 Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y),
275 Msaa::Off,
277 ));
278}
279
280fn spawn_instructions(commands: &mut Commands) {
282 commands.spawn((
283 Text::new("Use WASD to move the secondary camera"),
284 Node {
285 position_type: PositionType::Absolute,
286 top: px(12.0),
287 left: px(12.0),
288 ..Node::default()
289 },
290 ));
291}
292
293fn rotate_cube(mut cubes: Query<&mut Transform, With<RotatingCube>>, time: Res<Time>) {
295 for mut transform in &mut cubes {
296 transform.rotate_x(1.5 * time.delta_secs());
297 transform.rotate_y(1.1 * time.delta_secs());
298 transform.rotate_z(-1.3 * time.delta_secs());
299 }
300}
301
302impl Material for ShowDepthTextureMaterial {
303 fn fragment_shader() -> ShaderRef {
304 SHADER_ASSET_PATH.into()
305 }
306}
307
308impl FromWorld for DemoDepthTexture {
309 fn from_world(world: &mut World) -> Self {
310 let mut images = world.resource_mut::<Assets<Image>>();
311
312 let mut depth_image = Image::new_uninit(
314 Extent3d {
315 width: DEPTH_TEXTURE_SIZE,
316 height: DEPTH_TEXTURE_SIZE,
317 depth_or_array_layers: 1,
318 },
319 TextureDimension::D2,
320 TextureFormat::Depth32Float,
321 RenderAssetUsages::default(),
322 );
323
324 depth_image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
327 label: Some("custom depth image sampler".to_owned()),
328 compare: Some(ImageCompareFunction::Always),
329 ..ImageSamplerDescriptor::default()
330 });
331
332 let depth_image_handle = images.add(depth_image);
333 DemoDepthTexture(depth_image_handle)
334 }
335}
336
337impl ExtractResource<RenderApp> for DemoDepthTexture {
338 type Source = Self;
339
340 fn extract_resource(source: &Self::Source) -> Self {
341 (*source).clone()
344 }
345}
346
347fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
349 for (camera, transform) in &cameras {
350 if camera.order >= 0 {
353 continue;
354 }
355
356 gizmos.primitive_3d(
358 &Cone {
359 radius: 1.0,
360 height: 3.0,
361 },
362 Isometry3d::new(
363 transform.translation(),
364 transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
367 ),
368 LIME,
369 );
370 }
371}
372
373fn move_camera(
375 mut cameras: Query<(&Camera, &mut Transform)>,
376 keyboard: Res<ButtonInput<KeyCode>>,
377 time: Res<Time>,
378) {
379 for (camera, mut transform) in &mut cameras {
380 if camera.order >= 0 {
382 continue;
383 }
384
385 let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
387
388 let mut changed = false;
390 if keyboard.pressed(KeyCode::KeyW) {
391 spherical_coords.inclination -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
392 changed = true;
393 }
394 if keyboard.pressed(KeyCode::KeyS) {
395 spherical_coords.inclination += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
396 changed = true;
397 }
398 if keyboard.pressed(KeyCode::KeyA) {
399 spherical_coords.azimuth += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
400 changed = true;
401 }
402 if keyboard.pressed(KeyCode::KeyD) {
403 spherical_coords.azimuth -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
404 changed = true;
405 }
406
407 if changed {
410 spherical_coords.inclination = spherical_coords.inclination.clamp(0.01, PI - 0.01);
411 transform.translation = spherical_coords.to_cartesian();
412 transform.look_at(Vec3::ZERO, Vec3::Y);
413 }
414 }
415}
416
417impl SphericalCoordinates {
418 fn from_cartesian(p: Vec3) -> SphericalCoordinates {
422 let radius = p.length();
423 SphericalCoordinates {
424 radius,
425 inclination: acos(p.y / radius),
426 azimuth: atan2(p.z, p.x),
427 }
428 }
429
430 fn to_cartesian(self) -> Vec3 {
434 let (sin_inclination, cos_inclination) = sin_cos(self.inclination);
435 let (sin_azimuth, cos_azimuth) = sin_cos(self.azimuth);
436 self.radius
437 * vec3(
438 sin_inclination * cos_azimuth,
439 cos_inclination,
440 sin_inclination * sin_azimuth,
441 )
442 }
443}