2022-01-05 19:43:11 +00:00
|
|
|
use bevy::{
|
|
|
|
core_pipeline::Transparent3d,
|
|
|
|
ecs::system::{lifetimeless::*, SystemParamItem},
|
|
|
|
math::prelude::*,
|
|
|
|
pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup},
|
|
|
|
prelude::*,
|
|
|
|
render::{
|
|
|
|
mesh::GpuBufferInfo,
|
|
|
|
render_asset::RenderAssets,
|
|
|
|
render_component::{ExtractComponent, ExtractComponentPlugin},
|
|
|
|
render_phase::{
|
|
|
|
AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase,
|
|
|
|
SetItemPipeline, TrackedRenderPass,
|
|
|
|
},
|
|
|
|
render_resource::*,
|
|
|
|
renderer::RenderDevice,
|
2022-01-17 22:55:44 +00:00
|
|
|
view::{ComputedVisibility, ExtractedView, Msaa, NoFrustumCulling, Visibility},
|
2022-01-05 19:43:11 +00:00
|
|
|
RenderApp, RenderStage,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
use bytemuck::{Pod, Zeroable};
|
|
|
|
|
|
|
|
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>>) {
|
|
|
|
commands.spawn().insert_bundle((
|
|
|
|
meshes.add(Mesh::from(shape::Cube { size: 0.5 })),
|
|
|
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
|
|
|
GlobalTransform::default(),
|
|
|
|
InstanceMaterialData(
|
|
|
|
(1..=10)
|
|
|
|
.flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0)))
|
|
|
|
.map(|(x, y)| InstanceData {
|
|
|
|
position: Vec3::new(x * 10.0 - 5.0, y * 10.0 - 5.0, 0.0),
|
|
|
|
scale: 1.0,
|
|
|
|
color: Color::hsla(x * 360., y, 0.5, 1.0).as_rgba_f32(),
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
),
|
|
|
|
Visibility::default(),
|
|
|
|
ComputedVisibility::default(),
|
2022-01-17 22:55:44 +00:00
|
|
|
// NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform.
|
|
|
|
// As the cube is at the origin, if its Aabb moves outside the view frustum, all the
|
|
|
|
// instanced cubes will be culled.
|
|
|
|
// The InstanceMaterialData contains the 'GlobalTransform' information for this custom
|
|
|
|
// instancing, and that is not taken into account with the built-in frustum culling.
|
|
|
|
// We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker
|
|
|
|
// component to avoid incorrect culling.
|
|
|
|
NoFrustumCulling,
|
2022-01-05 19:43:11 +00:00
|
|
|
));
|
|
|
|
|
|
|
|
// camera
|
|
|
|
commands.spawn_bundle(PerspectiveCameraBundle {
|
|
|
|
transform: Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
|
|
..Default::default()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
struct InstanceMaterialData(Vec<InstanceData>);
|
|
|
|
impl ExtractComponent for InstanceMaterialData {
|
|
|
|
type Query = &'static InstanceMaterialData;
|
|
|
|
type Filter = ();
|
|
|
|
|
|
|
|
fn extract_component(item: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
|
|
|
|
InstanceMaterialData(item.0.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CustomMaterialPlugin;
|
|
|
|
|
|
|
|
impl Plugin for CustomMaterialPlugin {
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
app.add_plugin(ExtractComponentPlugin::<InstanceMaterialData>::default());
|
|
|
|
app.sub_app_mut(RenderApp)
|
|
|
|
.add_render_command::<Transparent3d, DrawCustom>()
|
|
|
|
.init_resource::<CustomPipeline>()
|
|
|
|
.init_resource::<SpecializedPipelines<CustomPipeline>>()
|
|
|
|
.add_system_to_stage(RenderStage::Queue, queue_custom)
|
|
|
|
.add_system_to_stage(RenderStage::Prepare, prepare_instance_buffers);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
|
|
#[repr(C)]
|
|
|
|
struct InstanceData {
|
|
|
|
position: Vec3,
|
|
|
|
scale: f32,
|
|
|
|
color: [f32; 4],
|
|
|
|
}
|
|
|
|
|
|
|
|
fn queue_custom(
|
|
|
|
transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,
|
|
|
|
custom_pipeline: Res<CustomPipeline>,
|
|
|
|
msaa: Res<Msaa>,
|
|
|
|
mut pipelines: ResMut<SpecializedPipelines<CustomPipeline>>,
|
|
|
|
mut pipeline_cache: ResMut<RenderPipelineCache>,
|
|
|
|
material_meshes: Query<
|
|
|
|
(Entity, &MeshUniform),
|
|
|
|
(With<Handle<Mesh>>, With<InstanceMaterialData>),
|
|
|
|
>,
|
|
|
|
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);
|
|
|
|
let pipeline = pipelines.specialize(&mut pipeline_cache, &custom_pipeline, key);
|
|
|
|
|
|
|
|
for (view, mut transparent_phase) in views.iter_mut() {
|
|
|
|
let view_matrix = view.transform.compute_matrix();
|
|
|
|
let view_row_2 = view_matrix.row(2);
|
|
|
|
for (entity, mesh_uniform) in material_meshes.iter() {
|
|
|
|
transparent_phase.add(Transparent3d {
|
|
|
|
entity,
|
|
|
|
pipeline,
|
|
|
|
draw_function: draw_custom,
|
|
|
|
distance: view_row_2.dot(mesh_uniform.transform.col(3)),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Component)]
|
|
|
|
pub struct InstanceBuffer {
|
|
|
|
buffer: Buffer,
|
|
|
|
length: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn prepare_instance_buffers(
|
|
|
|
mut commands: Commands,
|
|
|
|
query: Query<(Entity, &InstanceMaterialData)>,
|
|
|
|
render_device: Res<RenderDevice>,
|
|
|
|
) {
|
|
|
|
for (entity, instance_data) in query.iter() {
|
|
|
|
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
|
|
|
|
label: Some("instance data buffer"),
|
|
|
|
contents: bytemuck::cast_slice(instance_data.0.as_slice()),
|
|
|
|
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
|
|
|
});
|
|
|
|
commands.entity(entity).insert(InstanceBuffer {
|
|
|
|
buffer,
|
|
|
|
length: instance_data.0.len(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CustomPipeline {
|
|
|
|
shader: Handle<Shader>,
|
|
|
|
mesh_pipeline: MeshPipeline,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromWorld for CustomPipeline {
|
|
|
|
fn from_world(world: &mut World) -> Self {
|
|
|
|
let world = world.cell();
|
|
|
|
let asset_server = world.get_resource::<AssetServer>().unwrap();
|
|
|
|
asset_server.watch_for_changes().unwrap();
|
|
|
|
let shader = asset_server.load("shaders/instancing.wgsl");
|
|
|
|
|
|
|
|
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap();
|
|
|
|
|
|
|
|
CustomPipeline {
|
|
|
|
shader,
|
|
|
|
mesh_pipeline: mesh_pipeline.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SpecializedPipeline for CustomPipeline {
|
|
|
|
type Key = MeshPipelineKey;
|
|
|
|
|
|
|
|
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
|
|
|
let mut descriptor = self.mesh_pipeline.specialize(key);
|
|
|
|
descriptor.vertex.shader = self.shader.clone();
|
|
|
|
descriptor.vertex.buffers.push(VertexBufferLayout {
|
|
|
|
array_stride: std::mem::size_of::<InstanceData>() as u64,
|
|
|
|
step_mode: VertexStepMode::Instance,
|
|
|
|
attributes: vec![
|
|
|
|
VertexAttribute {
|
|
|
|
format: VertexFormat::Float32x4,
|
|
|
|
offset: 0,
|
|
|
|
shader_location: 3, // shader locations 0-2 are taken up by Position, Normal and UV attributes
|
|
|
|
},
|
|
|
|
VertexAttribute {
|
|
|
|
format: VertexFormat::Float32x4,
|
|
|
|
offset: VertexFormat::Float32x4.size(),
|
|
|
|
shader_location: 4,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
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(),
|
|
|
|
]);
|
|
|
|
|
|
|
|
descriptor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type DrawCustom = (
|
|
|
|
SetItemPipeline,
|
|
|
|
SetMeshViewBindGroup<0>,
|
|
|
|
SetMeshBindGroup<1>,
|
|
|
|
DrawMeshInstanced,
|
|
|
|
);
|
|
|
|
|
|
|
|
pub struct DrawMeshInstanced;
|
|
|
|
impl EntityRenderCommand for DrawMeshInstanced {
|
|
|
|
type Param = (
|
|
|
|
SRes<RenderAssets<Mesh>>,
|
|
|
|
SQuery<Read<Handle<Mesh>>>,
|
|
|
|
SQuery<Read<InstanceBuffer>>,
|
|
|
|
);
|
|
|
|
#[inline]
|
|
|
|
fn render<'w>(
|
|
|
|
_view: Entity,
|
|
|
|
item: Entity,
|
|
|
|
(meshes, mesh_query, instance_buffer_query): SystemParamItem<'w, '_, Self::Param>,
|
|
|
|
pass: &mut TrackedRenderPass<'w>,
|
|
|
|
) -> RenderCommandResult {
|
|
|
|
let mesh_handle = mesh_query.get(item).unwrap();
|
|
|
|
let instance_buffer = instance_buffer_query.get(item).unwrap();
|
|
|
|
|
|
|
|
let gpu_mesh = match meshes.into_inner().get(mesh_handle) {
|
|
|
|
Some(gpu_mesh) => gpu_mesh,
|
|
|
|
None => return RenderCommandResult::Failure,
|
|
|
|
};
|
|
|
|
|
|
|
|
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
|
|
|
|
pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..));
|
|
|
|
|
|
|
|
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
|
|
|
|
match &gpu_mesh.buffer_info {
|
|
|
|
GpuBufferInfo::Indexed {
|
|
|
|
buffer,
|
|
|
|
index_format,
|
|
|
|
count,
|
|
|
|
} => {
|
|
|
|
pass.set_index_buffer(buffer.slice(..), 0, *index_format);
|
|
|
|
pass.draw_indexed(0..*count, 0, 0..instance_buffer.length as u32);
|
|
|
|
}
|
|
|
|
GpuBufferInfo::NonIndexed { vertex_count } => {
|
|
|
|
pass.draw_indexed(0..*vertex_count, 0, 0..instance_buffer.length as u32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
RenderCommandResult::Success
|
|
|
|
}
|
|
|
|
}
|