From a77ab7fad8283f8bedc81b369db21977100eae48 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 27 Oct 2021 20:14:48 +0200 Subject: [PATCH 1/9] add shader not using MaterialPlugin --- Cargo.toml | 4 + assets/shaders/custom_minimal.wgsl | 32 ++++++ examples/shader/custom_shader_manual.rs | 142 ++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 assets/shaders/custom_minimal.wgsl create mode 100644 examples/shader/custom_shader_manual.rs diff --git a/Cargo.toml b/Cargo.toml index 76b6199116e46..e6549306291cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -418,6 +418,10 @@ path = "examples/shader/shader_defs.rs" name = "shader_material" path = "examples/shader/shader_material.rs" +[[example]] +name = "custom_shader_manual" +path = "examples/shader/custom_shader_manual.rs" + # Tools [[example]] name = "bevymark" diff --git a/assets/shaders/custom_minimal.wgsl b/assets/shaders/custom_minimal.wgsl new file mode 100644 index 0000000000000..a8904762c95fb --- /dev/null +++ b/assets/shaders/custom_minimal.wgsl @@ -0,0 +1,32 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] uv: vec2; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.model * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.uv = vertex.uv; + return out; +} + + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + return vec4(in.uv.x, in.uv.y, 0.0, 1.0); +} diff --git a/examples/shader/custom_shader_manual.rs b/examples/shader/custom_shader_manual.rs new file mode 100644 index 0000000000000..bc04d2ae57743 --- /dev/null +++ b/examples/shader/custom_shader_manual.rs @@ -0,0 +1,142 @@ +use bevy::{ + core_pipeline::Transparent3d, + ecs::prelude::*, + math::prelude::*, + pbr2::{ + DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + SetMeshViewBindGroup, + }, + prelude::{App, AssetServer, Assets, GlobalTransform, Handle, Plugin, Transform}, + render2::{ + camera::PerspectiveCameraBundle, + mesh::{shape, Mesh}, + render_component::{ExtractComponent, ExtractComponentPlugin}, + render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + view::{ComputedVisibility, ExtractedView, Msaa, Visibility}, + RenderApp, RenderStage, + }, + PipelinedDefaultPlugins, +}; + +fn main() { + App::new() + .add_plugins(PipelinedDefaultPlugins) + .add_plugin(CustomMaterialPlugin) + .add_startup_system(setup) + .run(); +} + +fn setup(mut commands: Commands, mut meshes: ResMut>) { + // 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(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +#[derive(Component)] +struct CustomMaterial; +impl ExtractComponent for CustomMaterial { + type Query = (); + type Filter = With; + + fn extract_component(_: bevy::ecs::query::QueryItem) -> Self { + CustomMaterial + } +} + +pub struct CustomMaterialPlugin; + +impl Plugin for CustomMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_plugin(ExtractComponentPlugin::::default()); + app.sub_app(RenderApp) + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue_custom); + } +} + +pub struct CustomPipeline { + shader: Handle, + mesh_pipeline: MeshPipeline, +} + +impl FromWorld for CustomPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let asset_server = world.get_resource::().unwrap(); + let shader = asset_server.load("shaders/custom_minimal.wgsl"); + let mesh_pipeline = world.get_resource::().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.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 + } +} + +fn queue_custom( + transparent_3d_draw_functions: Res>, + custom_pipeline: Res, + msaa: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = transparent_3d_draw_functions + .read() + .get_id::() + .unwrap(); + + let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + 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)), + }); + } + } +} + +type DrawCustom = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + DrawMesh, +); From 0336fffa2f18ca6d81c06c74fc956d6da1120d14 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 27 Oct 2021 19:22:18 +0200 Subject: [PATCH 2/9] add glsl shader example --- Cargo.toml | 4 + assets/shaders/custom.frag | 10 ++ assets/shaders/custom.vert | 21 ++++ examples/shader/shader_material_glsl.rs | 150 ++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 assets/shaders/custom.frag create mode 100644 assets/shaders/custom.vert create mode 100644 examples/shader/shader_material_glsl.rs diff --git a/Cargo.toml b/Cargo.toml index e6549306291cc..8e67dd715e0b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -422,6 +422,10 @@ path = "examples/shader/shader_material.rs" name = "custom_shader_manual" path = "examples/shader/custom_shader_manual.rs" +[[example]] +name = "custom_shader_pipelined_glsl" +path = "examples/shader/custom_shader_pipelined_glsl.rs" + # Tools [[example]] name = "bevymark" diff --git a/assets/shaders/custom.frag b/assets/shaders/custom.frag new file mode 100644 index 0000000000000..883afb48ef6f5 --- /dev/null +++ b/assets/shaders/custom.frag @@ -0,0 +1,10 @@ +#version 450 + +layout(location = 0) in vec2 uv; + +layout(location = 0) out vec4 o_Target; + + +void main() { + o_Target = vec4(uv.x, uv.y, 0.0, 1.0); +} diff --git a/assets/shaders/custom.vert b/assets/shaders/custom.vert new file mode 100644 index 0000000000000..80b1c63f6d518 --- /dev/null +++ b/assets/shaders/custom.vert @@ -0,0 +1,21 @@ +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec3 Vertex_Normal; +layout(location = 2) in vec2 Vertex_Uv; + +layout(set = 0, binding = 0) uniform CameraViewProj { + mat4 ViewProj; + mat4 Projection; + vec3 WorldPosition; +}; + +layout(set = 1, binding = 0) uniform Mesh { + mat4 Model; + mat4 InverseTransposeModel; + uint flags; +}; + +void main() { + gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0); +} diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs new file mode 100644 index 0000000000000..b0a3e5f582de7 --- /dev/null +++ b/examples/shader/shader_material_glsl.rs @@ -0,0 +1,150 @@ +use bevy::{ + core_pipeline::Transparent3d, + pbr::{ + DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + SetMeshViewBindGroup, + }, + prelude::*, + render::{ + render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + 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>) { + // 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(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +#[derive(Component)] +struct CustomMaterial; + +pub struct CustomMaterialPlugin; + +impl Plugin for CustomMaterialPlugin { + fn build(&self, app: &mut App) { + app.sub_app_mut(RenderApp) + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Extract, extract_custom_material) + .add_system_to_stage(RenderStage::Queue, queue_custom); + } +} + +// extract the `CustomMaterial` component into the render world +fn extract_custom_material( + mut commands: Commands, + mut previous_len: Local, + mut query: Query>, +) { + 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` +fn queue_custom( + transparent_3d_draw_functions: Res>, + custom_pipeline: Res, + msaa: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = transparent_3d_draw_functions + .read() + .get_id::() + .unwrap(); + + let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + 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)), + }); + } + } +} + +pub struct CustomPipeline { + shader_vert: Handle, + shader_frag: Handle, + mesh_pipeline: MeshPipeline, +} + +impl FromWorld for CustomPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let asset_server = world.get_resource::().unwrap(); + let shader_vert = asset_server.load("shaders/custom.vert"); + let shader_frag = asset_server.load("shaders/custom.frag"); + + let mesh_pipeline = world.get_resource::().unwrap(); + + CustomPipeline { + shader_vert, + shader_frag, + 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_vert.clone(); + descriptor.vertex.entry_point = "main".into(); + let mut fragment = descriptor.fragment.as_mut().unwrap(); + fragment.shader = self.shader_frag.clone(); + fragment.entry_point = "main".into(); + descriptor.layout = Some(vec![ + self.mesh_pipeline.view_layout.clone(), + self.mesh_pipeline.mesh_layout.clone(), + ]); + descriptor + } +} + +type DrawCustom = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + DrawMesh, +); From c4345a6d4687b44d91caf43e22b1771dd99dde70 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 28 Oct 2021 17:37:03 +0200 Subject: [PATCH 3/9] add instancing example --- Cargo.toml | 5 + assets/shaders/custom_instancing.wgsl | 35 ++++ examples/shader/shader_instancing.rs | 250 ++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 assets/shaders/custom_instancing.wgsl create mode 100644 examples/shader/shader_instancing.rs diff --git a/Cargo.toml b/Cargo.toml index 8e67dd715e0b9..cce3c5bd4e86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,6 +102,7 @@ anyhow = "1.0.4" rand = "0.8.0" ron = "0.7.0" serde = { version = "1", features = ["derive"] } +bytemuck = "1.7" # Needed to poll Task examples futures-lite = "1.11.3" @@ -426,6 +427,10 @@ path = "examples/shader/custom_shader_manual.rs" name = "custom_shader_pipelined_glsl" path = "examples/shader/custom_shader_pipelined_glsl.rs" +[[example]] +name = "custom_shader_instancing_pipelined" +path = "examples/shader/custom_shader_instancing_pipelined.rs" + # Tools [[example]] name = "bevymark" diff --git a/assets/shaders/custom_instancing.wgsl b/assets/shaders/custom_instancing.wgsl new file mode 100644 index 0000000000000..262df7224b887 --- /dev/null +++ b/assets/shaders/custom_instancing.wgsl @@ -0,0 +1,35 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; + + [[location(3)]] i_pos_scale: vec4; + [[location(4)]] i_color: vec4; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] color: vec4; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; + let world_position = mesh.model * vec4(position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.color = vertex.i_color; + return out; +} + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + return in.color; +} diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs new file mode 100644 index 0000000000000..072afe4c4fc48 --- /dev/null +++ b/examples/shader/shader_instancing.rs @@ -0,0 +1,250 @@ +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, + view::{ComputedVisibility, ExtractedView, Msaa, Visibility}, + 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>) { + 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(), + )); + + // 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); +impl ExtractComponent for InstanceMaterialData { + type Query = &'static InstanceMaterialData; + type Filter = (); + + fn extract_component(item: bevy::ecs::query::QueryItem) -> Self { + InstanceMaterialData(item.0.clone()) + } +} + +pub struct CustomMaterialPlugin; + +impl Plugin for CustomMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_plugin(ExtractComponentPlugin::::default()); + app.sub_app_mut(RenderApp) + .add_render_command::() + .init_resource::() + .init_resource::>() + .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>, + custom_pipeline: Res, + msaa: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + material_meshes: Query< + (Entity, &MeshUniform), + (With>, With), + >, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = transparent_3d_draw_functions + .read() + .get_id::() + .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, +) { + 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, + mesh_pipeline: MeshPipeline, +} + +impl FromWorld for CustomPipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let asset_server = world.get_resource::().unwrap(); + asset_server.watch_for_changes().unwrap(); + let shader = asset_server.load("shaders/custom_instancing.wgsl"); + + let mesh_pipeline = world.get_resource::().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::() 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>, + SQuery>>, + SQuery>, + ); + #[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 + } +} From 1182577dca9adbb6bf2a35b15f2c3b46079b0ff1 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 28 Oct 2021 18:33:47 +0200 Subject: [PATCH 4/9] add animate_shader example --- Cargo.toml | 4 + assets/shaders/animate_shader.wgsl | 72 +++++++ examples/shader/animate_shader.rs | 249 ++++++++++++++++++++++++ examples/shader/custom_shader_manual.rs | 101 +++++----- 4 files changed, 376 insertions(+), 50 deletions(-) create mode 100644 assets/shaders/animate_shader.wgsl create mode 100644 examples/shader/animate_shader.rs diff --git a/Cargo.toml b/Cargo.toml index cce3c5bd4e86c..dbcde410bcae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -431,6 +431,10 @@ path = "examples/shader/custom_shader_pipelined_glsl.rs" name = "custom_shader_instancing_pipelined" path = "examples/shader/custom_shader_instancing_pipelined.rs" +[[example]] +name = "animate_shader_pipelined" +path = "examples/shader/animate_shader_pipelined.rs" + # Tools [[example]] name = "bevymark" diff --git a/assets/shaders/animate_shader.wgsl b/assets/shaders/animate_shader.wgsl new file mode 100644 index 0000000000000..fdc60da00b539 --- /dev/null +++ b/assets/shaders/animate_shader.wgsl @@ -0,0 +1,72 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] uv: vec2; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.model * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.uv = vertex.uv; + return out; +} + + +struct Time { + time_since_startup: f32; +}; +[[group(2), binding(0)]] +var time: Time; + + +fn oklab_to_linear_srgb(c: vec3) -> vec3 { + let L = c.x; + let a = c.y; + let b = c.z; + + let l_ = L + 0.3963377774 * a + 0.2158037573 * b; + let m_ = L - 0.1055613458 * a - 0.0638541728 * b; + let s_ = L - 0.0894841775 * a - 1.2914855480 * b; + + let l = l_*l_*l_; + let m = m_*m_*m_; + let s = s_*s_*s_; + + return vec3( + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s, + ); +} + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + let speed = 2.0; + let t_1 = sin(time.time_since_startup * speed) * 0.5 + 0.5; + let t_2 = cos(time.time_since_startup * speed); + + let distance_to_center = distance(in.uv, vec2(0.5)) * 1.4; + + // blending is done in a perceptual color space: https://bottosson.github.io/posts/oklab/ + let red = vec3(0.627955, 0.224863, 0.125846); + let green = vec3(0.86644, -0.233887, 0.179498); + let blue = vec3(0.701674, 0.274566, -0.169156); + let white = vec3(1.0, 0.0, 0.0); + let mixed = mix(mix(red, blue, t_1), mix(green, white, t_2), distance_to_center); + + return vec4(oklab_to_linear_srgb(mixed), 1.0); +} diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs new file mode 100644 index 0000000000000..6e8f4b00af0fc --- /dev/null +++ b/examples/shader/animate_shader.rs @@ -0,0 +1,249 @@ +use bevy::{ + core_pipeline::Transparent3d, + ecs::system::{lifetimeless::SRes, SystemParamItem}, + pbr::{ + DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, + SetMeshViewBindGroup, + }, + prelude::*, + render::{ + 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>) { + // 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(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +#[derive(Component)] +struct CustomMaterial; + +pub struct CustomMaterialPlugin; + +impl Plugin for CustomMaterialPlugin { + fn build(&self, app: &mut App) { + let render_device = app.world.get_resource::().unwrap(); + let buffer = render_device.create_buffer(&BufferDescriptor { + label: Some("time uniform buffer"), + size: std::mem::size_of::() as u64, + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + app.sub_app_mut(RenderApp) + .add_render_command::() + .insert_resource(TimeMeta { + buffer, + bind_group: None, + }) + .init_resource::() + .init_resource::>() + .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, + mut query: Query>, +) { + 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` +fn queue_custom( + transparent_3d_draw_functions: Res>, + custom_pipeline: Res, + msaa: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + material_meshes: Query<(Entity, &MeshUniform), (With>, With)>, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = transparent_3d_draw_functions + .read() + .get_id::() + .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(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