diff --git a/assets/materials/fbm.wgsl b/assets/materials/fbm.wgsl new file mode 100644 index 0000000..70806d1 --- /dev/null +++ b/assets/materials/fbm.wgsl @@ -0,0 +1,16 @@ +#import bevy_vfx_bag::fbm + +struct Material { + scale: f32, + offset_x: f32, + offset_y: f32, +}; +@group(1) @binding(0) +var material: Material; + +@fragment +fn fragment( + #import bevy_pbr::mesh_vertex_output +) -> @location(0) vec4 { + return vec4(vec3(fbm((material.scale * uv) + vec2(material.offset_x, material.offset_y))), 1.0); +} diff --git a/assets/materials/value_noise.wgsl b/assets/materials/value_noise.wgsl new file mode 100644 index 0000000..17e217f --- /dev/null +++ b/assets/materials/value_noise.wgsl @@ -0,0 +1,17 @@ +#import bevy_vfx_bag::value_noise + +struct Material { + scale: f32, + offset_x: f32, + offset_y: f32, +}; +@group(1) @binding(0) +var material: Material; + +@fragment +fn fragment( + #import bevy_pbr::mesh_vertex_output +) -> @location(0) vec4 { + let noise = value_noise((material.scale * uv) + vec2(material.offset_x, material.offset_y)); + return vec4(vec3(noise), 1.0); +} diff --git a/assets/materials/voronoi.wgsl b/assets/materials/voronoi.wgsl new file mode 100644 index 0000000..2a4de97 --- /dev/null +++ b/assets/materials/voronoi.wgsl @@ -0,0 +1,83 @@ +#import bevy_vfx_bag::math + +struct Material { + scale: f32, + offset_x: f32, + offset_y: f32, +}; +@group(1) @binding(0) +var material: Material; + +fn voronoi( + uv: vec2 +) -> f32 { + var uv = uv * material.scale; + let id = vec2(floor(uv)); + + let cell_space = -0.5 + fract(uv); + + var min_dist = 10.0; + + for (var y: i32 = -1; y <= 1; y++) { + for (var x: i32 = -1; x <= 1; x++) { + let offset = vec2(x, y); + + // Random 2D vector in range (-0.5, +0.5). + // Note that the strategy is to go from normal UVs (0,0) -> (1,1) to something scaled, like + // (0,0) -> (10,10) (for example). + // We floor that to get integer IDs. + // Then we add an offset in order to index our neighbours. + let neighbour_random_point = hash22f(vec2(id + offset)) / 2.; + + // So now we have a random 2D point which is deterministic with regards + // to the ID of the cell. + // What we care about though is how far away this point is relative to _this_ cell. + // Therefore we have to add the offset of the cell with the point in question to realize + // that distance. + // var p = sin(random2d * vec2(material.offset_x, material.offset_y)) + vec2(offset); + // var p = sin(neighbour_random_point * vec2(material.offset_x, material.offset_y)) + vec2(offset); + var p = sin(neighbour_random_point) + vec2(offset); + // var p = random2d + vec2(offset); + + // p += sin(vec2(material.offset_x, material.offset_y)) / 2.; + + let dist = length(cell_space - p); + + if (dist < min_dist) { + min_dist = dist; + } + } + } + + // min_dist = 1. - min(1., min_dist); + min_dist = smoothstep(0.31, 1.0, min_dist); + + return min_dist; +} + +@fragment +fn fragment( + #import bevy_pbr::mesh_vertex_output +) -> @location(0) vec4 { + // let v = voronoi(uv); + // return vec4(v * 0.1, v * 4., v, 1.0); + + let uv = uv * 10.; + + let xoff = sin(material.offset_x / 2.) / 2.1; + let yoff = cos(material.offset_x / 3.) / 2.1; + let off = vec2(xoff, yoff); + + let a = vec2(3. + xoff); + let b = vec2(6., 6. + yoff); + + let auv = -a + uv; + let ab = -a + b; + + let h = clamp(dot(auv, normalize(ab)) / length(ab), 0., 1.); + + let d = smoothstep(0.5, 0.6, length(-auv + h * ab)); + // let d = length(h * ab); + + return vec4(vec3(d), 1.0); +} diff --git a/assets/shaders/auto-update-shader.sh b/assets/shaders/auto-update-shader.sh deleted file mode 100644 index 3e252af..0000000 --- a/assets/shaders/auto-update-shader.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -for (( ; ; )) -do - sed -E 's/0\.5/0\.4/g' -i pixelate.wgsl - touch pixelate.wgsl - sleep 5 - - sed -E 's/0\.4/0\.5/g' -i pixelate.wgsl - touch pixelate.wgsl - sleep 5 -done diff --git a/assets/shaders/fbm.wgsl b/assets/shaders/fbm.wgsl new file mode 100644 index 0000000..632818c --- /dev/null +++ b/assets/shaders/fbm.wgsl @@ -0,0 +1,30 @@ +#define_import_path bevy_vfx_bag::fbm +#import bevy_vfx_bag::value_noise + +/* +TODO: +- Expose parameters +- Consider if parameters should be shader preprocessor values +- The noise used should be defined by a shader preprocessor definition +*/ + +const NUM_OCTAVES: u32 = 8u; +const hurst: f32 = 1.0; + +fn fbm(coords: vec2) -> f32 { + let gain = exp2(-hurst); + var result = 0.0; + + var amplitude = 1.0; + var frequency = 1.0; + + for (var i: u32 = 0u; i < NUM_OCTAVES; i++) { + let noise = value_noise(coords * frequency); + result += amplitude * noise; + + amplitude *= gain; + frequency *= 2.041002312; + } + + return result; +} \ No newline at end of file diff --git a/assets/shaders/marble.wgsl b/assets/shaders/marble.wgsl new file mode 100644 index 0000000..580048d --- /dev/null +++ b/assets/shaders/marble.wgsl @@ -0,0 +1,111 @@ +/* +From: https://www.shadertoy.com/view/Xs3fR4 + +// variant of Vorocracks: https://shadertoy.com/view/lsVyRy +// integrated with cracks here: https://www.shadertoy.com/view/Xd3fRN + + +float ofs = 0.; + + +float RATIO = 1., // stone length/width ratio + CRACK_depth = 2., + CRACK_zebra_scale = 1., // fractal shape of the fault zebra + CRACK_zebra_amp = 0.34, + CRACK_profile = 1., // fault vertical shape 1. .2 + CRACK_slope = 50., // 10. 1.4 + CRACK_width = .001; + + +// === Voronoi ===================================================== +// --- Base Voronoi. inspired by https://www.shadertoy.com/view/MslGD8 + +#define hash22(p) fract( 18.5453 * sin( p * mat2(127.1,311.7,269.5,183.3)) ) + + +// --- Voronoi distance to borders. inspired by https://www.shadertoy.com/view/ldl3W8 +vec3 voronoiB( vec2 u ) // returns len + id +{ + vec2 iu = floor(u), C, P; + float m = 1e9,d; + + for( int k=0; k < 9; k++ ) { + vec2 p = iu + vec2(k%3-1,k/3-1), + + o = hash22(p), + r = p - u + o; + d = dot(r,r); + if( d < m ) m = d, C = p-iu, P = r; + } + + m = 1e9; + + for( int k=0; k < 25; k++ ) { + vec2 p = iu+C + vec2(k%5-2,k/5-2), + o = hash22(p), + r = p-u + o; + + if( dot(P-r,P-r)>1e-5 ) + m = min( m, .5*dot( (P+r), normalize(r-P) ) ); + } + + return vec3( m, P+u ); +} + +// === pseudo Perlin noise ============================================= +#define rot(a) mat2(cos(a),-sin(a),sin(a),cos(a)) +int MOD = 1; // type of Perlin noise + +// --- 2D +#define hash21(p) fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123) +float noise2(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); f = f*f*(3.-2.*f); // smoothstep + + float v= mix( mix(hash21(i+vec2(0,0)),hash21(i+vec2(1,0)),f.x), + mix(hash21(i+vec2(0,1)),hash21(i+vec2(1,1)),f.x), f.y); + return MOD==0 ? v + : MOD==1 ? 2.*v-1. + : MOD==2 ? abs(2.*v-1.) + : 1.-abs(2.*v-1.); +} + + +#define noise22(p) vec2(noise2(p),noise2(p+17.7)) +vec2 fbm22(vec2 p) { + vec2 v = vec2(0); + float a = .5; + mat2 R = rot(0.5); + + for (int i = 0; i < 6; i++, p*=2.,a/=2.) + p *= R, + v += a * noise22(p); + + return v; +} + + +// ====================================================== + +void mainImage( out vec4 O, vec2 U ) +{ + U *= 3./iResolution.y; + U.x += iTime; // for demo + + vec3 H0; + O-=O; + + for(float i=0.; i) -> vec3 { + var v = v * 1664525u + 1013904223u; + + v.x += v.y * v.z; + v.y += v.z * v.x; + v.z += v.x * v.y; + + v = v ^ (v >> 16u); + + v.x += v.y*v.z; + v.y += v.z*v.x; + v.z += v.x*v.y; + + return v; +} + +// https://github.com/gfx-rs/naga/issues/1908 +fn ldexp_workaround(v: f32, e: f32) -> f32 { + return v * exp2(e); +} +fn ldexp_workaround2(v: vec2, e: f32) -> vec2 { + return v * exp2(e); +} + +fn hash21(point: vec2) -> u32 { + return pcg3d(vec3(point.xy, 0u)).x; +} + +fn hash21f(point: vec2) -> f32 { + // https://www.pcg-random.org/using-pcg-c.html + // We get a random value in a u32's range. + // Divide it by 1/2^32 to produce a value in the [0, 1) range. + // Then massage it to get the desired [-1, 1) range. + let noise = ldexp_workaround(f32(hash21(point)), -32.0); + return -1.0 + 2.0 * noise; +} + +fn hash22(point: vec2) -> vec2 { + return pcg3d(vec3(point.xy, 0u)).xy; +} + +fn hash22f(point: vec2) -> vec2 { + let noise = ldexp_workaround2(vec2(hash22(point)), -32.0); + return vec2(-1.0) + (2.0 * noise); +} diff --git a/assets/shaders/value_noise.wgsl b/assets/shaders/value_noise.wgsl new file mode 100644 index 0000000..74972a6 --- /dev/null +++ b/assets/shaders/value_noise.wgsl @@ -0,0 +1,27 @@ +#define_import_path bevy_vfx_bag::value_noise +#import bevy_vfx_bag::math + +fn value_noise(coords: vec2) -> f32 { + let index = vec2(floor(coords)); + let frac = fract(coords); + + // Sometimes a smoothstepped frac is used instead. + let interpolant = frac; + + let noise_xy00 = hash21f(index + vec2(0u, 0u)); + let noise_xy10 = hash21f(index + vec2(1u, 0u)); + let noise_xy01 = hash21f(index + vec2(0u, 1u)); + let noise_xy11 = hash21f(index + vec2(1u, 1u)); + + // Gives us the noise at the correct point in the x direction + // between the upper corners + let noise_x0_lerp = mix(f32(noise_xy00), f32(noise_xy10), interpolant.x); + + // x direction lower corners + let noise_x1_lerp = mix(f32(noise_xy01), f32(noise_xy11), interpolant.x); + + // Lastly lerp between the values found in the y direction. + let noise = mix(noise_x0_lerp, noise_x1_lerp, interpolant.y); + + return noise; +} diff --git a/examples/examples_common.rs b/examples/examples_common.rs index 9d15470..32d4517 100644 --- a/examples/examples_common.rs +++ b/examples/examples_common.rs @@ -141,7 +141,13 @@ mod shapes { // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane { size: 50. }.into()), + mesh: meshes.add( + shape::Plane { + size: 50., + ..default() + } + .into(), + ), material: materials.add(Color::SILVER.into()), ..default() }); diff --git a/examples/noise/main.rs b/examples/noise/main.rs new file mode 100644 index 0000000..baa9e88 --- /dev/null +++ b/examples/noise/main.rs @@ -0,0 +1,196 @@ +#[path = "../examples_common.rs"] +mod examples_common; + +use bevy::{ + prelude::*, + reflect::TypeUuid, + render::render_resource::{AsBindGroup, ShaderRef, ShaderType}, +}; + +use bevy_vfx_bag::BevyVfxBagPlugin; + +fn main() { + let mut app = App::new(); + + app.add_plugin(examples_common::SaneDefaultsPlugin) + .add_plugin(BevyVfxBagPlugin::default()) + .add_plugin(MaterialPlugin::::default()) + .add_plugin(MaterialPlugin::::default()) + .add_plugin(MaterialPlugin::::default()) + .add_startup_system(startup) + .add_system(update_value_noise) + .add_system(update_fbm) + .add_system(update_voronoi) + .run(); +} + +fn startup( + mut commands: Commands, + mut meshes: ResMut>, + mut noise_materials: ResMut>, + mut fbm_materials: ResMut>, + mut voronoi_materials: ResMut>, +) { + /* + // noise cubes + for i in 1..=4 { + let i = i as f32; + let uv = Uv { + scale: i * 4., + ..default() + }; + + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + transform: Transform::from_xyz(-4. + (i * 2.), 1.5, 0.0), + material: noise_materials.add(ValueNoise { uv }), + ..default() + }); + } + + // fbm cubes + for i in 1..=4 { + let i = i as f32; + let uv = Uv { + scale: i * 4., + ..default() + }; + + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + transform: Transform::from_xyz(-4. + (i * 2.), 0.0, 0.0), + material: fbm_materials.add(Fbm { uv }), + ..default() + }); + } + + // voronoi cubes + for i in 1..=4 { + let i = i as f32; + let uv = Uv { + scale: i * 4., + ..default() + }; + + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + transform: Transform::from_xyz(-4. + (i * 2.), -1.5, 0.0), + material: voronoi_materials.add(Voronoi { uv }), + ..default() + }); + } + */ + + let uv = Uv { ..default() }; + + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 3.8 })), + transform: Transform::from_xyz(0.0, 0.0, 0.0), + material: voronoi_materials.add(Voronoi { uv }), + ..default() + }); + + // camera + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(-0.5, 0.5, 7.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); +} + +fn update_value_noise( + time: Res