From 7906a0303109bfeb0c2e41e351ce21464ff1e778 Mon Sep 17 00:00:00 2001 From: Torstein Grindvik <52322338+torsteingrindvik@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:35:45 +0200 Subject: [PATCH] Bug fixes, plugin passthrough, documentation updates.. (#55) - Add limitations section in readme - Add breakout Bevy example showcase in readme - Fix LUT shader: Green and blue channels were swapped - Allow added plugins to be skipped via a passthrough functionality - Add LUTs when not feature dev such that external users may use them. Also list the available ones in docs. - Add missing raindrops tga when not feature dev - Fix minor doc issues: - Copy pasted text - Hide macros not meant to be used externally Signed-off-by: Torstein Grindvik --- .github/workflows/basics.yml | 30 ++-- Cargo.toml | 1 + README.md | 37 ++++- assets/shaders/blur.wgsl | 13 +- assets/shaders/chromatic-aberration.wgsl | 13 +- assets/shaders/flip.wgsl | 15 +- assets/shaders/lut.wgsl | 23 +-- assets/shaders/masks.wgsl | 14 +- assets/shaders/pixelate.wgsl | 14 +- assets/shaders/raindrops.wgsl | 14 +- assets/shaders/wave.wgsl | 14 +- examples/examples_common.rs | 22 +-- examples/lut/main.rs | 74 +++------ examples/pixelate/main.rs | 12 +- examples/raindrops/main.rs | 11 +- examples/trex/main.rs | 59 +++++++ examples/underwater/main.rs | 2 +- src/image.rs | 2 +- src/image/blur.rs | 55 ++++++- src/image/chromatic_aberration.rs | 53 ++++++- src/image/flip.rs | 46 +++++- src/image/lut/mod.rs | 186 ++++++++++++++++++++--- src/image/lut/neutral.png | Bin 4328 -> 0 bytes src/image/mask.rs | 46 +++++- src/image/pixelate.rs | 52 ++++++- src/image/{rainrops.rs => raindrops.rs} | 82 ++++++++-- src/image/wave.rs | 55 ++++++- src/lib.rs | 47 +++++- src/passthrough.wgsl | 19 +++ 29 files changed, 786 insertions(+), 225 deletions(-) create mode 100644 examples/trex/main.rs delete mode 100644 src/image/lut/neutral.png rename src/image/{rainrops.rs => raindrops.rs} (67%) create mode 100644 src/passthrough.wgsl diff --git a/.github/workflows/basics.yml b/.github/workflows/basics.yml index 3d09871..a31f0ba 100644 --- a/.github/workflows/basics.yml +++ b/.github/workflows/basics.yml @@ -41,21 +41,21 @@ jobs: command: check args: --all-targets --features dev - test: - name: Test Suite - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Install alsa and udev - run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev - - uses: actions-rs/cargo@v1 - with: - command: test + # test: + # name: Test Suite + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + # - uses: actions-rs/toolchain@v1 + # with: + # profile: minimal + # toolchain: stable + # override: true + # - name: Install alsa and udev + # run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + # - uses: actions-rs/cargo@v1 + # with: + # command: test fmt: name: Rustfmt diff --git a/Cargo.toml b/Cargo.toml index bc198c1..45ef8d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ alsa = "=0.6.0" [dev-dependencies] bevy = { git = "https://github.com/bevyengine/bevy", features = ["tga"] } +# bevy = { path = "../bevy", features = ["tga"] } color-eyre = "0.6" image = "0.24" diff --git a/README.md b/README.md index 68a4e49..4742517 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,23 @@ This crate has an assortment of effects easily applied to Bevy apps via plugins. -## Getting started +Here is a showcase made by adding some visual effects to Bevy's breakout example. +Note that the game itself is not modified except visually. + +[Breakout Video](https://user-images.githubusercontent.com/52322338/198832540-a1a2fa00-8c75-4f55-a3e7-b4ce18fef958.mp4) + +The effects added in the above are: -### Bevy compatibility +- A raindrop effect (due to effects applied after this one it gives the impression of static noise across the screen). +- Chromatic aberration. +- Waves. This effect causes the general "waviness" seen as well as the collision shake effect. +- A CRT mask. This changes the edges of the screen to resemeble a CRT. +- Pixelation. This pixelates the image. The demo increases this effect slightly each time a collision happens, but it's a bit hard to notice due to the short length of the video. +- Color grading. A color lookup texture is applied, which changes the color output accordingly. + +Scroll down to see videos of the examples in contained in this repo. + +## Bevy compatibility When adding this crate as a dependency in your project, the Bevy version you use will need to match up according to the @@ -14,7 +28,7 @@ following table: |---|---| |0.1 (unreleased)|0.9 (unreleased)| -### In your code +## Getting started The general strategy is: @@ -48,9 +62,15 @@ fn update(mut flip: ResMut) { } ``` +## Limitations + +- You can only use a single camera as the source for effects. +- You cannot change the order of applied effects at runtime- this is decided by plugin insertion order when making the `App`. +- You can toggle effects off/on at runtime- but the shaders will still run. They simply pass through the input image to the output, but this requires a texture sample. Therefore there is likely a slight performance cost for added-but-disabled effects. + ## Examples -All videos are captured from running the [examples](https://github.com/torsteingrindvik/bevy-vfx-bag/tree/main/examples). +All videos below are captured from running the [examples](https://github.com/torsteingrindvik/bevy-vfx-bag/tree/main/examples). Do `cargo r --example` in this repository to get a list of examples you may run. Some examples use keyboard/mouse input to change parameters at runtime as well. @@ -123,6 +143,14 @@ Not shown is changing the radius, and changing the color of the vignette. [Vignette Example Video](https://user-images.githubusercontent.com/52322338/195917174-0be12446-d527-4d81-8e0d-24370b8bdd03.mp4) +### T-Rex + +Shows another use of the wave effect. +By having a violent wave effect we can simulate something like the earth shaking due to something +big approaching by toggling the effect in intervals. + +[T-Rex Example Video](https://user-images.githubusercontent.com/52322338/198832244-4898bbe9-4b24-4ddb-a5d5-665f3ecc71e3.mp4) + ### Underwater Underwater (ish) effect. @@ -145,4 +173,3 @@ which might be achieved by having a high number of waves at high speed with low and quickly dampening those parameters to zero so the effect ends. [Wave Example Video](https://user-images.githubusercontent.com/52322338/195917192-461fd2a1-8bdf-4671-bfce-a1182de41fb1.mp4) - diff --git a/assets/shaders/blur.wgsl b/assets/shaders/blur.wgsl index e9c67b5..48b5598 100644 --- a/assets/shaders/blur.wgsl +++ b/assets/shaders/blur.wgsl @@ -37,13 +37,10 @@ fn s_blurred(uv: vec2) -> vec3 { ; } -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv = coords_to_viewport_uv(position.xy, view.viewport); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { let original = s(uv); let blurred = s_blurred(uv); @@ -51,3 +48,5 @@ fn fragment( return vec4(output, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/chromatic-aberration.wgsl b/assets/shaders/chromatic-aberration.wgsl index 7ec02d1..836cf7b 100644 --- a/assets/shaders/chromatic-aberration.wgsl +++ b/assets/shaders/chromatic-aberration.wgsl @@ -21,13 +21,10 @@ struct ChromaticAberration { @group(1) @binding(2) var chromatic_aberration: ChromaticAberration; -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv = coords_to_viewport_uv(position.xy, view.viewport); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { let out = vec3( textureSample(texture, our_sampler, uv + (chromatic_aberration.dir_r * chromatic_aberration.magnitude_r)).r, textureSample(texture, our_sampler, uv + (chromatic_aberration.dir_g * chromatic_aberration.magnitude_g)).g, @@ -36,3 +33,5 @@ fn fragment( return vec4(out, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/flip.wgsl b/assets/shaders/flip.wgsl index ba264dd..be56f3d 100644 --- a/assets/shaders/flip.wgsl +++ b/assets/shaders/flip.wgsl @@ -14,13 +14,12 @@ struct FlipUniform { @group(1) @binding(2) var flip: FlipUniform; -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv_unaltered = coords_to_viewport_uv(position.xy, view.viewport); - let uv = abs(vec2(flip.x, flip.y) - uv_unaltered); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { + let uv = abs(vec2(flip.x, flip.y) - uv); return vec4(textureSample(texture, our_sampler, uv).rgb, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/lut.wgsl b/assets/shaders/lut.wgsl index 92d253f..f1a1160 100644 --- a/assets/shaders/lut.wgsl +++ b/assets/shaders/lut.wgsl @@ -13,27 +13,32 @@ var lut: texture_3d; @group(1) @binding(3) var lut_sampler: sampler; -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv = coords_to_viewport_uv(position.xy, view.viewport); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { // https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-24-using-lookup-tables-accelerate-color // I'm honestly not sure why this is necessary, I don't quite follow the reasoning. // But the neutral LUT seems indistinguishable from the original input texture // when this is used. Great! let half_texel = vec3(1.0 / 64. / 2.); - let raw_color = textureSample(texture, our_sampler, uv).rgb; + // Notice the ".rbg". + // If we sample the LUT using ".rgb" instead, + // the way the 3D texture is loaded will mean the + // green and blue colors are swapped. + // This mitigates that. + let raw_color = textureSample(texture, our_sampler, uv).rbg; + var out = textureSample(lut, lut_sampler, raw_color + half_texel).rgb; #ifdef SPLIT_VERTICALLY - if (uv.x > 0.5) { + if (uv.x < 0.5) { out = raw_color; } #endif return vec4(out, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/masks.wgsl b/assets/shaders/masks.wgsl index 2d385f3..b045033 100644 --- a/assets/shaders/masks.wgsl +++ b/assets/shaders/masks.wgsl @@ -76,16 +76,12 @@ fn vignette(uv: vec2) -> f32 { } #endif -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - - let uv = coords_to_viewport_uv(position.xy, view.viewport); +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { let t = textureSample(texture, our_sampler, uv); - #ifdef SQUARE let mask = square(uv); #endif @@ -98,3 +94,5 @@ fn fragment( return vec4(t.rgb * mask, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/pixelate.wgsl b/assets/shaders/pixelate.wgsl index 5020293..5218f34 100644 --- a/assets/shaders/pixelate.wgsl +++ b/assets/shaders/pixelate.wgsl @@ -13,13 +13,11 @@ struct Pixelate { @group(1) @binding(2) var pixelate: Pixelate; -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - var uv = coords_to_viewport_uv(position.xy, view.viewport); - uv += 0.5; +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { + var uv = uv + 0.5; let width_height_over_block_size = view.viewport.zw / max(1.0, pixelate.block_size); @@ -32,3 +30,5 @@ fn fragment( return vec4(textureSample(texture, our_sampler, uv).rgb, 1.0); } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/raindrops.wgsl b/assets/shaders/raindrops.wgsl index 3f03be6..1634b45 100644 --- a/assets/shaders/raindrops.wgsl +++ b/assets/shaders/raindrops.wgsl @@ -32,13 +32,10 @@ fn animation(raindrops_b: f32) -> f32 { return fract(raindrops_b - (globals.time * raindrops.time_scaling)); } -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv = coords_to_viewport_uv(position.xy, view.viewport); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { // Raindrops texture wraps. // Make aspect-ratio independent UV coords. let uv_aspect_fixed = vec2(uv.x * view.viewport.z / view.viewport.w, uv.y); @@ -65,4 +62,7 @@ fn fragment( let masked_norms = mask * offset; return vec4(textureSample(texture, our_sampler, uv + masked_norms).rgb, 1.0); + } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/assets/shaders/wave.wgsl b/assets/shaders/wave.wgsl index ca17810..2b12423 100644 --- a/assets/shaders/wave.wgsl +++ b/assets/shaders/wave.wgsl @@ -21,13 +21,10 @@ struct Wave { @group(1) @binding(2) var wave: Wave; -@fragment -fn fragment( - @builtin(position) position: vec4, - #import bevy_sprite::mesh2d_vertex_output -) -> @location(0) vec4 { - let uv = coords_to_viewport_uv(position.xy, view.viewport); - +fn fragment_impl( + position: vec4, + uv: vec2 +) -> vec4 { let pi_uv = PI * uv; let pi_time = PI * globals.time; @@ -37,4 +34,7 @@ fn fragment( let uv_displaced = vec2(uv.x + offset_x, uv.y + offset_y); return vec4(textureSample(texture, our_sampler, uv_displaced).rgb, 1.0); + } + +#import bevy_vfx_bag::post_processing_passthrough diff --git a/examples/examples_common.rs b/examples/examples_common.rs index 5b085b3..98a2bb2 100644 --- a/examples/examples_common.rs +++ b/examples/examples_common.rs @@ -20,13 +20,16 @@ impl Default for ExampleText { impl Plugin for SaneDefaultsPlugin { fn build(&self, app: &mut App) { - app.insert_resource(AssetServerSettings { - watch_for_changes: true, - ..default() - }) - .init_resource::() - .add_plugins(DefaultPlugins) - .add_system(bevy::window::close_on_esc); + app.init_resource::() + .add_plugins( + DefaultPlugins + .set(AssetPlugin { + watch_for_changes: true, + ..default() + }) + .set(ImagePlugin::default_nearest()), + ) + .add_system(bevy::window::close_on_esc); } } @@ -60,8 +63,7 @@ pub(crate) struct ShouldAdd3dCameraBundle(bool); impl Plugin for ShapesExamplePlugin { fn build(&self, app: &mut App) { - app.insert_resource(ImageSettings::default_nearest()) - .insert_resource(ShouldAdd3dCameraBundle(self.add_3d_camera_bundle)) + app.insert_resource(ShouldAdd3dCameraBundle(self.add_3d_camera_bundle)) .add_plugin(FrameTimeDiagnosticsPlugin::default()) .add_startup_system(shapes::setup) .add_startup_system(ui::setup) @@ -223,7 +225,7 @@ mod ui { "hello\nbevy!", TextStyle { font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: 40.0, + font_size: 30.0, color: Color::WHITE, }, ) // Set the alignment of the Text diff --git a/examples/lut/main.rs b/examples/lut/main.rs index ed4d829..0b38051 100644 --- a/examples/lut/main.rs +++ b/examples/lut/main.rs @@ -1,23 +1,13 @@ #[path = "../examples_common.rs"] mod examples_common; -use bevy::{prelude::*, utils::HashMap}; +use bevy::prelude::*; use bevy_vfx_bag::{ - image::lut::{Lut, Lut3d, LutPlugin}, + image::lut::{Lut, LutPassthrough, LutPlugin, Luts}, BevyVfxBagPlugin, PostProcessingInput, }; -// Load the LUT presets from disk via the asset server, -// and give them a readable name. -// When the event fires that the image is actually loaded, -// move it into the vector of LUTs ready for use. -#[derive(Debug, Resource, Default)] -struct LutsState { - handles: HashMap, &'static str>, - ready: Vec<(&'static str, Lut3d)>, -} - fn main() { let mut app = App::new(); @@ -25,14 +15,12 @@ fn main() { .add_plugin(examples_common::ShapesExamplePlugin::without_3d_camera()) .add_plugin(BevyVfxBagPlugin) .add_plugin(LutPlugin) - .init_resource::() .add_startup_system(startup) - .add_system(make_luts) .add_system(update) .run(); } -fn startup(mut commands: Commands, assets: Res, mut luts: ResMut) { +fn startup(mut commands: Commands) { commands .spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 6., 12.0) @@ -40,60 +28,41 @@ fn startup(mut commands: Commands, assets: Res, mut luts: ResMut>, - mut assets: ResMut>, - mut luts: ResMut, -) { - for ev in ev_asset.iter() { - if let AssetEvent::Created { handle } = ev { - if let Some(lut_name) = luts.handles.remove(handle) { - luts.ready - .push((lut_name, Lut3d::from_image(&mut assets, handle))); - } - } - } } // Cycle through some preset LUTs. fn update( - time: Res