-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
367 additions
and
24 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#import bevy_sprite::{ | ||
mesh2d_vertex_output::VertexOutput, | ||
mesh2d_view_bindings::globals, | ||
} | ||
|
||
|
||
@group(2) @binding(0) var base_color_texture: texture_2d<f32>; | ||
@group(2) @binding(1) var base_color_sampler: sampler; | ||
@group(2) @binding(2) var<uniform> index: vec4<f32>; | ||
@group(2) @binding(3) var<uniform> distance_to_player: vec4<f32>; | ||
|
||
|
||
fn Hash12(t: f32) -> vec2<f32> { | ||
let x = fract(sin(t*748.32)*367.34); | ||
let y = fract(sin((t+x)*623.785)*292.45); | ||
|
||
return vec2(x,y)-.5; | ||
} | ||
|
||
fn Hash12Polar(t: f32) -> vec2<f32> { | ||
let a = fract(sin(t*748.31)*367.34)*6.2832; | ||
let d = fract(sin((t+a)*623.785)*292.45); | ||
|
||
return vec2<f32>(cos(a),sin(a))*d; | ||
} | ||
|
||
@fragment | ||
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> { | ||
let atlas_width = 1024.0; | ||
let atlas_height = 512.0; | ||
let sprite_size = 128.0; | ||
|
||
var texture = textureSample( | ||
base_color_texture, | ||
base_color_sampler, | ||
vec2<f32>((mesh.uv.x + index.x) * sprite_size / atlas_width, (mesh.uv.y + index.y) * sprite_size / atlas_height) | ||
); | ||
|
||
let max_distance = 750.0; | ||
|
||
if distance_to_player.x < max_distance { | ||
// Adapted from https://www.shadertoy.com/view/7sjfRy | ||
for(var j = 0; j < 3; j++){ | ||
for(var i = 0; i < 100; i++){ | ||
|
||
let t = fract(globals.time); | ||
let bright = mix(0.002 * (1.0 - distance_to_player.x / max_distance), 0.001, smoothstep(0.025, 0.0, t) ); | ||
let dir = Hash12Polar(f32(i)+1.); | ||
let dist = distance(mesh.uv - vec2(0.5,0.5) - dir*t, vec2(0, 0)+(Hash12Polar(f32(j*i))/2.)); | ||
|
||
if bright / dist > 0.1 { | ||
texture.r = bright / dist * 2.0; | ||
texture.g = bright / dist / 2.0; | ||
texture.b = bright / dist / 2.0; | ||
texture.a = 1.0; | ||
} | ||
} | ||
} | ||
|
||
} | ||
|
||
return texture; | ||
} |
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,10 @@ | ||
# ✍️ Exercises | ||
|
||
## Jumping | ||
|
||
Let's add a shader displaying an effect when jumping. | ||
|
||
Tips: | ||
* Use the time the player started jumping in the material | ||
* Use the current velocity in the material | ||
* Try to find a cool effect on <https://www.shadertoy.com> and port it |
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,186 @@ | ||
# Dynamic Flag | ||
|
||
We'll build a first shader adding some particles to the flag depending on how close the player is. | ||
|
||
## Custom GPU type | ||
|
||
First step is to declare the data we'll send to the GPU: | ||
|
||
```rust | ||
# extern crate bevy; | ||
# use bevy::{ | ||
# prelude::*, | ||
# render::render_resource::{AsBindGroup, ShaderRef}, | ||
# sprite::{AlphaMode2d, Material2d, Material2dPlugin}, | ||
# }; | ||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] | ||
pub struct FlagMaterial { | ||
#[texture(0)] | ||
#[sampler(1)] | ||
pub atlas: Handle<Image>, | ||
#[uniform(2)] | ||
pub index: Vec4, | ||
#[uniform(3)] | ||
pub distance: Vec4, | ||
} | ||
``` | ||
|
||
By deriving the [`AsBindGroup`](https://docs.rs/bevy/0.15.0-rc.2/bevy/render/render_resource/trait.AsBindGroup.html) trait and annotating the field of the struct, Bevy will be able to know how to transform the data from Rust type to what is expected by the GPU: | ||
* `atlas` has the handle to the spritesheet | ||
* `index` is the index of the sprite in the spritesheet. Bevy uses a single `u32` for that, and get the number of rows and columns from the [`TextureAtlasLayout`](https://docs.rs/bevy/0.15.0-rc.2/bevy/prelude/struct.TextureAtlasLayout.html). We'll do simpler and hard code some values, and use `(i, j)` coordinatesto specify which sprite to use | ||
* `distance` is the distance between the flag and the player | ||
|
||
<div class="warning"> | ||
|
||
`index` will have a `Vec2`, and `distance` a `f32`, but they are both defined as `Vec4`. This is for WebGL2 compatibility, where types must be aligned on 16 bytes. | ||
|
||
The two strategies to solve that are padding and packing. Padding is using bigger types than necessary and wasting memory, packing is grouping fields that have separate meaning in a single type. | ||
|
||
This workshop use padding as it's easier to read and the material is only used once, so doesn't waste a lot of memory. | ||
|
||
</div> | ||
|
||
## Custom Material | ||
|
||
Next is to define the shader that will be used to render the data. This is done by implementing the [`Material2d`](https://docs.rs/bevy/0.15.0-rc.2/bevy/sprite/trait.Material2d.html) trait: | ||
|
||
```rust | ||
# extern crate bevy; | ||
# use bevy::{ | ||
# prelude::*, | ||
# render::render_resource::{AsBindGroup, ShaderRef}, | ||
# sprite::{AlphaMode2d, Material2d, Material2dPlugin}, | ||
# }; | ||
# #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] | ||
# pub struct FlagMaterial {} | ||
impl Material2d for FlagMaterial { | ||
fn fragment_shader() -> ShaderRef { | ||
"flag_shader.wgsl".into() | ||
} | ||
|
||
fn alpha_mode(&self) -> AlphaMode2d { | ||
AlphaMode2d::Blend | ||
} | ||
} | ||
``` | ||
|
||
The trait has more customisation than used here, and use sane defaults. By just using a string for the fragment shader, Bevy will load the file specified from the asset folder. | ||
|
||
This is a basic shader that will display the sprite selected by the `index` from a sprite sheet: | ||
|
||
```wgsl | ||
#import bevy_sprite::{ | ||
mesh2d_vertex_output::VertexOutput, | ||
mesh2d_view_bindings::globals, | ||
} | ||
@group(2) @binding(0) var base_color_texture: texture_2d<f32>; | ||
@group(2) @binding(1) var base_color_sampler: sampler; | ||
@group(2) @binding(2) var<uniform> index: vec4<f32>; | ||
@group(2) @binding(3) var<uniform> distance_to_player: vec4<f32>; | ||
@fragment | ||
fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> { | ||
let atlas_width = 1024.0; | ||
let atlas_height = 512.0; | ||
let sprite_size = 128.0; | ||
var texture = textureSample( | ||
base_color_texture, | ||
base_color_sampler, | ||
vec2<f32>((mesh.uv.x + index.x) * sprite_size / atlas_width, (mesh.uv.y + index.y) * sprite_size / atlas_height) | ||
); | ||
return texture; | ||
} | ||
``` | ||
|
||
Bevy has some extensions to WGSL to allow imports and expose some helpful features. | ||
|
||
Variables with the `@group(2)` will match the bind group declared on Rust side. | ||
|
||
## Using the Material | ||
|
||
Our new material must be added to Bevy before it can be used. This can be done in a plugin: | ||
|
||
```rust | ||
# extern crate bevy; | ||
# use bevy::{ | ||
# prelude::*, | ||
# render::render_resource::{AsBindGroup, ShaderRef}, | ||
# sprite::{AlphaMode2d, Material2d, Material2dPlugin}, | ||
# }; | ||
# #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] | ||
# pub struct FlagMaterial {} | ||
# impl Material2d for FlagMaterial {} | ||
pub struct FlagPlugin; | ||
|
||
impl Plugin for FlagPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_plugins(Material2dPlugin::<FlagMaterial>::default()); | ||
} | ||
} | ||
``` | ||
|
||
Then we can replace `Sprite` for the flag with our new material: | ||
|
||
```rust | ||
# extern crate bevy; | ||
# use bevy::{ | ||
# prelude::*, | ||
# render::render_resource::{AsBindGroup, ShaderRef}, | ||
# sprite::{AlphaMode2d, Material2d, Material2dPlugin}, | ||
# }; | ||
# #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] | ||
# pub struct FlagMaterial { | ||
# #[texture(0)] | ||
# #[sampler(1)] | ||
# pub atlas: Handle<Image>, | ||
# #[uniform(2)] | ||
# pub index: Vec4, | ||
# #[uniform(3)] | ||
# pub distance: Vec4, | ||
# } | ||
# impl Material2d for FlagMaterial {} | ||
# enum Tile { Flag } | ||
# #[derive(Component)] | ||
# struct Flag; | ||
# #[derive(Event)] | ||
# struct ReachedFlag; | ||
# fn reached_flag(_trigger: Trigger<ReachedFlag>) {} | ||
# struct GameAssets { | ||
# items_image: Handle<Image>, | ||
# items_layout: Handle<TextureAtlasLayout>, | ||
# } | ||
# #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, States, Default)] | ||
# enum GameState { #[default] Game } | ||
fn display_tile( | ||
// ... | ||
meshes: &mut Assets<Mesh>, | ||
flag_materials: &mut Assets<FlagMaterial>, | ||
) { | ||
# let commands: Commands = unimplemented!(); | ||
# let assets: GameAssets = unimplemented!(); | ||
# let (x, y) = (0.0, 0.0); | ||
# let tile = Tile::Flag; | ||
match tile { | ||
// ... | ||
Tile::Flag => { | ||
commands | ||
.spawn(( | ||
Mesh2d(meshes.add(Rectangle::default())), | ||
MeshMaterial2d(flag_materials.add(FlagMaterial { | ||
atlas: assets.items_image.clone(), | ||
index: Vec4::new(0.0, 1.0, 0.0, 0.0), | ||
distance: Vec4::ZERO, | ||
})), | ||
Transform::from_xyz(x, y, 1.0).with_scale(Vec3::splat(0.5) * 128.0), | ||
StateScoped(GameState::Game), | ||
Flag, | ||
)) | ||
.observe(reached_flag); | ||
} | ||
// ... | ||
} | ||
} | ||
``` |
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,8 @@ | ||
# Visual Effects | ||
|
||
Visual effects can help your game pop up. This is commonly done with shaders, which are programs that execute on the GPU. The best languages to write them in Bevy is the [WebGPU Shading Language](https://www.w3.org/TR/WGSL/), and it will be translated as needed by the platform on which the application is running. | ||
|
||
Bevy offers several abstractions to render things on screen: | ||
* Directly using images or colors or texture atlas, which is what we've been doing until now. The shaders are built-in Bevy, and use as many optimisation as possible at the cost of customisation. | ||
* Custom materials, which we'll explore in this section. For 2d, you'll need to implement the [`Material2d`](https://docs.rs/bevy/0.15.0-rc.2/bevy/sprite/trait.Material2d.html) trait. | ||
* Lower level abstractions, down to complete control on the whole rendering pipeline. This won't be in this workshop. |
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,16 @@ | ||
# Progress Report | ||
|
||
## What You've learned | ||
|
||
* Defining a custom material | ||
* With the [`AsBindGroup`](https://docs.rs/bevy/0.15.0-rc.2/bevy/render/render_resource/trait.AsBindGroup.html) derive and its attributes to handle data transfer to the GPU | ||
* Implementing the [`Material2d`](https://docs.rs/bevy/0.15.0-rc.2/bevy/sprite/trait.Material2d.html) trait to define the shader | ||
* And some basic WGSL | ||
* And using that material | ||
* Adding it to the app with the [`Material2dPlugin`](https://docs.rs/bevy/0.15.0-rc.2/bevy/sprite/struct.Material2dPlugin.html) | ||
* With the [`Mesh2d`](https://docs.rs/bevy/0.15.0-rc.2/bevy/prelude/struct.Mesh2d.html) component to define the shape | ||
* And the [`MeshMaterial2d`](https://docs.rs/bevy/0.15.0-rc.2/bevy/prelude/struct.MeshMaterial2d.html) component to define the material | ||
|
||
## Going Further | ||
|
||
Shaders and rendering is a *very big* domain. |
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,34 @@ | ||
use bevy::{ | ||
prelude::*, | ||
render::render_resource::{AsBindGroup, ShaderRef}, | ||
sprite::{AlphaMode2d, Material2d, Material2dPlugin}, | ||
}; | ||
|
||
pub struct FlagPlugin; | ||
|
||
impl Plugin for FlagPlugin { | ||
fn build(&self, app: &mut App) { | ||
app.add_plugins(Material2dPlugin::<FlagMaterial>::default()); | ||
} | ||
} | ||
|
||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] | ||
pub struct FlagMaterial { | ||
#[texture(0)] | ||
#[sampler(1)] | ||
pub atlas: Handle<Image>, | ||
#[uniform(2)] | ||
pub index: Vec4, | ||
#[uniform(3)] | ||
pub distance: Vec4, | ||
} | ||
|
||
impl Material2d for FlagMaterial { | ||
fn fragment_shader() -> ShaderRef { | ||
"flag_shader.wgsl".into() | ||
} | ||
|
||
fn alpha_mode(&self) -> AlphaMode2d { | ||
AlphaMode2d::Blend | ||
} | ||
} |
Oops, something went wrong.