bevy/examples/shader/animate_shader.rs
Robin KAY 5b5013d540 Add ViewRangefinder3d to reduce boilerplate when enqueuing standard 3D PhaseItems. (#5014)
# Objective

Reduce the boilerplate code needed to make draw order sorting work correctly when queuing items through new common functionality. Also fix several instances in the bevy code-base (mostly examples) where this boilerplate appears to be incorrect.

## Solution

- Moved the logic for handling back-to-front vs front-to-back draw ordering into the PhaseItems by inverting the sort key ordering of Opaque3d and AlphaMask3d. The means that all the standard 3d rendering phases measure distance in the same way. Clients of these structs no longer need to know to negate the distance.
- Added a new utility struct, ViewRangefinder3d, which encapsulates the maths needed to calculate a "distance" from an ExtractedView and a mesh's transform matrix.
- Converted all the occurrences of the distance calculations in Bevy and its examples to use ViewRangefinder3d. Several of these occurrences appear to be buggy because they don't invert the view matrix or don't negate the distance where appropriate. This leads me to the view that Bevy should expose a facility to correctly perform this calculation.

## Migration Guide

Code which creates Opaque3d, AlphaMask3d, or Transparent3d phase items _should_ use ViewRangefinder3d to calculate the distance value.

Code which manually calculated the distance for Opaque3d or AlphaMask3d phase items and correctly negated the z value will no longer depth sort correctly. However, incorrect depth sorting for these types will not impact the rendered output as sorting is only a performance optimisation when drawing with depth-testing enabled. Code which manually calculated the distance for Transparent3d phase items will continue to work as before.
2022-07-05 06:13:39 +00:00

265 lines
8.5 KiB
Rust

//! A shader that uses dynamic data like the time since startup.
//!
//! This example uses a specialized pipeline.
use bevy::{
core_pipeline::core_3d::Transparent3d,
ecs::system::{lifetimeless::SRes, SystemParamItem},
pbr::{
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
SetMeshViewBindGroup,
},
prelude::*,
render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
render_phase::{
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
SetItemPipeline, TrackedRenderPass,
},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
view::{ComputedVisibility, ExtractedView, Msaa, Visibility},
RenderApp, RenderStage,
},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(CustomMaterialPlugin)
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
// cube
commands.spawn().insert_bundle((
meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Transform::from_xyz(0.0, 0.5, 0.0),
GlobalTransform::default(),
CustomMaterial,
Visibility::default(),
ComputedVisibility::default(),
));
// camera
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}
#[derive(Component)]
struct CustomMaterial;
pub struct CustomMaterialPlugin;
impl Plugin for CustomMaterialPlugin {
fn build(&self, app: &mut App) {
let render_device = app.world.resource::<RenderDevice>();
let buffer = render_device.create_buffer(&BufferDescriptor {
label: Some("time uniform buffer"),
size: std::mem::size_of::<f32>() as u64,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
app.sub_app_mut(RenderApp)
.add_render_command::<Transparent3d, DrawCustom>()
.insert_resource(TimeMeta {
buffer,
bind_group: None,
})
.init_resource::<CustomPipeline>()
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
.add_system_to_stage(RenderStage::Extract, extract_time)
.add_system_to_stage(RenderStage::Extract, extract_custom_material)
.add_system_to_stage(RenderStage::Prepare, prepare_time)
.add_system_to_stage(RenderStage::Queue, queue_custom)
.add_system_to_stage(RenderStage::Queue, queue_time_bind_group);
}
}
// extract the `CustomMaterial` component into the render world
fn extract_custom_material(
mut commands: Commands,
mut previous_len: Local<usize>,
mut query: Query<Entity, With<CustomMaterial>>,
) {
let mut values = Vec::with_capacity(*previous_len);
for entity in query.iter_mut() {
values.push((entity, (CustomMaterial,)));
}
*previous_len = values.len();
commands.insert_or_spawn_batch(values);
}
// add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline`
#[allow(clippy::too_many_arguments)]
fn queue_custom(
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
custom_pipeline: Res<CustomPipeline>,
msaa: Res<Msaa>,
mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,
mut pipeline_cache: ResMut<PipelineCache>,
render_meshes: Res<RenderAssets<Mesh>>,
material_meshes: Query<(Entity, &MeshUniform, &Handle<Mesh>), With<CustomMaterial>>,
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent3d>)>,
) {
let draw_custom = transparent_3d_draw_functions
.read()
.get_id::<DrawCustom>()
.unwrap();
let key = MeshPipelineKey::from_msaa_samples(msaa.samples)
| MeshPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList);
for (view, mut transparent_phase) in views.iter_mut() {
let rangefinder = view.rangefinder3d();
for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() {
if let Some(mesh) = render_meshes.get(mesh_handle) {
let pipeline = pipelines
.specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout)
.unwrap();
transparent_phase.add(Transparent3d {
entity,
pipeline,
draw_function: draw_custom,
distance: rangefinder.distance(&mesh_uniform.transform),
});
}
}
}
}
#[derive(Default)]
struct ExtractedTime {
seconds_since_startup: f32,
}
// extract the passed time into a resource in the render world
fn extract_time(mut commands: Commands, time: Res<Time>) {
commands.insert_resource(ExtractedTime {
seconds_since_startup: time.seconds_since_startup() as f32,
});
}
struct TimeMeta {
buffer: Buffer,
bind_group: Option<BindGroup>,
}
// write the extracted time into the corresponding uniform buffer
fn prepare_time(
time: Res<ExtractedTime>,
time_meta: ResMut<TimeMeta>,
render_queue: Res<RenderQueue>,
) {
render_queue.write_buffer(
&time_meta.buffer,
0,
bevy::core::cast_slice(&[time.seconds_since_startup]),
);
}
// create a bind group for the time uniform buffer
fn queue_time_bind_group(
render_device: Res<RenderDevice>,
mut time_meta: ResMut<TimeMeta>,
pipeline: Res<CustomPipeline>,
) {
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
label: None,
layout: &pipeline.time_bind_group_layout,
entries: &[BindGroupEntry {
binding: 0,
resource: time_meta.buffer.as_entire_binding(),
}],
});
time_meta.bind_group = Some(bind_group);
}
pub struct CustomPipeline {
shader: Handle<Shader>,
mesh_pipeline: MeshPipeline,
time_bind_group_layout: BindGroupLayout,
}
impl FromWorld for CustomPipeline {
fn from_world(world: &mut World) -> Self {
let asset_server = world.resource::<AssetServer>();
let shader = asset_server.load("shaders/animate_shader.wgsl");
let render_device = world.resource::<RenderDevice>();
let time_bind_group_layout =
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
label: Some("time bind group"),
entries: &[BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(std::mem::size_of::<f32>() as u64),
},
count: None,
}],
});
let mesh_pipeline = world.resource::<MeshPipeline>();
CustomPipeline {
shader,
mesh_pipeline: mesh_pipeline.clone(),
time_bind_group_layout,
}
}
}
impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;
descriptor.vertex.shader = self.shader.clone();
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();
descriptor.layout = Some(vec![
self.mesh_pipeline.view_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
self.time_bind_group_layout.clone(),
]);
Ok(descriptor)
}
}
type DrawCustom = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetMeshBindGroup<1>,
SetTimeBindGroup<2>,
DrawMesh,
);
struct SetTimeBindGroup<const I: usize>;
impl<const I: usize> EntityRenderCommand for SetTimeBindGroup<I> {
type Param = SRes<TimeMeta>;
fn render<'w>(
_view: Entity,
_item: Entity,
time_meta: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let time_bind_group = time_meta.into_inner().bind_group.as_ref().unwrap();
pass.set_bind_group(I, time_bind_group, &[]);
RenderCommandResult::Success
}
}