forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender_depth_to_texture.rs
More file actions
483 lines (438 loc) · 16.9 KB
/
render_depth_to_texture.rs
File metadata and controls
483 lines (438 loc) · 16.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
//! Demonstrates how to use depth-only cameras.
//!
//! A *depth-only camera* is a camera that renders only to a depth buffer, not
//! to a color buffer. That depth buffer can then be used in shaders for various
//! special effects.
//!
//! To create a depth-only camera, we create a [`Camera3d`] and set its
//! [`RenderTarget`] to [`RenderTarget::None`] to disable creation of a color
//! buffer. Then we add a new node to the render graph that copies the
//! [`bevy::render::view::ViewDepthTexture`] that Bevy creates for that camera
//! to a texture. This texture can then be attached to a material and sampled in
//! the shader.
//!
//! This demo consists of a rotating cube with a depth-only camera pointed at
//! it. The depth texture from the depth-only camera appears on a plane. You can
//! use the WASD keys to make the depth-only camera orbit around the cube.
use std::f32::consts::{FRAC_PI_2, PI};
use bevy::{
asset::RenderAssetUsages,
camera::RenderTarget,
color::palettes::css::LIME,
core_pipeline::{
core_3d::graph::{Core3d, Node3d},
prepass::DepthPrepass,
},
ecs::{query::QueryItem, system::lifetimeless::Read},
image::{ImageCompareFunction, ImageSampler, ImageSamplerDescriptor},
math::ops::{acos, atan2, sin_cos},
prelude::*,
render::{
camera::ExtractedCamera,
extract_resource::{ExtractResource, ExtractResourcePlugin},
render_asset::RenderAssets,
render_graph::{
NodeRunError, RenderGraphContext, RenderGraphExt as _, RenderLabel, ViewNode,
ViewNodeRunner,
},
render_resource::{
AsBindGroup, CommandEncoderDescriptor, Extent3d, Origin3d, TexelCopyTextureInfo,
TextureAspect, TextureDimension, TextureFormat,
},
renderer::RenderContext,
texture::GpuImage,
view::ViewDepthTexture,
RenderApp,
},
shader::ShaderRef,
};
/// A marker component for a rotating cube.
#[derive(Component)]
struct RotatingCube;
/// The material that displays the contents of the depth buffer.
///
/// This material is placed on the plane.
#[derive(Clone, Debug, Asset, TypePath, AsBindGroup)]
struct ShowDepthTextureMaterial {
/// A copy of the depth texture that the depth-only camera produced.
#[texture(0, sample_type = "depth")]
#[sampler(1, sampler_type = "comparison")]
depth_texture: Option<Handle<Image>>,
}
/// A label for the render node that copies the depth buffer from that of the
/// camera to the [`DemoDepthTexture`].
#[derive(Clone, PartialEq, Eq, Hash, Debug, RenderLabel)]
struct CopyDepthTexturePass;
/// The render node that copies the depth buffer from that of the camera to the
/// [`DemoDepthTexture`].
#[derive(Default)]
struct CopyDepthTextureNode;
/// Holds a copy of the depth buffer that the depth-only camera produces.
///
/// We need to make a copy for two reasons:
///
/// 1. The Bevy renderer automatically creates and maintains depth buffers on
/// its own. There's no mechanism to fetch the depth buffer for a camera outside
/// the render app. Thus it can't easily be attached to a material.
///
/// 2. `wgpu` doesn't allow applications to simultaneously render to and sample
/// from a standard depth texture, so a copy must be made regardless.
#[derive(Clone, Resource)]
struct DemoDepthTexture(Handle<Image>);
/// [Spherical coordinates], used to implement the camera orbiting
/// functionality.
///
/// Note that these are in the mathematics convention, not the physics
/// convention. In a real application, one would probably use the physics
/// convention, but for familiarity's sake we stick to the most common
/// convention here.
///
/// [Spherical coordinates]: https://en.wikipedia.org/wiki/Spherical_coordinate_system
#[derive(Clone, Copy, Debug)]
struct SphericalCoordinates {
/// The radius, in world units.
radius: f32,
/// The elevation angle (latitude).
inclination: f32,
/// The azimuth angle (longitude).
azimuth: f32,
}
/// The path to the shader that renders the depth texture.
static SHADER_ASSET_PATH: &str = "shaders/show_depth_texture_material.wgsl";
/// The size in texels of a depth texture.
const DEPTH_TEXTURE_SIZE: u32 = 256;
/// The rate at which the user can move the camera, in radians per second.
const CAMERA_MOVEMENT_SPEED: f32 = 2.0;
/// The entry point.
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.add_plugins(MaterialPlugin::<ShowDepthTextureMaterial>::default())
.add_plugins(ExtractResourcePlugin::<DemoDepthTexture>::default())
.init_resource::<DemoDepthTexture>()
.add_systems(Startup, setup)
.add_systems(Update, rotate_cube)
.add_systems(Update, draw_camera_gizmo)
.add_systems(Update, move_camera);
// Add the `CopyDepthTextureNode` to the render app.
let render_app = app
.get_sub_app_mut(RenderApp)
.expect("Render app should be present");
render_app.add_render_graph_node::<ViewNodeRunner<CopyDepthTextureNode>>(
Core3d,
CopyDepthTexturePass,
);
// We have the texture copy operation run in between the prepasses and
// the opaque pass. Since the depth rendering is part of the prepass, this
// is a reasonable time to perform the operation.
render_app.add_render_graph_edges(
Core3d,
(
Node3d::EndPrepasses,
CopyDepthTexturePass,
Node3d::MainOpaquePass,
),
);
app.run();
}
/// Creates the scene.
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut standard_materials: ResMut<Assets<StandardMaterial>>,
mut show_depth_texture_materials: ResMut<Assets<ShowDepthTextureMaterial>>,
demo_depth_texture: Res<DemoDepthTexture>,
) {
spawn_rotating_cube(&mut commands, &mut meshes, &mut standard_materials);
spawn_plane(
&mut commands,
&mut meshes,
&mut show_depth_texture_materials,
&demo_depth_texture,
);
spawn_light(&mut commands);
spawn_depth_only_camera(&mut commands);
spawn_main_camera(&mut commands);
spawn_instructions(&mut commands);
}
/// Spawns the main rotating cube.
fn spawn_rotating_cube(
commands: &mut Commands,
meshes: &mut Assets<Mesh>,
standard_materials: &mut Assets<StandardMaterial>,
) {
let cube_handle = meshes.add(Cuboid::new(3.0, 3.0, 3.0));
let rotating_cube_material_handle = standard_materials.add(StandardMaterial {
base_color: Color::WHITE,
unlit: false,
..default()
});
commands.spawn((
Mesh3d(cube_handle.clone()),
MeshMaterial3d(rotating_cube_material_handle),
Transform::IDENTITY,
RotatingCube,
));
}
// Spawns the plane that shows the depth texture.
fn spawn_plane(
commands: &mut Commands,
meshes: &mut Assets<Mesh>,
show_depth_texture_materials: &mut Assets<ShowDepthTextureMaterial>,
demo_depth_texture: &DemoDepthTexture,
) {
let plane_handle = meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(2.0)));
let show_depth_texture_material = show_depth_texture_materials.add(ShowDepthTextureMaterial {
depth_texture: Some(demo_depth_texture.0.clone()),
});
commands.spawn((
Mesh3d(plane_handle),
MeshMaterial3d(show_depth_texture_material),
Transform::from_xyz(10.0, 4.0, 0.0).with_scale(Vec3::splat(2.5)),
));
}
/// Spawns a light.
fn spawn_light(commands: &mut Commands) {
commands.spawn((PointLight::default(), Transform::from_xyz(5.0, 6.0, 7.0)));
}
/// Spawns the depth-only camera.
fn spawn_depth_only_camera(commands: &mut Commands) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-4.0, -5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Camera {
// We specify no color render target, for maximum efficiency.
target: RenderTarget::None {
// When specifying no render target, we must manually specify
// the viewport size. Otherwise, Bevy won't know how big to make
// the depth buffer.
size: UVec2::splat(DEPTH_TEXTURE_SIZE),
},
// Make sure that we render from this depth-only camera *before*
// rendering from the main camera.
order: -1,
..Camera::default()
},
// We need to disable multisampling or the depth texture will be
// multisampled, which adds complexity we don't care about for this
// demo.
Msaa::Off,
// Cameras with no render target render *nothing* by default. To get
// them to render something, we must add a prepass that specifies what
// we want to render: in this case, depth.
DepthPrepass,
));
}
/// Spawns the main camera that renders to the window.
fn spawn_main_camera(commands: &mut Commands) {
commands.spawn((
Camera3d::default(),
Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y),
// Disable antialiasing just for simplicity's sake.
Msaa::Off,
));
}
/// Spawns the instructional text at the top of the screen.
fn spawn_instructions(commands: &mut Commands) {
commands.spawn((
Text::new("Use WASD to move the secondary camera"),
Node {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..Node::default()
},
));
}
/// Spins the cube a bit every frame.
fn rotate_cube(mut cubes: Query<&mut Transform, With<RotatingCube>>, time: Res<Time>) {
for mut transform in &mut cubes {
transform.rotate_x(1.5 * time.delta_secs());
transform.rotate_y(1.1 * time.delta_secs());
transform.rotate_z(-1.3 * time.delta_secs());
}
}
impl Material for ShowDepthTextureMaterial {
fn fragment_shader() -> ShaderRef {
SHADER_ASSET_PATH.into()
}
}
impl ViewNode for CopyDepthTextureNode {
type ViewQuery = (Read<ExtractedCamera>, Read<ViewDepthTexture>);
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, depth_texture): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
// Make sure we only run on the depth-only camera.
// We could make a marker component for that camera and extract it to
// the render world, but using `order` as a tag to tell the main camera
// and the depth-only camera apart works in a pinch.
if camera.order >= 0 {
return Ok(());
}
// Grab the texture we're going to copy to.
let demo_depth_texture = world.resource::<DemoDepthTexture>();
let image_assets = world.resource::<RenderAssets<GpuImage>>();
let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else {
return Ok(());
};
// Perform the copy.
render_context.add_command_buffer_generation_task(move |render_device| {
let mut command_encoder =
render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("copy depth to demo texture command encoder"),
});
command_encoder.push_debug_group("copy depth to demo texture");
// Copy from the view's depth texture to the destination depth
// texture.
command_encoder.copy_texture_to_texture(
TexelCopyTextureInfo {
texture: &depth_texture.texture,
mip_level: 0,
origin: Origin3d::default(),
aspect: TextureAspect::DepthOnly,
},
TexelCopyTextureInfo {
texture: &demo_depth_image.texture,
mip_level: 0,
origin: Origin3d::default(),
aspect: TextureAspect::DepthOnly,
},
Extent3d {
width: DEPTH_TEXTURE_SIZE,
height: DEPTH_TEXTURE_SIZE,
depth_or_array_layers: 1,
},
);
command_encoder.pop_debug_group();
command_encoder.finish()
});
Ok(())
}
}
impl FromWorld for DemoDepthTexture {
fn from_world(world: &mut World) -> Self {
let mut images = world.resource_mut::<Assets<Image>>();
// Create a new 32-bit floating point depth texture.
let mut depth_image = Image::new_uninit(
Extent3d {
width: DEPTH_TEXTURE_SIZE,
height: DEPTH_TEXTURE_SIZE,
depth_or_array_layers: 1,
},
TextureDimension::D2,
TextureFormat::Depth32Float,
RenderAssetUsages::default(),
);
// Create a sampler. Note that this needs to specify a `compare`
// function in order to be compatible with depth textures.
depth_image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
label: Some("custom depth image sampler".to_owned()),
compare: Some(ImageCompareFunction::Always),
..ImageSamplerDescriptor::default()
});
let depth_image_handle = images.add(depth_image);
DemoDepthTexture(depth_image_handle)
}
}
impl ExtractResource for DemoDepthTexture {
type Source = Self;
fn extract_resource(source: &Self::Source) -> Self {
// Share the `DemoDepthTexture` resource over to the render world so
// that our `CopyDepthTextureNode` can access it.
(*source).clone()
}
}
/// Draws an outline of the depth texture on the screen.
fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
for (camera, transform) in &cameras {
// As above, we use the order as a cheap tag to tell the depth texture
// apart from the main texture.
if camera.order >= 0 {
continue;
}
// Draw a cone representing the camera.
gizmos.primitive_3d(
&Cone {
radius: 1.0,
height: 3.0,
},
Isometry3d::new(
transform.translation(),
// We have to rotate here because `Cone` primitives are oriented
// along +Y and cameras point along +Z.
transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
),
LIME,
);
}
}
/// Orbits the cube when WASD is pressed.
fn move_camera(
mut cameras: Query<(&Camera, &mut Transform)>,
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
for (camera, mut transform) in &mut cameras {
// Only affect the depth camera.
if camera.order >= 0 {
continue;
}
// Convert the camera's position from Cartesian to spherical coordinates.
let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
// Modify those spherical coordinates as appropriate.
let mut changed = false;
if keyboard.pressed(KeyCode::KeyW) {
spherical_coords.inclination -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
changed = true;
}
if keyboard.pressed(KeyCode::KeyS) {
spherical_coords.inclination += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
changed = true;
}
if keyboard.pressed(KeyCode::KeyA) {
spherical_coords.azimuth += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
changed = true;
}
if keyboard.pressed(KeyCode::KeyD) {
spherical_coords.azimuth -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
changed = true;
}
// If they were changed, convert from spherical coordinates back to
// Cartesian ones, and update the camera's transform.
if changed {
spherical_coords.inclination = spherical_coords.inclination.clamp(0.01, PI - 0.01);
transform.translation = spherical_coords.to_cartesian();
transform.look_at(Vec3::ZERO, Vec3::Y);
}
}
}
impl SphericalCoordinates {
/// [Converts] from Cartesian coordinates to spherical coordinates.
///
/// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
fn from_cartesian(p: Vec3) -> SphericalCoordinates {
let radius = p.length();
SphericalCoordinates {
radius,
inclination: acos(p.y / radius),
azimuth: atan2(p.z, p.x),
}
}
/// [Converts] from spherical coordinates to Cartesian coordinates.
///
/// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
fn to_cartesian(self) -> Vec3 {
let (sin_inclination, cos_inclination) = sin_cos(self.inclination);
let (sin_azimuth, cos_azimuth) = sin_cos(self.azimuth);
self.radius
* vec3(
sin_inclination * cos_azimuth,
cos_inclination,
sin_inclination * sin_azimuth,
)
}
}