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::ViewDepthTexture,
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.wgsl";
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, &ViewDepthTexture)>,
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 {
155 texture: &depth_texture.texture,
156 mip_level: 0,
157 origin: Origin3d::default(),
158 aspect: TextureAspect::DepthOnly,
159 },
160 TexelCopyTextureInfo {
161 texture: &demo_depth_image.texture,
162 mip_level: 0,
163 origin: Origin3d::default(),
164 aspect: TextureAspect::DepthOnly,
165 },
166 Extent3d {
167 width: DEPTH_TEXTURE_SIZE,
168 height: DEPTH_TEXTURE_SIZE,
169 depth_or_array_layers: 1,
170 },
171 );
172 command_encoder.pop_debug_group();
173}
174
175fn setup(
177 mut commands: Commands,
178 mut meshes: ResMut<Assets<Mesh>>,
179 mut standard_materials: ResMut<Assets<StandardMaterial>>,
180 mut show_depth_texture_materials: ResMut<Assets<ShowDepthTextureMaterial>>,
181 demo_depth_texture: Res<DemoDepthTexture>,
182) {
183 spawn_rotating_cube(&mut commands, &mut meshes, &mut standard_materials);
184 spawn_plane(
185 &mut commands,
186 &mut meshes,
187 &mut show_depth_texture_materials,
188 &demo_depth_texture,
189 );
190 spawn_light(&mut commands);
191 spawn_depth_only_camera(&mut commands);
192 spawn_main_camera(&mut commands);
193 spawn_instructions(&mut commands);
194}
195
196fn spawn_rotating_cube(
198 commands: &mut Commands,
199 meshes: &mut Assets<Mesh>,
200 standard_materials: &mut Assets<StandardMaterial>,
201) {
202 let cube_handle = meshes.add(Cuboid::new(3.0, 3.0, 3.0));
203 let rotating_cube_material_handle = standard_materials.add(StandardMaterial {
204 base_color: Color::WHITE,
205 unlit: false,
206 ..default()
207 });
208 commands.spawn((
209 Mesh3d(cube_handle.clone()),
210 MeshMaterial3d(rotating_cube_material_handle),
211 Transform::IDENTITY,
212 RotatingCube,
213 ));
214}
215
216fn spawn_plane(
218 commands: &mut Commands,
219 meshes: &mut Assets<Mesh>,
220 show_depth_texture_materials: &mut Assets<ShowDepthTextureMaterial>,
221 demo_depth_texture: &DemoDepthTexture,
222) {
223 let plane_handle = meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(2.0)));
224 let show_depth_texture_material = show_depth_texture_materials.add(ShowDepthTextureMaterial {
225 depth_texture: Some(demo_depth_texture.0.clone()),
226 });
227 commands.spawn((
228 Mesh3d(plane_handle),
229 MeshMaterial3d(show_depth_texture_material),
230 Transform::from_xyz(10.0, 4.0, 0.0).with_scale(Vec3::splat(2.5)),
231 ));
232}
233
234fn spawn_light(commands: &mut Commands) {
236 commands.spawn((PointLight::default(), Transform::from_xyz(5.0, 6.0, 7.0)));
237}
238
239fn spawn_depth_only_camera(commands: &mut Commands) {
241 commands.spawn((
242 Camera3d::default(),
243 Transform::from_xyz(-4.0, -5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
244 Camera {
245 order: -1,
248 ..Camera::default()
249 },
250 RenderTarget::None {
252 size: UVec2::splat(DEPTH_TEXTURE_SIZE),
256 },
257 Msaa::Off,
261 DepthPrepass,
265 ));
266}
267
268fn spawn_main_camera(commands: &mut Commands) {
270 commands.spawn((
271 Camera3d::default(),
272 Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y),
273 Msaa::Off,
275 ));
276}
277
278fn spawn_instructions(commands: &mut Commands) {
280 commands.spawn((
281 Text::new("Use WASD to move the secondary camera"),
282 Node {
283 position_type: PositionType::Absolute,
284 top: px(12.0),
285 left: px(12.0),
286 ..Node::default()
287 },
288 ));
289}
290
291fn rotate_cube(mut cubes: Query<&mut Transform, With<RotatingCube>>, time: Res<Time>) {
293 for mut transform in &mut cubes {
294 transform.rotate_x(1.5 * time.delta_secs());
295 transform.rotate_y(1.1 * time.delta_secs());
296 transform.rotate_z(-1.3 * time.delta_secs());
297 }
298}
299
300impl Material for ShowDepthTextureMaterial {
301 fn fragment_shader() -> ShaderRef {
302 SHADER_ASSET_PATH.into()
303 }
304}
305
306impl FromWorld for DemoDepthTexture {
307 fn from_world(world: &mut World) -> Self {
308 let mut images = world.resource_mut::<Assets<Image>>();
309
310 let mut depth_image = Image::new_uninit(
312 Extent3d {
313 width: DEPTH_TEXTURE_SIZE,
314 height: DEPTH_TEXTURE_SIZE,
315 depth_or_array_layers: 1,
316 },
317 TextureDimension::D2,
318 TextureFormat::Depth32Float,
319 RenderAssetUsages::default(),
320 );
321
322 depth_image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
325 label: Some("custom depth image sampler".to_owned()),
326 compare: Some(ImageCompareFunction::Always),
327 ..ImageSamplerDescriptor::default()
328 });
329
330 let depth_image_handle = images.add(depth_image);
331 DemoDepthTexture(depth_image_handle)
332 }
333}
334
335impl ExtractResource for DemoDepthTexture {
336 type Source = Self;
337
338 fn extract_resource(source: &Self::Source) -> Self {
339 (*source).clone()
342 }
343}
344
345fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
347 for (camera, transform) in &cameras {
348 if camera.order >= 0 {
351 continue;
352 }
353
354 gizmos.primitive_3d(
356 &Cone {
357 radius: 1.0,
358 height: 3.0,
359 },
360 Isometry3d::new(
361 transform.translation(),
362 transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
365 ),
366 LIME,
367 );
368 }
369}
370
371fn move_camera(
373 mut cameras: Query<(&Camera, &mut Transform)>,
374 keyboard: Res<ButtonInput<KeyCode>>,
375 time: Res<Time>,
376) {
377 for (camera, mut transform) in &mut cameras {
378 if camera.order >= 0 {
380 continue;
381 }
382
383 let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
385
386 let mut changed = false;
388 if keyboard.pressed(KeyCode::KeyW) {
389 spherical_coords.inclination -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
390 changed = true;
391 }
392 if keyboard.pressed(KeyCode::KeyS) {
393 spherical_coords.inclination += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
394 changed = true;
395 }
396 if keyboard.pressed(KeyCode::KeyA) {
397 spherical_coords.azimuth += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
398 changed = true;
399 }
400 if keyboard.pressed(KeyCode::KeyD) {
401 spherical_coords.azimuth -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
402 changed = true;
403 }
404
405 if changed {
408 spherical_coords.inclination = spherical_coords.inclination.clamp(0.01, PI - 0.01);
409 transform.translation = spherical_coords.to_cartesian();
410 transform.look_at(Vec3::ZERO, Vec3::Y);
411 }
412 }
413}
414
415impl SphericalCoordinates {
416 fn from_cartesian(p: Vec3) -> SphericalCoordinates {
420 let radius = p.length();
421 SphericalCoordinates {
422 radius,
423 inclination: acos(p.y / radius),
424 azimuth: atan2(p.z, p.x),
425 }
426 }
427
428 fn to_cartesian(self) -> Vec3 {
432 let (sin_inclination, cos_inclination) = sin_cos(self.inclination);
433 let (sin_azimuth, cos_azimuth) = sin_cos(self.azimuth);
434 self.radius
435 * vec3(
436 sin_inclination * cos_azimuth,
437 cos_inclination,
438 sin_inclination * sin_azimuth,
439 )
440 }
441}