Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/shader_advanced/render_depth_to_texture.rs
9341 views
1
//! Demonstrates how to use depth-only cameras.
2
//!
3
//! A *depth-only camera* is a camera that renders only to a depth buffer, not
4
//! to a color buffer. That depth buffer can then be used in shaders for various
5
//! special effects.
6
//!
7
//! To create a depth-only camera, we create a [`Camera3d`] and set its
8
//! [`RenderTarget`] to [`RenderTarget::None`] to disable creation of a color
9
//! buffer. Then we add a system to the Core3d schedule that copies the
10
//! [`bevy::render::view::ViewDepthTexture`] that Bevy creates for that camera
11
//! to a texture. This texture can then be attached to a material and sampled in
12
//! the shader.
13
//!
14
//! This demo consists of a rotating cube with a depth-only camera pointed at
15
//! it. The depth texture from the depth-only camera appears on a plane. You can
16
//! use the WASD keys to make the depth-only camera orbit around the cube.
17
18
use std::f32::consts::{FRAC_PI_2, PI};
19
20
use 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
/// A marker component for a rotating cube.
45
#[derive(Component)]
46
struct RotatingCube;
47
48
/// The material that displays the contents of the depth buffer.
49
///
50
/// This material is placed on the plane.
51
#[derive(Clone, Debug, Asset, TypePath, AsBindGroup)]
52
struct ShowDepthTextureMaterial {
53
/// A copy of the depth texture that the depth-only camera produced.
54
#[texture(0, sample_type = "depth")]
55
#[sampler(1, sampler_type = "comparison")]
56
depth_texture: Option<Handle<Image>>,
57
}
58
59
/// Holds a copy of the depth buffer that the depth-only camera produces.
60
///
61
/// We need to make a copy for two reasons:
62
///
63
/// 1. The Bevy renderer automatically creates and maintains depth buffers on
64
/// its own. There's no mechanism to fetch the depth buffer for a camera outside
65
/// the render app. Thus it can't easily be attached to a material.
66
///
67
/// 2. `wgpu` doesn't allow applications to simultaneously render to and sample
68
/// from a standard depth texture, so a copy must be made regardless.
69
#[derive(Clone, Resource)]
70
struct DemoDepthTexture(Handle<Image>);
71
72
/// [Spherical coordinates], used to implement the camera orbiting
73
/// functionality.
74
///
75
/// Note that these are in the mathematics convention, not the physics
76
/// convention. In a real application, one would probably use the physics
77
/// convention, but for familiarity's sake we stick to the most common
78
/// convention here.
79
///
80
/// [Spherical coordinates]: https://en.wikipedia.org/wiki/Spherical_coordinate_system
81
#[derive(Clone, Copy, Debug)]
82
struct SphericalCoordinates {
83
/// The radius, in world units.
84
radius: f32,
85
/// The elevation angle (latitude).
86
inclination: f32,
87
/// The azimuth angle (longitude).
88
azimuth: f32,
89
}
90
91
/// The path to the shader that renders the depth texture.
92
static SHADER_ASSET_PATH: &str = "shaders/show_depth_texture_material.wgsl";
93
94
/// The size in texels of a depth texture.
95
const DEPTH_TEXTURE_SIZE: u32 = 256;
96
97
/// The rate at which the user can move the camera, in radians per second.
98
const CAMERA_MOVEMENT_SPEED: f32 = 2.0;
99
100
/// The entry point.
101
fn 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
127
fn 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
// Make sure we only run on the depth-only camera.
140
// We could make a marker component for that camera and extract it to
141
// the render world, but using `order` as a tag to tell the main camera
142
// and the depth-only camera apart works in a pinch.
143
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
175
/// Creates the scene.
176
fn 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
196
/// Spawns the main rotating cube.
197
fn 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
216
// Spawns the plane that shows the depth texture.
217
fn 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
234
/// Spawns a light.
235
fn spawn_light(commands: &mut Commands) {
236
commands.spawn((PointLight::default(), Transform::from_xyz(5.0, 6.0, 7.0)));
237
}
238
239
/// Spawns the depth-only camera.
240
fn 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
// Make sure that we render from this depth-only camera *before*
246
// rendering from the main camera.
247
order: -1,
248
..Camera::default()
249
},
250
// We specify no color render target, for maximum efficiency.
251
RenderTarget::None {
252
// When specifying no render target, we must manually specify
253
// the viewport size. Otherwise, Bevy won't know how big to make
254
// the depth buffer.
255
size: UVec2::splat(DEPTH_TEXTURE_SIZE),
256
},
257
// We need to disable multisampling or the depth texture will be
258
// multisampled, which adds complexity we don't care about for this
259
// demo.
260
Msaa::Off,
261
// Cameras with no render target render *nothing* by default. To get
262
// them to render something, we must add a prepass that specifies what
263
// we want to render: in this case, depth.
264
DepthPrepass,
265
));
266
}
267
268
/// Spawns the main camera that renders to the window.
269
fn 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
// Disable antialiasing just for simplicity's sake.
274
Msaa::Off,
275
));
276
}
277
278
/// Spawns the instructional text at the top of the screen.
279
fn 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
291
/// Spins the cube a bit every frame.
292
fn 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
300
impl Material for ShowDepthTextureMaterial {
301
fn fragment_shader() -> ShaderRef {
302
SHADER_ASSET_PATH.into()
303
}
304
}
305
306
impl FromWorld for DemoDepthTexture {
307
fn from_world(world: &mut World) -> Self {
308
let mut images = world.resource_mut::<Assets<Image>>();
309
310
// Create a new 32-bit floating point depth texture.
311
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
// Create a sampler. Note that this needs to specify a `compare`
323
// function in order to be compatible with depth textures.
324
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
335
impl ExtractResource for DemoDepthTexture {
336
type Source = Self;
337
338
fn extract_resource(source: &Self::Source) -> Self {
339
// Share the `DemoDepthTexture` resource over to the render world so
340
// that our system can access it.
341
(*source).clone()
342
}
343
}
344
345
/// Draws an outline of the depth texture on the screen.
346
fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
347
for (camera, transform) in &cameras {
348
// As above, we use the order as a cheap tag to tell the depth texture
349
// apart from the main texture.
350
if camera.order >= 0 {
351
continue;
352
}
353
354
// Draw a cone representing the camera.
355
gizmos.primitive_3d(
356
&Cone {
357
radius: 1.0,
358
height: 3.0,
359
},
360
Isometry3d::new(
361
transform.translation(),
362
// We have to rotate here because `Cone` primitives are oriented
363
// along +Y and cameras point along +Z.
364
transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
365
),
366
LIME,
367
);
368
}
369
}
370
371
/// Orbits the cube when WASD is pressed.
372
fn 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
// Only affect the depth camera.
379
if camera.order >= 0 {
380
continue;
381
}
382
383
// Convert the camera's position from Cartesian to spherical coordinates.
384
let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
385
386
// Modify those spherical coordinates as appropriate.
387
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 they were changed, convert from spherical coordinates back to
406
// Cartesian ones, and update the camera's transform.
407
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
415
impl SphericalCoordinates {
416
/// [Converts] from Cartesian coordinates to spherical coordinates.
417
///
418
/// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
419
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
/// [Converts] from spherical coordinates to Cartesian coordinates.
429
///
430
/// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
431
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
}
442
443