10 releases (breaking)

0.9.0 Aug 9, 2024
0.7.0 Nov 19, 2023
0.6.0 Aug 11, 2023
0.5.0 Mar 11, 2023
0.1.1 Feb 22, 2022

#246 in Rendering

MIT/Apache

77KB
960 lines

bevy_smud

crates.io MIT/Apache 2.0 crates.io docs.rs

Signed_Distance_Field/Function (SDF) 2D shape rendering for Bevy.

screenshot of a bird drawn with bevy_smud

Bevy smud is a way to conveniently construct and render sdf shapes with Bevy.

Given a shape function/expression, and a fill type, it generates shaders at run-time.

If you keep the number of different sdf and fill combinations relatively low it's pretty performant. My machine easily handles 100k shapes at 60 fps, with 40 different shape/fill combinations in randomized order (see gallery example).

Usage

An SDF is a way to map points in space to distances to a surface. If a point maps to a positive value, it's outside the shape, if it's negative, it's inside the shape. These "mappings" can be described by functions, which takes a point as input and returns a distance to a surface. For instance, if you wanted to make a circle, it could be described as length(position - center) - radius. That way, all the points that are radius away from center would be 0 and would define the edge of the shape.

Many such functions describing geometric primitives are included in this library, they are imported automatically when using the single-expression or body shorthand for adding sdfs. For instance, the circle above could also be described as:

sd_circle(p - center, 50.)

Similarly there are a bunch of other shapes (sd_ellipse, sd_box, sd_rounded_box, sd_egg etc. etc.)

Most of the built-in shapes are direct ports of the ones on this page, which includes screenshots of the various shapes. So that page might act as a good reference. The ports here use snake_case instead of camelCase.

To put together a shape, you can do:

use bevy::prelude::*;
use bevy_smud::prelude::*;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, SmudPlugin))
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut shaders: ResMut<Assets<Shader>>,
) {
    commands.spawn(Camera2dBundle::default());

    let circle = shaders.add_sdf_expr("sd_circle(p, 50.)");

    commands.spawn(ShapeBundle {
        shape: SmudShape {
            color: Color::WHITE,
            sdf: circle,
            frame: Frame::Quad(55.),
            ..default()
        },
        ..default()
    });
}

Make sure you reuse the shaders, i.e. don't call add_sdf_expr every frame.

You can also define shapes in .wgsl files. Note that in order to use the built-in shapes, you have to import smud, and you must create a function named sdf that takes a vec2<f32> and returns f32.

Other than that, make sure you understand how to combine shapes, use symmetries and change domains. For instance, the bevy in the screenshot above is built up of several circles, ellipses, and a vesica for the beak.

Also, check out the examples. In particular, the basic example should be a good place to start.

Showcase

Send me a PR if you want your project featured here:

  • Dis order: dis order screenshot

Word of caution

This crate is still fairly experimental.

If you want something more finished, you should probably check out bevy_prototype_lyon.

Bevy version support

The main branch targets the latest bevy release.

bevy bevy_smud
0.14 0.9, main
0.12 0.7
0.11 0.6
0.10 0.5
0.9 0.4
0.8 0.3
0.7 0.2
0.6 0.1

Thanks!

Little of this crate is original work. It's mostly a mishmash of bevy_sprite and Inigo Quilez sdf rendering primitives ported to wgsl. I just put the two together in a way I found convenient.

Dependencies

~38–75MB
~1.5M SLoC