Skip to content

Commit

Permalink
Add animate shaders example (#1765)
Browse files Browse the repository at this point in the history
This PR adds an example on how to animate a shader by passing the global `time.seconds_since_startup()` to a component, and accessing that component inside the shader.

Hopefully this is the current proper solution, please let me know if it should be solved in another way.

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
wilk10 and cart committed Apr 15, 2021
1 parent 8a6b929 commit 9b7ed18
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ name = "scene"
path = "examples/scene/scene.rs"

# Shaders
[[example]]
name = "animate_shader"
path = "examples/shader/animate_shader.rs"

[[example]]
name = "array_texture"
path = "examples/shader/array_texture.rs"
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ Example | File | Description

Example | File | Description
--- | --- | ---
`animate_shader` | [`shader/animate_shader.rs`](./shader/animate_shader.rs) | Shows how to animate a shader by accessing a time uniform variable
`array_texture` | [`shader/array_texture.rs`](./shader/array_texture.rs) | Illustrates how to create a texture for use with a texture2DArray shader uniform variable
`hot_shader_reloading` | [`shader/hot_shader_reloading.rs`](./shader/hot_shader_reloading.rs) | Illustrates how to load shaders such that they can be edited while the example is still running
`mesh_custom_attribute` | [`shader/mesh_custom_attribute.rs`](./shader/mesh_custom_attribute.rs) | Illustrates how to add a custom attribute to a mesh and use it in a custom shader
Expand Down
124 changes: 124 additions & 0 deletions examples/shader/animate_shader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use bevy::{
prelude::*,
reflect::TypeUuid,
render::{
mesh::shape,
pipeline::{PipelineDescriptor, RenderPipeline},
render_graph::{base, RenderGraph, RenderResourcesNode},
renderer::RenderResources,
shader::{ShaderStage, ShaderStages},
},
};

/// This example shows how to animate a shader, by passing the global `time.seconds_since_startup()`
/// via a 'TimeComponent` to the shader.
pub fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.add_system(animate_shader.system())
.run();
}

#[derive(RenderResources, Default, TypeUuid)]
#[uuid = "463e4b8a-d555-4fc2-ba9f-4c880063ba92"]
struct TimeUniform {
value: f32,
}

const VERTEX_SHADER: &str = r#"
#version 450
layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec2 Vertex_Uv;
layout(location = 0) out vec2 v_Uv;
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
};
layout(set = 1, binding = 0) uniform Transform {
mat4 Model;
};
void main() {
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
v_Uv = Vertex_Uv;
}
"#;

const FRAGMENT_SHADER: &str = r#"
#version 450
layout(location = 0) in vec2 v_Uv;
layout(location = 0) out vec4 o_Target;
layout(set = 2, binding = 0) uniform TimeUniform_value {
float time;
};
void main() {
float speed = 0.7;
float translation = sin(time * speed);
float percentage = 0.6;
float threshold = v_Uv.x + translation * percentage;
vec3 red = vec3(1., 0., 0.);
vec3 blue = vec3(0., 0., 1.);
vec3 mixed = mix(red, blue, threshold);
o_Target = vec4(mixed, 1.0);
}
"#;

fn setup(
mut commands: Commands,
mut pipelines: ResMut<Assets<PipelineDescriptor>>,
mut shaders: ResMut<Assets<Shader>>,
mut meshes: ResMut<Assets<Mesh>>,
mut render_graph: ResMut<RenderGraph>,
) {
// Create a new shader pipeline.
let pipeline_handle = pipelines.add(PipelineDescriptor::default_config(ShaderStages {
vertex: shaders.add(Shader::from_glsl(ShaderStage::Vertex, VERTEX_SHADER)),
fragment: Some(shaders.add(Shader::from_glsl(ShaderStage::Fragment, FRAGMENT_SHADER))),
}));

// Add a `RenderResourcesNode` to our `RenderGraph`. This will bind `TimeComponent` to our shader.
render_graph.add_system_node(
"time_uniform",
RenderResourcesNode::<TimeUniform>::new(true),
);

// Add a `RenderGraph` edge connecting our new "time_component" node to the main pass node. This
// ensures that "time_component" runs before the main pass.
render_graph
.add_node_edge("time_uniform", base::node::MAIN_PASS)
.unwrap();

// Spawn a quad and insert the `TimeComponent`.
commands
.spawn_bundle(MeshBundle {
mesh: meshes.add(Mesh::from(shape::Quad::new(Vec2::new(5.0, 5.0)))),
render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new(
pipeline_handle,
)]),
transform: Transform::from_xyz(0.0, 0.0, 0.0),
..Default::default()
})
.insert(TimeUniform { value: 0.0 });

// Spawn a camera.
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
}

/// In this system we query for the `TimeComponent` and global `Time` resource, and set `time.seconds_since_startup()`
/// as the `value` of the `TimeComponent`. This value will be accessed by the fragment shader and used
/// to animate the shader.
fn animate_shader(time: Res<Time>, mut query: Query<&mut TimeUniform>) {
let mut time_uniform = query.single_mut().unwrap();
time_uniform.value = time.seconds_since_startup() as f32;
}

0 comments on commit 9b7ed18

Please sign in to comment.