From 8dc040cf08e51caefe1626cfcf221e7c7fe83189 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 4 Jan 2024 21:30:12 -0800 Subject: [PATCH] Added animations to objects and ui elements --- crates/render/shaders/include/animate.wgsl | 26 ++++++++ crates/render/shaders/object.wgsl | 33 +--------- crates/render/shaders/particle.wgsl | 75 ++++++---------------- crates/render/shaders/starfield.wgsl | 3 +- crates/render/shaders/ui.wgsl | 5 +- crates/render/src/gpustate.rs | 20 ++++-- 6 files changed, 68 insertions(+), 94 deletions(-) create mode 100644 crates/render/shaders/include/animate.wgsl diff --git a/crates/render/shaders/include/animate.wgsl b/crates/render/shaders/include/animate.wgsl new file mode 100644 index 0000000..58f86df --- /dev/null +++ b/crates/render/shaders/include/animate.wgsl @@ -0,0 +1,26 @@ +// Pick frame of animation from an instance. +// +// This function assumes that the uniform header has been loaded, +// and that `InstanceInput` contains a field `texture_index` +fn animate(instance: InstanceInput, age: f32) -> u32 { + + let idx = instance.texture_index; + let len = sprites.data[idx].frame_count; + let rep = sprites.data[idx].repeatmode; + let fps = sprites.data[idx].fps; + var frame: u32 = u32(0); + + if rep == u32(1) { // Repeat + let x = age / fps; + let m = f32(len); + // x fmod m + frame = u32(x - floor(x / m) * m); + } else { // Once + frame = u32(min( + (age / fps), + f32(len) - 1.0 + )); + } + + return frame + sprites.data[idx].first_frame; +} \ No newline at end of file diff --git a/crates/render/shaders/object.wgsl b/crates/render/shaders/object.wgsl index 50701ab..3b3bfa8 100644 --- a/crates/render/shaders/object.wgsl +++ b/crates/render/shaders/object.wgsl @@ -26,39 +26,12 @@ var texture_array: binding_array>; var sampler_array: binding_array; -fn fmod(x: f32, m: f32) -> f32 { - return x - floor(x / m) * m; -} +// INCLUDE: animate.wgsl -// Returns texture index // TODO: random age -// TODO: preprocessor include function -// TODO: packed location config, better error +// TODO: configure packed asset location, better error // TODO: bounce animations // TODO: animation randomness? -fn animate(instance: InstanceInput) -> u32 { - // Age doesn't make sense here, so arbitrarily pick zero. - let age = global.current_time.x; - let len = sprites.data[instance.texture_index].frame_count; - let rep = sprites.data[instance.texture_index].repeatmode; - let fps = sprites.data[instance.texture_index].fps; - var frame: u32 = u32(0); - if rep == u32(1) { - // Repeat - frame = u32(fmod( - (age / fps), - f32(len) - )); - } else { - // Once - frame = u32(min( - (age / fps), - f32(len) - 1.0 - )); - } - - return frame + sprites.data[instance.texture_index].first_frame; -} @vertex fn vertex_main( @@ -76,7 +49,7 @@ fn vertex_main( var out: VertexOutput; out.position = transform * vec4(vertex.position, 1.0); - let t = atlas.data[animate(instance)]; + let t = atlas.data[animate(instance, global.current_time.x)]; out.texture_index = u32(0); out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/shaders/particle.wgsl b/crates/render/shaders/particle.wgsl index 29289e5..403dba2 100644 --- a/crates/render/shaders/particle.wgsl +++ b/crates/render/shaders/particle.wgsl @@ -29,33 +29,7 @@ var texture_array: binding_array>; var sampler_array: binding_array; -// Returns texture index -fn animate(instance: InstanceInput) -> u32 { - let age = global.current_time.x - instance.created; - let len = sprites.data[instance.texture_index].frame_count; - let rep = sprites.data[instance.texture_index].repeatmode; - let fps = sprites.data[instance.texture_index].fps; - var frame: u32 = u32(0); - if rep == u32(1) { - // Repeat - frame = u32(fmod( - (age / fps), - f32(len) - )); - } else { - // Once - frame = u32(min( - (age / fps), - f32(len) - 1.0 - )); - } - - return frame + sprites.data[instance.texture_index].first_frame; -} - -fn fmod(x: f32, m: f32) -> f32 { - return x - floor(x / m) * m; -} +// INCLUDE: animate.wgsl @vertex fn vertex_main( @@ -66,51 +40,25 @@ fn vertex_main( var out: VertexOutput; out.texture_coords = vertex.texture_coords; + // Skip expired particles if instance.expires < global.current_time.x { out.texture_index = u32(0); + // Draw off screen out.position = vec4(2.0, 2.0, 0.0, 1.0); return out; } let age = global.current_time.x - instance.created; - let len = sprites.data[instance.texture_index].frame_count; - let rep = sprites.data[instance.texture_index].repeatmode; - let fps = sprites.data[instance.texture_index].fps; - var frame: u32 = u32(0); - if rep == u32(1) { - // Repeat - frame = u32(fmod( - (age / fps), - f32(len) - )); - } else { - // Once - frame = u32(min( - (age / fps), - f32(len) - 1.0 - )); - } - - let t = atlas.data[animate(instance)]; - out.texture_index = u32(0); - out.texture_coords = vec2(t.xpos, t.ypos); - if vertex.texture_coords.x == 1.0 { - out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); - } - if vertex.texture_coords.y == 1.0 { - out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); - } - + // Apply transformations let rotation = mat2x2(instance.rotation_0, instance.rotation_1); - var scale: f32 = instance.size / global.camera_zoom.x; var pos: vec2 = vec2(vertex.position.x, vertex.position.y); - pos = pos * vec2( sprites.data[instance.texture_index].aspect * scale / global.window_aspect.x, scale ); + pos = rotation * pos; var ipos: vec2 = ( @@ -125,6 +73,19 @@ fn vertex_main( ); out.position = vec4(pos, 0.0, 1.0); + + + // Compute texture coordinates + let t = atlas.data[animate(instance, age)]; + out.texture_index = u32(0); + out.texture_coords = vec2(t.xpos, t.ypos); + if vertex.texture_coords.x == 1.0 { + out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y); + } + if vertex.texture_coords.y == 1.0 { + out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height); + } + return out; } diff --git a/crates/render/shaders/starfield.wgsl b/crates/render/shaders/starfield.wgsl index d872a50..599c2dc 100644 --- a/crates/render/shaders/starfield.wgsl +++ b/crates/render/shaders/starfield.wgsl @@ -25,7 +25,6 @@ var sampler_array: binding_array; - fn fmod(x: vec2, m: f32) -> vec2 { return x - floor(x / m) * m; } @@ -108,6 +107,8 @@ fn vertex_main( out.position = vec4(pos, 0.0, 1.0) * instance.position.z; + + // Starfield sprites may not be animated let i = sprites.data[global.starfield_sprite.x].first_frame; let t = atlas.data[i]; out.texture_index = u32(0); diff --git a/crates/render/shaders/ui.wgsl b/crates/render/shaders/ui.wgsl index 765369a..55b3f5c 100644 --- a/crates/render/shaders/ui.wgsl +++ b/crates/render/shaders/ui.wgsl @@ -27,6 +27,7 @@ var texture_array: binding_array>; var sampler_array: binding_array; +// INCLUDE: animate.wgsl @vertex @@ -46,8 +47,8 @@ fn vertex_main( out.position = transform * vec4(vertex.position, 1.0); out.color_transform = instance.color_transform; - let i = sprites.data[instance.texture_index].first_frame; - let t = atlas.data[i]; + // Pick texture frame + let t = atlas.data[animate(instance, global.current_time.x)]; out.texture_index = u32(0); out.texture_coords = vec2(t.xpos, t.ypos); if vertex.texture_coords.x == 1.0 { diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index 810f593..f93d47f 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -60,17 +60,29 @@ struct VertexBuffers { particle: Rc, } -/// Preprocess shader files +/// Basic wgsl preprocesser fn preprocess_shader( shader: &str, global_uniform: &GlobalUniform, global_uniform_group: u32, ) -> String { - // Insert common headers - shader.replace( + // Insert dynamically-generated global definitions + let shader = shader.replace( "// INCLUDE: global uniform header", &global_uniform.shader_header(global_uniform_group), - ) + ); + + // Insert common functions + let shader = shader.replace( + "// INCLUDE: animate.wgsl", + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/include/", + "animate.wgsl" + )), + ); + + return shader; } impl GPUState {