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,
};
#[derive(Component)]
struct RotatingCube;
#[derive(Clone, Debug, Asset, TypePath, AsBindGroup)]
struct ShowDepthTextureMaterial {
#[texture(0, sample_type = "depth")]
#[sampler(1, sampler_type = "comparison")]
depth_texture: Option<Handle<Image>>,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, RenderLabel)]
struct CopyDepthTexturePass;
#[derive(Default)]
struct CopyDepthTextureNode;
#[derive(Clone, Resource)]
struct DemoDepthTexture(Handle<Image>);
#[derive(Clone, Copy, Debug)]
struct SphericalCoordinates {
radius: f32,
inclination: f32,
azimuth: f32,
}
static SHADER_ASSET_PATH: &str = "shaders/show_depth_texture_material.wgsl";
const DEPTH_TEXTURE_SIZE: u32 = 256;
const CAMERA_MOVEMENT_SPEED: f32 = 2.0;
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);
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,
);
render_app.add_render_graph_edges(
Core3d,
(
Node3d::EndPrepasses,
CopyDepthTexturePass,
Node3d::MainOpaquePass,
),
);
app.run();
}
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);
}
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,
));
}
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)),
));
}
fn spawn_light(commands: &mut Commands) {
commands.spawn((PointLight::default(), Transform::from_xyz(5.0, 6.0, 7.0)));
}
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 {
target: RenderTarget::None {
size: UVec2::splat(DEPTH_TEXTURE_SIZE),
},
order: -1,
..Camera::default()
},
Msaa::Off,
DepthPrepass,
));
}
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),
Msaa::Off,
));
}
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()
},
));
}
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> {
if camera.order >= 0 {
return Ok(());
}
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(());
};
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");
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>>();
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(),
);
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 {
(*source).clone()
}
}
fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
for (camera, transform) in &cameras {
if camera.order >= 0 {
continue;
}
gizmos.primitive_3d(
&Cone {
radius: 1.0,
height: 3.0,
},
Isometry3d::new(
transform.translation(),
transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
),
LIME,
);
}
}
fn move_camera(
mut cameras: Query<(&Camera, &mut Transform)>,
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
for (camera, mut transform) in &mut cameras {
if camera.order >= 0 {
continue;
}
let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
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 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 {
fn from_cartesian(p: Vec3) -> SphericalCoordinates {
let radius = p.length();
SphericalCoordinates {
radius,
inclination: acos(p.y / radius),
azimuth: atan2(p.z, p.x),
}
}
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,
)
}
}