This commit is contained in:
Periwink 2025-07-18 12:42:50 -04:00 committed by GitHub
commit 99ddf518d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 174 additions and 0 deletions

View File

@ -2865,6 +2865,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"

View File

@ -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);
}

BIN
assets/textures/rocks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -476,6 +476,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).

View File

@ -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
}