-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |