start to a shader??

This commit is contained in:
Mitchell Marino 2026-03-17 20:00:07 -05:00
parent db19b9c416
commit f509833262
3 changed files with 175 additions and 33 deletions

View File

@ -0,0 +1,61 @@
struct Material {
p0: vec2<f32>,
c0: vec2<f32>,
c1: vec2<f32>,
p1: vec2<f32>,
width: f32,
color: vec4<f32>,
};
@group(2) @binding(0)
var<uniform> mat: Material;
fn bezier(p0: vec2<f32>, c0: vec2<f32>, c1: vec2<f32>, p1: vec2<f32>, t: f32) -> vec2<f32> {
let u = 1.0 - t;
return
u*u*u*p0 +
3.0*u*u*t*c0 +
3.0*u*t*t*c1 +
t*t*t*p1;
}
fn distance_to_bezier(p: vec2<f32>) -> f32 {
var min_d = 1e9;
let steps = 32;
var prev = mat.p0;
for (var i = 1; i <= steps; i++) {
let t = f32(i) / f32(steps);
let cur = bezier(mat.p0, mat.c0, mat.c1, mat.p1, t);
// distance to segment
let v = cur - prev;
let w = p - prev;
let t_seg = clamp(dot(w, v) / dot(v, v), 0.0, 1.0);
let proj = prev + t_seg * v;
min_d = min(min_d, distance(p, proj));
prev = cur;
}
return min_d;
}
@fragment
fn fragment(
@location(0) world_pos: vec3<f32>,
) -> @location(0) vec4<f32> {
let d = distance_to_bezier(world_pos.xy);
let half_w = mat.width * 0.5;
let aa = fwidth(d);
let alpha = smoothstep(half_w + aa, half_w - aa, d);
if (alpha <= 0.001) {
discard;
}
return vec4(mat.color.rgb, mat.color.a * alpha);
}

View File

@ -1,4 +1,7 @@
use crate::avian::{CharacterControllerBundle, CharacterControllerPlugin}; use crate::{
avian::{CharacterControllerBundle, CharacterControllerPlugin},
shaders::BezierMaterial,
};
use avian2d::{ use avian2d::{
PhysicsPlugins, PhysicsPlugins,
math::Vector, math::Vector,
@ -6,12 +9,14 @@ use avian2d::{
}; };
use bevy::{ use bevy::{
camera::ScalingMode, camera::ScalingMode,
color::palettes::css::{GREEN, RED}, color::palettes::css::{GREEN, RED, WHITE},
input_focus::InputFocus, input_focus::InputFocus,
prelude::*, prelude::*,
sprite_render::Material2dPlugin,
}; };
pub mod avian; pub mod avian;
pub mod shaders;
const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
@ -24,12 +29,12 @@ fn main() {
// Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels. // Add physics plugins and specify a units-per-meter scaling factor, 1 meter = 20 pixels.
// The unit allows the engine to tune its parameters for the scale of the world, improving stability. // The unit allows the engine to tune its parameters for the scale of the world, improving stability.
PhysicsPlugins::default().with_length_unit(20.0), PhysicsPlugins::default().with_length_unit(20.0),
Material2dPlugin::<BezierMaterial>::default(),
CharacterControllerPlugin, CharacterControllerPlugin,
)) ))
.init_resource::<InputFocus>() .init_resource::<InputFocus>()
.add_systems(Startup, setup) .add_systems(Startup, (setup, setup_ui))
.add_systems(Update, debug_border) .add_systems(Update, (debug_border, do_menu, move_bezier))
.add_systems(Update, do_menu)
.insert_resource(Gravity(Vector::ZERO)) .insert_resource(Gravity(Vector::ZERO))
.run(); .run();
} }
@ -81,8 +86,41 @@ fn do_menu(
} }
} }
fn button(asset_server: &AssetServer) -> impl Bundle { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
( let mut projection = OrthographicProjection::default_2d();
projection.scaling_mode = ScalingMode::AutoMin {
min_width: 1920.,
min_height: 1080.,
};
commands.spawn((Camera2d, Projection::Orthographic(projection)));
// player
commands.spawn((
Sprite::from_image(asset_server.load("player.png")),
Transform::from_xyz(0.0, 0.0, 0.0),
CharacterControllerBundle::new(Collider::capsule(15., 27.5)),
));
// A cube to move around
commands.spawn((
Sprite {
color: Color::srgb(0.0, 0.4, 0.7),
custom_size: Some(Vec2::new(30.0, 30.0)),
..default()
},
Transform::from_xyz(50.0, -100.0, 0.0),
RigidBody::Dynamic,
Collider::rectangle(30.0, 30.0),
));
}
fn setup_ui(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<BezierMaterial>>,
) {
commands.spawn((
Node { Node {
width: percent(100), width: percent(100),
height: percent(100), height: percent(100),
@ -116,37 +154,47 @@ fn button(asset_server: &AssetServer) -> impl Bundle {
TextShadow::default(), TextShadow::default(),
)] )]
)], )],
) ));
// Curve points (world-space)
let p0 = Vec2::new(-200.0, -100.0);
let p1 = Vec2::new(200.0, 100.0);
let c0 = p0 + Vec2::new(150.0, 0.0);
let c1 = p1 + Vec2::new(-150.0, 0.0);
// Bounding quad (must contain entire curve + width)
let center = (p0 + p1) * 0.5;
let size = Vec2::new(500.0, 300.0);
commands.spawn((
Mesh2d(meshes.add(Rectangle::new(size.x, size.y))),
MeshMaterial2d(materials.add(BezierMaterial {
p0,
c0,
c1,
p1,
width: 12.0,
color: WHITE.into(),
})),
Transform::from_translation(center.extend(0.0)),
));
} }
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, assets: Res<AssetServer>) { fn move_bezier(mut materials: ResMut<Assets<BezierMaterial>>, windows: Query<&Window>) {
let mut projection = OrthographicProjection::default_2d(); let Ok(window) = windows.single() else {
projection.scaling_mode = ScalingMode::AutoMin { return;
min_width: 1920., };
min_height: 1080., let Some(mouse_pos) = window.cursor_position() else {
return;
}; };
commands.spawn((Camera2d, Projection::Orthographic(projection)));
// player for (_, mat) in materials.iter_mut() {
commands.spawn(( mat.p0 = mouse_pos;
Sprite::from_image(asset_server.load("player.png")), }
Transform::from_xyz(0.0, 0.0, 0.0),
CharacterControllerBundle::new(Collider::capsule(15., 27.5)),
));
// A cube to move around // pos: Vec2, in logical pixels
commands.spawn(( // (0, 0) is bottom-left of the window
Sprite { println!("Mouse window position: {:?}", mouse_pos);
color: Color::srgb(0.0, 0.4, 0.7),
custom_size: Some(Vec2::new(30.0, 30.0)),
..default()
},
Transform::from_xyz(50.0, -100.0, 0.0),
RigidBody::Dynamic,
Collider::rectangle(30.0, 30.0),
));
commands.spawn(button(&assets));
} }
fn debug_border(mut gizmos: Gizmos) { fn debug_border(mut gizmos: Gizmos) {

33
src/shaders/mod.rs Normal file
View File

@ -0,0 +1,33 @@
use bevy::prelude::*;
use bevy::render::render_resource::*;
use bevy::shader::ShaderRef;
use bevy::sprite_render::{AlphaMode2d, Material2d};
// This is the struct that will be passed to your shader
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
pub struct BezierMaterial {
#[uniform(0)]
pub p0: Vec2,
#[uniform(0)]
pub c0: Vec2,
#[uniform(0)]
pub c1: Vec2,
#[uniform(0)]
pub p1: Vec2,
#[uniform(0)]
pub width: f32,
#[uniform(0)]
pub color: LinearRgba,
}
/// The Material2d 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 Material2d api docs for details!
impl Material2d for BezierMaterial {
fn fragment_shader() -> ShaderRef {
"shaders/bezier.wgsl".into()
}
fn alpha_mode(&self) -> AlphaMode2d {
AlphaMode2d::Blend
}
}