Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example for stochastic texture sampling #17527

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2605,6 +2605,17 @@ description = "A custom shader that builds on the standard material"
category = "Shaders"
wasm = true

[[example]]
name = "stochastic_sampling"
path = "examples/shader/stochastic_sampling.rs"
doc-scrape-examples = true

[package.metadata.example.stochastic_sampling]
name = "Stochastic Sampling"
description = "A custom shader that avoids repetition artifacts when tiling a texture"
category = "Shaders"
wasm = true

[[example]]
name = "shader_prepass"
path = "examples/shader/shader_prepass.rs"
Expand Down
87 changes: 87 additions & 0 deletions assets/shaders/stochastic_sampling.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#import bevy_sprite::mesh2d_vertex_output::VertexOutput
#import bevy_sprite::mesh2d_view_bindings::globals
#import bevy_sprite::mesh2d_functions

fn permute_3_(x: vec3<f32>) -> vec3<f32> {
return (((x * 34.) + 1.) * x) % vec3(289.);
}

// Noise implementation from https://github.com/johanhelsing/noisy_bevy/blob/v0.8.0/assets/noisy_bevy.wgsl
fn simplex_noise_2d(v: vec2<f32>) -> f32 {
let C = vec4(
0.211324865405187, // (3.0 - sqrt(3.0)) / 6.0
0.366025403784439, // 0.5 * (sqrt(3.0) - 1.0)
-0.577350269189626, // -1.0 + 2.0 * C.x
0.024390243902439 // 1.0 / 41.0
);

// first corner
var i = floor(v + dot(v, C.yy));
let x0 = v - i + dot(i, C.xx);

// other corners
var i1 = select(vec2(0., 1.), vec2(1., 0.), x0.x > x0.y);
var x12 = x0.xyxy + C.xxzz - vec4(i1, 0., 0.);

// permutations
i = i % vec2(289.);

let p = permute_3_(permute_3_(i.y + vec3(0., i1.y, 1.)) + i.x + vec3(0., i1.x, 1.));
var m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), vec3(0.));
m *= m;
m *= m;

// gradients: 41 points uniformly over a line, mapped onto a diamond
// the ring size, 17*17 = 289, is close to a multiple of 41 (41*7 = 287)
let x = 2. * fract(p * C.www) - 1.;
let h = abs(x) - 0.5;
let ox = floor(x + 0.5);
let a0 = x - ox;

// normalize gradients implicitly by scaling m
// approximation of: m *= inversesqrt(a0 * a0 + h * h);
m = m * (1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h));

// compute final noise value at P
let g = vec3(a0.x * x0.x + h.x * x0.y, a0.yz * x12.xz + h.yz * x12.yw);
return 130. * dot(m, g);
}

fn sum(v: vec4<f32>) -> f32 {
return v.x+v.y+v.z;
}

// Stochastic sampling method from https://iquilezles.org/articles/texturerepetition/
fn stochastic_sampling(uv: vec2<f32>, dx: vec2<f32>, dy: vec2<f32>, s: f32) -> vec4<f32> {

// sample variation pattern
let frequency_scale = 5.0;
let amplitude_scale = 0.3;
let k = simplex_noise_2d(uv.xy / frequency_scale) * amplitude_scale;

// compute index from 0-7
let index = k * 8.0;
let i = floor(index);
let f = fract(index);

// offsets for the different virtual patterns from 0 to 7
let offa = sin(vec2<f32>(3.0,7.0)*(i+0.0)); // can replace with any other hash
let offb = sin(vec2<f32>(3.0,7.0)*(i+1.0)); // can replace with any other hash

// sample the two closest virtual patterns
let cola = textureSampleGrad(texture, texture_sampler, uv + s * offa, dx, dy);
let colb = textureSampleGrad(texture, texture_sampler, uv + s * offb, dx, dy);

// interpolate between the two virtual patterns
return mix(cola, colb, smoothstep(0.2,0.8,f - 0.1*sum(cola-colb)) );
}

@group(2) @binding(1) var texture: texture_2d<f32>;
@group(2) @binding(2) var texture_sampler: sampler;

@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
let speed = 0.5;
let s = smoothstep(0.4, 0.6, sin(globals.time * speed));
return stochastic_sampling(in.uv, dpdx(in.uv), dpdy(in.uv), s);
}
Binary file added assets/textures/rocks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ Example | Description
[Post Processing - Custom Render Pass](../examples/shader/custom_post_processing.rs) | A custom post processing effect, using a custom render pass that runs after the main pass
[Shader Defs](../examples/shader/shader_defs.rs) | A shader that uses "shaders defs" (a bevy tool to selectively toggle parts of a shader)
[Specialized Mesh Pipeline](../examples/shader/specialized_mesh_pipeline.rs) | Demonstrates how to write a specialized mesh pipeline
[Stochastic Sampling](../examples/shader/stochastic_sampling.rs) | A custom shader that avoids repetition artifacts when tiling a texture
[Storage Buffer](../examples/shader/storage_buffer.rs) | A shader that shows how to bind a storage buffer using a custom material.
[Texture Binding Array (Bindless Textures)](../examples/shader/texture_binding_array.rs) | A shader that shows how to bind and sample multiple textures as a binding array (a.k.a. bindless textures).

Expand Down
75 changes: 75 additions & 0 deletions examples/shader/stochastic_sampling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Demonstrates using a custom extension to the `StandardMaterial` to create a repeating texture that avoids seams
//! by using stochastic sampling. This example uses a custom shader to achieve the effect.
use bevy::image::{ImageAddressMode, ImageSamplerDescriptor};
use bevy::prelude::*;
use bevy::render::mesh::VertexAttributeValues;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::sprite::{Material2d, Material2dPlugin};

fn main() {
App::new()
.add_plugins(DefaultPlugins.set(ImagePlugin {
default_sampler: ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..Default::default()
},
}))
.add_plugins(Material2dPlugin::<CustomMaterial>::default())
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<CustomMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
commands.spawn(Camera2d);
let texture = asset_server.load("textures/rocks.png");
commands.spawn((
Mesh2d(meshes.add(repeating_quad(10.0))),
MeshMaterial2d(materials.add(CustomMaterial {
texture: Some(texture),
})),
Transform::default(),
));
}

// This struct defines the data that will be passed to your shader
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct CustomMaterial {
#[texture(1)]
#[sampler(2)]
texture: Option<Handle<Image>>,
}

/// This example uses a shader source file from the assets subdirectory
const SHADER_ASSET_PATH: &str = "shaders/stochastic_sampling.wgsl";

/// The Material trait is very configurable, but comes with sensible defaults for all methods.
/// You only need to implement functions for features that need non-default behavior. See the Material api docs for details!
impl Material2d for CustomMaterial {
fn fragment_shader() -> ShaderRef {
SHADER_ASSET_PATH.into()
}
}

/// Creates a quad where the texture repeats n times in both directions.
fn repeating_quad(n: f32) -> Mesh {
let mut mesh: Mesh = Rectangle::from_length(1000.0).into();
let uv_attribute = mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap();
// The format of the UV coordinates should be Float32x2.
let VertexAttributeValues::Float32x2(uv_attribute) = uv_attribute else {
panic!("Unexpected vertex format, expected Float32x2.");
};
// The default `Rectangle`'s texture coordinates are in the range of `0..=1`. Values outside
// this range cause the texture to repeat.
for uv_coord in uv_attribute.iter_mut() {
uv_coord[0] *= n;
uv_coord[1] *= n;
}
mesh
}
Loading