Skip to content

Commit

Permalink
add globals to mesh view bind group (bevyengine#5409)
Browse files Browse the repository at this point in the history
# 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>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent 56abfcb commit 14b7710
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 310 deletions.
58 changes: 16 additions & 42 deletions assets/shaders/animate_shader.wgsl
@@ -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;
Expand All @@ -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;

Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_core/src/lib.rs
Expand Up @@ -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::*;
Expand Down Expand Up @@ -37,6 +38,8 @@ impl Plugin for CorePlugin {

register_rust_types(app);
register_math_types(app);

app.init_resource::<FrameCount>();
}
}

Expand Down Expand Up @@ -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);
1 change: 1 addition & 0 deletions crates/bevy_diagnostic/Cargo.toml
Expand Up @@ -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" }
22 changes: 4 additions & 18 deletions crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs
@@ -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);
}
}
Expand All @@ -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;
Expand All @@ -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;
}
}
20 changes: 19 additions & 1 deletion crates/bevy_pbr/src/render/mesh.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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"),
});
Expand Down Expand Up @@ -469,6 +480,7 @@ impl FromWorld for MeshPipeline {
),
}
};

MeshPipeline {
view_layout,
mesh_layout,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_pbr/src/render/mesh_view_bindings.wgsl
Expand Up @@ -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;
11 changes: 11 additions & 0 deletions crates/bevy_pbr/src/render/mesh_view_types.wgsl
Expand Up @@ -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 changes: 67 additions & 0 deletions crates/bevy_render/src/globals.rs
@@ -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);
}
26 changes: 25 additions & 1 deletion crates/bevy_render/src/lib.rs
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -34,6 +36,7 @@ pub mod prelude {
};
}

use globals::GlobalsPlugin;
pub use once_cell;
use prelude::ComputedVisibility;

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}

0 comments on commit 14b7710

Please sign in to comment.