add render_to_texture example (#1927)

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Mariusz Kryński 2021-04-19 21:07:19 +00:00
parent 3d4b1b9ff2
commit fa6d4dbd53
5 changed files with 308 additions and 0 deletions

View file

@ -153,6 +153,10 @@ path = "examples/3d/parenting.rs"
name = "pbr"
path = "examples/3d/pbr.rs"
[[example]]
name = "render_to_texture"
path = "examples/3d/render_to_texture.rs"
[[example]]
name = "spawner"
path = "examples/3d/spawner.rs"

View file

@ -3,6 +3,7 @@ mod pass_node;
mod render_resources_node;
mod shared_buffers_node;
mod texture_copy_node;
mod texture_node;
mod window_swapchain_node;
mod window_texture_node;
@ -11,5 +12,6 @@ pub use pass_node::*;
pub use render_resources_node::*;
pub use shared_buffers_node::*;
pub use texture_copy_node::*;
pub use texture_node::*;
pub use window_swapchain_node::*;
pub use window_texture_node::*;

View file

@ -0,0 +1,69 @@
use bevy_asset::HandleUntyped;
use bevy_ecs::world::World;
use std::borrow::Cow;
use crate::{
render_graph::{Node, ResourceSlotInfo, ResourceSlots},
renderer::{RenderContext, RenderResourceId, RenderResourceType},
texture::{SamplerDescriptor, TextureDescriptor, SAMPLER_ASSET_INDEX, TEXTURE_ASSET_INDEX},
};
pub struct TextureNode {
pub texture_descriptor: TextureDescriptor,
pub sampler_descriptor: Option<SamplerDescriptor>,
pub handle: Option<HandleUntyped>,
}
impl TextureNode {
pub const TEXTURE: &'static str = "texture";
pub fn new(
texture_descriptor: TextureDescriptor,
sampler_descriptor: Option<SamplerDescriptor>,
handle: Option<HandleUntyped>,
) -> Self {
Self {
texture_descriptor,
sampler_descriptor,
handle,
}
}
}
impl Node for TextureNode {
fn output(&self) -> &[ResourceSlotInfo] {
static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo {
name: Cow::Borrowed(TextureNode::TEXTURE),
resource_type: RenderResourceType::Texture,
}];
OUTPUT
}
fn update(
&mut self,
_world: &World,
render_context: &mut dyn RenderContext,
_input: &ResourceSlots,
output: &mut ResourceSlots,
) {
if output.get(0).is_none() {
let render_resource_context = render_context.resources_mut();
let texture_id = render_resource_context.create_texture(self.texture_descriptor);
if let Some(handle) = &self.handle {
render_resource_context.set_asset_resource_untyped(
handle.clone(),
RenderResourceId::Texture(texture_id),
TEXTURE_ASSET_INDEX,
);
if let Some(sampler_descriptor) = self.sampler_descriptor {
let sampler_id = render_resource_context.create_sampler(&sampler_descriptor);
render_resource_context.set_asset_resource_untyped(
handle.clone(),
RenderResourceId::Sampler(sampler_id),
SAMPLER_ASSET_INDEX,
);
}
}
output.set(0, RenderResourceId::Texture(texture_id));
}
}
}

View file

@ -0,0 +1,232 @@
use bevy::{
prelude::*,
reflect::TypeUuid,
render::{
camera::{ActiveCameras, Camera, CameraProjection},
pass::{
LoadOp, Operations, PassDescriptor, RenderPassColorAttachmentDescriptor,
RenderPassDepthStencilAttachmentDescriptor, TextureAttachment,
},
render_graph::{
base::{node::MAIN_PASS, MainPass},
CameraNode, PassNode, RenderGraph, TextureNode,
},
texture::{
Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat,
TextureUsage,
},
},
window::WindowId,
};
pub struct FirstPass;
pub const RENDER_TEXTURE_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Texture::TYPE_UUID, 13378939762009864029);
pub const TEXTURE_NODE: &str = "texure_node";
pub const DEPTH_TEXTURE_NODE: &str = "depth_texure_node";
pub const FIRST_PASS: &str = "first_pass";
pub const FIRST_PASS_CAMERA: &str = "first_pass_camera";
fn add_render_to_texture_graph(graph: &mut RenderGraph, size: Extent3d) {
let mut pass_node = PassNode::<&FirstPass>::new(PassDescriptor {
color_attachments: vec![RenderPassColorAttachmentDescriptor {
attachment: TextureAttachment::Input("color_attachment".to_string()),
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(Color::rgb(0.1, 0.2, 0.3)),
store: true,
},
}],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
attachment: TextureAttachment::Input("depth".to_string()),
depth_ops: Some(Operations {
load: LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
}),
sample_count: 1,
});
pass_node.add_camera(FIRST_PASS_CAMERA);
graph.add_node(FIRST_PASS, pass_node);
graph.add_system_node(FIRST_PASS_CAMERA, CameraNode::new(FIRST_PASS_CAMERA));
graph.add_node_edge(FIRST_PASS_CAMERA, FIRST_PASS).unwrap();
graph.add_node(
TEXTURE_NODE,
TextureNode::new(
TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: Default::default(),
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
},
Some(SamplerDescriptor::default()),
Some(RENDER_TEXTURE_HANDLE),
),
);
graph.add_node(
DEPTH_TEXTURE_NODE,
TextureNode::new(
TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Depth32Float,
usage: TextureUsage::OUTPUT_ATTACHMENT | TextureUsage::SAMPLED,
},
None,
None,
),
);
graph.add_node_edge(TEXTURE_NODE, FIRST_PASS).unwrap();
graph
.add_slot_edge(
TEXTURE_NODE,
TextureNode::TEXTURE,
FIRST_PASS,
"color_attachment",
)
.unwrap();
graph
.add_slot_edge(
DEPTH_TEXTURE_NODE,
TextureNode::TEXTURE,
FIRST_PASS,
"depth",
)
.unwrap();
graph.add_node_edge(FIRST_PASS, MAIN_PASS).unwrap();
graph.add_node_edge("transform", FIRST_PASS).unwrap();
}
struct FirstPassCube;
struct MainPassCube;
/// rotates the inner cube (first pass)
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(1.5 * time.delta_seconds());
transform.rotation *= Quat::from_rotation_z(1.3 * time.delta_seconds());
}
}
/// rotates the outer cube (main pass)
fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(1.0 * time.delta_seconds());
transform.rotation *= Quat::from_rotation_y(0.7 * time.delta_seconds());
}
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut render_graph: ResMut<RenderGraph>,
mut active_cameras: ResMut<ActiveCameras>,
) {
let size = Extent3d::new(512, 512, 1);
add_render_to_texture_graph(&mut render_graph, size);
let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 }));
let cube_material_handle = materials.add(StandardMaterial {
base_color: Color::rgb(0.8, 0.7, 0.6),
reflectance: 0.02,
roughness: 1.0,
unlit: false,
..Default::default()
});
commands
.spawn_bundle(PbrBundle {
mesh: cube_handle,
material: cube_material_handle,
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)),
..Default::default()
})
.insert(FirstPassCube)
.insert(FirstPass)
.remove::<MainPass>();
// light
// note: currently lights are shared between passes!
commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
..Default::default()
});
// camera
let mut first_pass_camera = PerspectiveCameraBundle {
camera: Camera {
name: Some(FIRST_PASS_CAMERA.to_string()),
window: WindowId::new(), // otherwise it will use main window size / aspect for calculation of projection matrix
..Default::default()
},
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
.looking_at(Vec3::default(), Vec3::Y),
..Default::default()
};
active_cameras.add(FIRST_PASS_CAMERA);
let camera_projection = &mut first_pass_camera.perspective_projection;
camera_projection.update(size.width as f32, size.height as f32);
first_pass_camera.camera.projection_matrix = camera_projection.get_projection_matrix();
first_pass_camera.camera.depth_calculation = camera_projection.depth_calculation();
commands.spawn_bundle(first_pass_camera);
let texture_handle = RENDER_TEXTURE_HANDLE.typed();
let cube_size = 4.0;
let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size)));
let material_handle = materials.add(StandardMaterial {
base_color_texture: Some(texture_handle),
reflectance: 0.02,
unlit: false,
..Default::default()
});
// add entities to the world
commands
.spawn_bundle(PbrBundle {
mesh: cube_handle,
material: material_handle,
transform: Transform {
translation: Vec3::new(0.0, 0.0, 1.5),
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0),
..Default::default()
},
visible: Visible {
is_transparent: true,
..Default::default()
},
..Default::default()
})
.insert(MainPassCube);
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0))
.looking_at(Vec3::default(), Vec3::Y),
..Default::default()
});
}
fn main() {
let mut app = App::build();
app.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.add_system(cube_rotator_system.system())
.add_system(rotator_system.system())
.run();
}

View file

@ -91,6 +91,7 @@ Example | File | Description
`orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
`pbr` | [`3d/pbr.rs`](./3d/[pbr].rs) | Demonstrates use of Physically Based Rendering (PBR) properties
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to texture
`spawner` | [`3d/spawner.rs`](./3d/spawner.rs) | Renders a large number of cubes with changing position and material
`texture` | [`3d/texture.rs`](./3d/texture.rs) | Shows configuration of texture materials
`update_gltf_scene` | [`3d/update_gltf_scene.rs`](./3d/update_gltf_scene.rs) | Update a scene from a gltf file, either by spawning the scene as a child of another entity, or by accessing the entities of the scene