mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
add globals to mesh view bind group (#5409)
# Objective - It's often really useful to have access to the time when writing shaders. ## Solution - Add a UnifformBuffer in the mesh view bind group - This buffer contains the time, delta time and a wrapping frame count https://user-images.githubusercontent.com/8348954/180130314-97948c2a-2d11-423d-a9c4-fb5c9d1892c7.mp4 --- ## Changelog - Added a `GlobalsUniform` at position 9 of the mesh view bind group ## Notes The implementation is currently split between bevy_render and bevy_pbr because I was basing my implementation on the `ViewPlugin`. I'm not sure if that's the right way to structure it. I named this `globals` instead of just time because we could potentially add more things to it. ## References in other engines - Godot: <https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/canvas_item_shader.html#global-built-ins> - Global time since startup, in seconds, by default resets to 0 after 3600 seconds - Doesn't seem to have anything else - Unreal: <https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/Materials/ExpressionReference/Constant/> - Generic time value that updates every frame. Can be paused or scaled. - Frame count node, doesn't seem to be an equivalent for shaders: <https://docs.unrealengine.com/4.26/en-US/BlueprintAPI/Utilities/GetFrameCount/> - Unity: <https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html> - time since startup in seconds. No mention of time wrapping. Stored as a `vec4(t/20, t, t*2, t*3)` where `t` is the value in seconds - Also has delta time, sin time and cos time - ShaderToy: <https://www.shadertoy.com/howto> - iTime is the time since startup in seconds. - iFrameRate - iTimeDelta - iFrame frame counter Co-authored-by: Charles <IceSentry@users.noreply.github.com>
This commit is contained in:
parent
018509c3a1
commit
8073362039
10 changed files with 175 additions and 310 deletions
|
@ -1,39 +1,7 @@
|
|||
#import bevy_pbr::mesh_types
|
||||
// The time since startup data is in the globals binding which is part of the mesh_view_bindings import
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> mesh: Mesh;
|
||||
|
||||
// NOTE: Bindings must come before functions that use them!
|
||||
#import bevy_pbr::mesh_functions
|
||||
|
||||
struct Vertex {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) normal: vec3<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.clip_position = mesh_position_local_to_clip(mesh.model, vec4<f32>(vertex.position, 1.0));
|
||||
out.uv = vertex.uv;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
struct Time {
|
||||
time_since_startup: f32,
|
||||
};
|
||||
@group(2) @binding(0)
|
||||
var<uniform> time: Time;
|
||||
|
||||
|
||||
fn oklab_to_linear_srgb(c: vec3<f32>) -> vec3<f32> {
|
||||
let L = c.x;
|
||||
let a = c.y;
|
||||
|
@ -43,22 +11,28 @@ fn oklab_to_linear_srgb(c: vec3<f32>) -> vec3<f32> {
|
|||
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_;
|
||||
let l = l_ * l_ * l_;
|
||||
let m = m_ * m_ * m_;
|
||||
let s = s_ * s_ * s_;
|
||||
|
||||
return vec3<f32>(
|
||||
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,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
struct FragmentInput {
|
||||
#import bevy_pbr::mesh_vertex_output
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
|
||||
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);
|
||||
// The globals binding contains various global values like time
|
||||
// which is the time since startup in seconds
|
||||
let t_1 = sin(globals.time * speed) * 0.5 + 0.5;
|
||||
let t_2 = cos(globals.time * speed);
|
||||
|
||||
let distance_to_center = distance(in.uv, vec2<f32>(0.5)) * 1.4;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
mod name;
|
||||
mod task_pool_options;
|
||||
|
||||
use bevy_ecs::system::Resource;
|
||||
pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable};
|
||||
pub use name::*;
|
||||
pub use task_pool_options::*;
|
||||
|
@ -37,6 +38,8 @@ impl Plugin for CorePlugin {
|
|||
|
||||
register_rust_types(app);
|
||||
register_math_types(app);
|
||||
|
||||
app.init_resource::<FrameCount>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,3 +86,9 @@ fn register_math_types(app: &mut App) {
|
|||
.register_type::<bevy_math::DQuat>()
|
||||
.register_type::<bevy_math::Quat>();
|
||||
}
|
||||
|
||||
/// Keeps a count of rendered frames since the start of the app
|
||||
///
|
||||
/// Wraps to 0 when it reaches the maximum u32 value
|
||||
#[derive(Default, Resource, Clone, Copy)]
|
||||
pub struct FrameCount(pub u32);
|
||||
|
|
|
@ -16,3 +16,4 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" }
|
|||
bevy_log = { path = "../bevy_log", version = "0.9.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.9.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.9.0-dev" }
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
use crate::{Diagnostic, DiagnosticId, Diagnostics};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::system::{Res, ResMut, Resource};
|
||||
use bevy_core::FrameCount;
|
||||
use bevy_ecs::system::{Res, ResMut};
|
||||
use bevy_time::Time;
|
||||
|
||||
/// Adds "frame time" diagnostic to an App, specifically "frame time", "fps" and "frame count"
|
||||
#[derive(Default)]
|
||||
pub struct FrameTimeDiagnosticsPlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FrameTimeDiagnosticsState {
|
||||
frame_count: u64,
|
||||
}
|
||||
|
||||
impl Plugin for FrameTimeDiagnosticsPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_startup_system(Self::setup_system)
|
||||
.insert_resource(FrameTimeDiagnosticsState { frame_count: 0 })
|
||||
.add_system(Self::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +31,9 @@ impl FrameTimeDiagnosticsPlugin {
|
|||
pub fn diagnostic_system(
|
||||
mut diagnostics: ResMut<Diagnostics>,
|
||||
time: Res<Time>,
|
||||
mut state: ResMut<FrameTimeDiagnosticsState>,
|
||||
frame_count: Res<FrameCount>,
|
||||
) {
|
||||
diagnostics.add_measurement(Self::FRAME_COUNT, || {
|
||||
state.frame_count = state.frame_count.wrapping_add(1);
|
||||
state.frame_count as f64
|
||||
});
|
||||
diagnostics.add_measurement(Self::FRAME_COUNT, || frame_count.0 as f64);
|
||||
|
||||
if time.delta_seconds_f64() == 0.0 {
|
||||
return;
|
||||
|
@ -52,9 +44,3 @@ impl FrameTimeDiagnosticsPlugin {
|
|||
diagnostics.add_measurement(Self::FPS, || 1.0 / time.delta_seconds_f64());
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameTimeDiagnosticsState {
|
||||
pub fn reset_frame_count(&mut self) {
|
||||
self.frame_count = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use bevy_math::{Mat3A, Mat4, Vec2};
|
|||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
||||
globals::{GlobalsBuffer, GlobalsUniform},
|
||||
mesh::{
|
||||
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||
GpuBufferInfo, Mesh, MeshVertexBufferLayout,
|
||||
|
@ -383,6 +384,16 @@ impl FromWorld for MeshPipeline {
|
|||
},
|
||||
count: None,
|
||||
},
|
||||
BindGroupLayoutEntry {
|
||||
binding: 9,
|
||||
visibility: ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: Some(GlobalsUniform::min_size()),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("mesh_view_layout"),
|
||||
});
|
||||
|
@ -469,6 +480,7 @@ impl FromWorld for MeshPipeline {
|
|||
),
|
||||
}
|
||||
};
|
||||
|
||||
MeshPipeline {
|
||||
view_layout,
|
||||
mesh_layout,
|
||||
|
@ -764,11 +776,13 @@ pub fn queue_mesh_view_bind_groups(
|
|||
global_light_meta: Res<GlobalLightMeta>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
views: Query<(Entity, &ViewShadowBindings, &ViewClusterBindings)>,
|
||||
globals_buffer: Res<GlobalsBuffer>,
|
||||
) {
|
||||
if let (Some(view_binding), Some(light_binding), Some(point_light_binding)) = (
|
||||
if let (Some(view_binding), Some(light_binding), Some(point_light_binding), Some(globals)) = (
|
||||
view_uniforms.uniforms.binding(),
|
||||
light_meta.view_gpu_lights.binding(),
|
||||
global_light_meta.gpu_point_lights.binding(),
|
||||
globals_buffer.buffer.binding(),
|
||||
) {
|
||||
for (entity, view_shadow_bindings, view_cluster_bindings) in &views {
|
||||
let view_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
|
||||
|
@ -815,6 +829,10 @@ pub fn queue_mesh_view_bind_groups(
|
|||
binding: 8,
|
||||
resource: view_cluster_bindings.offsets_and_counts_binding().unwrap(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 9,
|
||||
resource: globals.clone(),
|
||||
},
|
||||
],
|
||||
label: Some("mesh_view_bind_group"),
|
||||
layout: &mesh_pipeline.view_layout,
|
||||
|
|
|
@ -40,3 +40,6 @@ var<storage> cluster_light_index_lists: ClusterLightIndexLists;
|
|||
@group(0) @binding(8)
|
||||
var<storage> cluster_offsets_and_counts: ClusterOffsetsAndCounts;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(9)
|
||||
var<uniform> globals: Globals;
|
||||
|
|
|
@ -85,3 +85,14 @@ struct ClusterOffsetsAndCounts {
|
|||
data: array<vec4<u32>>,
|
||||
};
|
||||
#endif
|
||||
|
||||
struct Globals {
|
||||
// The time since startup in seconds
|
||||
// Wraps to 0 after 1 hour.
|
||||
time: f32,
|
||||
// The delta time since the previous frame in seconds
|
||||
delta_time: f32,
|
||||
// Frame count since the start of the app.
|
||||
// It wraps to zero when it reaches the maximum value of a u32.
|
||||
frame_count: u32,
|
||||
}
|
||||
|
|
67
crates/bevy_render/src/globals.rs
Normal file
67
crates/bevy_render/src/globals.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::{
|
||||
extract_resource::ExtractResource,
|
||||
render_resource::{ShaderType, UniformBuffer},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
Extract, RenderApp, RenderStage,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_core::FrameCount;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_time::Time;
|
||||
|
||||
pub struct GlobalsPlugin;
|
||||
|
||||
impl Plugin for GlobalsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<GlobalsBuffer>()
|
||||
.init_resource::<Time>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_time)
|
||||
.add_system_to_stage(RenderStage::Prepare, prepare_globals_buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_time(mut commands: Commands, time: Extract<Res<Time>>) {
|
||||
commands.insert_resource(time.clone());
|
||||
}
|
||||
|
||||
/// Contains global values useful when writing shaders.
|
||||
/// Currently only contains values related to time.
|
||||
#[derive(Default, Clone, Resource, ExtractResource, Reflect, ShaderType)]
|
||||
#[reflect(Resource)]
|
||||
pub struct GlobalsUniform {
|
||||
/// The time since startup in seconds.
|
||||
/// Wraps to 0 after 1 hour.
|
||||
time: f32,
|
||||
/// The delta time since the previous frame in seconds
|
||||
delta_time: f32,
|
||||
/// Frame count since the start of the app.
|
||||
/// It wraps to zero when it reaches the maximum value of a u32.
|
||||
frame_count: u32,
|
||||
}
|
||||
|
||||
/// The buffer containing the [`GlobalsUniform`]
|
||||
#[derive(Resource, Default)]
|
||||
pub struct GlobalsBuffer {
|
||||
pub buffer: UniformBuffer<GlobalsUniform>,
|
||||
}
|
||||
|
||||
fn prepare_globals_buffer(
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut globals_buffer: ResMut<GlobalsBuffer>,
|
||||
time: Res<Time>,
|
||||
frame_count: Res<FrameCount>,
|
||||
) {
|
||||
let buffer = globals_buffer.buffer.get_mut();
|
||||
buffer.time = time.seconds_since_startup_wrapped_f32();
|
||||
buffer.delta_time = time.delta_seconds();
|
||||
buffer.frame_count = frame_count.0;
|
||||
|
||||
globals_buffer
|
||||
.buffer
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
}
|
|
@ -5,6 +5,7 @@ pub mod color;
|
|||
pub mod extract_component;
|
||||
mod extract_param;
|
||||
pub mod extract_resource;
|
||||
pub mod globals;
|
||||
pub mod mesh;
|
||||
pub mod primitives;
|
||||
pub mod rangefinder;
|
||||
|
@ -18,6 +19,7 @@ mod spatial_bundle;
|
|||
pub mod texture;
|
||||
pub mod view;
|
||||
|
||||
use bevy_core::FrameCount;
|
||||
use bevy_hierarchy::ValidParentCheckPlugin;
|
||||
pub use extract_param::Extract;
|
||||
|
||||
|
@ -34,6 +36,7 @@ pub mod prelude {
|
|||
};
|
||||
}
|
||||
|
||||
use globals::GlobalsPlugin;
|
||||
pub use once_cell;
|
||||
use prelude::ComputedVisibility;
|
||||
|
||||
|
@ -324,7 +327,9 @@ impl Plugin for RenderPlugin {
|
|||
.add_plugin(MeshPlugin)
|
||||
// NOTE: Load this after renderer initialization so that it knows about the supported
|
||||
// compressed texture formats
|
||||
.add_plugin(ImagePlugin);
|
||||
.add_plugin(ImagePlugin)
|
||||
.add_plugin(GlobalsPlugin)
|
||||
.add_plugin(FrameCountPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,3 +363,22 @@ fn extract(app_world: &mut World, render_app: &mut App) {
|
|||
// see <https://github.com/bevyengine/bevy/issues/5082>
|
||||
extract.apply_buffers(running_world);
|
||||
}
|
||||
|
||||
pub struct FrameCountPlugin;
|
||||
impl Plugin for FrameCountPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_system(update_frame_count);
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.add_system_to_stage(RenderStage::Extract, extract_frame_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_frame_count(mut frame_count: ResMut<FrameCount>) {
|
||||
frame_count.0 = frame_count.0.wrapping_add(1);
|
||||
}
|
||||
|
||||
fn extract_frame_count(mut commands: Commands, frame_count: Extract<Res<FrameCount>>) {
|
||||
commands.insert_resource(**frame_count);
|
||||
}
|
||||
|
|
|
@ -1,52 +1,28 @@
|
|||
//! A shader that uses dynamic data like the time since startup.
|
||||
//!
|
||||
//! This example uses a specialized pipeline.
|
||||
//! The time data is in the globals binding which is part of the `mesh_view_bindings` shader import.
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::core_3d::Transparent3d,
|
||||
ecs::system::{
|
||||
lifetimeless::{Read, SRes},
|
||||
SystemParamItem,
|
||||
},
|
||||
pbr::{
|
||||
DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
|
||||
SetMeshViewBindGroup,
|
||||
},
|
||||
prelude::*,
|
||||
render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||
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,
|
||||
},
|
||||
};
|
||||
use bevy::{prelude::*, reflect::TypeUuid, render::render_resource::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugin(CustomMaterialPlugin)
|
||||
.add_plugin(MaterialPlugin::<CustomMaterial>::default())
|
||||
.add_startup_system(setup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<CustomMaterial>>,
|
||||
) {
|
||||
// cube
|
||||
commands.spawn((
|
||||
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(),
|
||||
));
|
||||
commands.spawn(MaterialMeshBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
material: materials.add(CustomMaterial {}),
|
||||
..default()
|
||||
});
|
||||
|
||||
// camera
|
||||
commands.spawn(Camera3dBundle {
|
||||
|
@ -55,216 +31,12 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
|
|||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct CustomMaterial;
|
||||
#[derive(AsBindGroup, TypeUuid, Debug, Clone)]
|
||||
#[uuid = "a3d71c04-d054-4946-80f8-ba6cfbc90cad"]
|
||||
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.add_plugin(ExtractComponentPlugin::<CustomMaterial>::default())
|
||||
.add_plugin(ExtractResourcePlugin::<ExtractedTime>::default());
|
||||
|
||||
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::Prepare, prepare_time)
|
||||
.add_system_to_stage(RenderStage::Queue, queue_custom)
|
||||
.add_system_to_stage(RenderStage::Queue, queue_time_bind_group);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractComponent for CustomMaterial {
|
||||
type Query = Read<CustomMaterial>;
|
||||
|
||||
type Filter = ();
|
||||
|
||||
fn extract_component(_: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
|
||||
CustomMaterial
|
||||
}
|
||||
}
|
||||
|
||||
// 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 &mut views {
|
||||
let rangefinder = view.rangefinder3d();
|
||||
for (entity, mesh_uniform, mesh_handle) in &material_meshes {
|
||||
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(Resource, Default)]
|
||||
struct ExtractedTime {
|
||||
seconds_since_startup: f32,
|
||||
}
|
||||
|
||||
impl ExtractResource for ExtractedTime {
|
||||
type Source = Time;
|
||||
|
||||
fn extract_resource(time: &Self::Source) -> Self {
|
||||
ExtractedTime {
|
||||
seconds_since_startup: time.seconds_since_startup() as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
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);
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
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
|
||||
impl Material for CustomMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/animate_shader.wgsl".into()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue