Skip to content

Releases: GEMI/bevy-grass

bevy_grass 0.20.6: strategy cameras, terrain from code, no blinking blades

Choose a tag to compare

@GEMI GEMI released this 24 Oct 18:06

Strategy cameras, terrain built from code, and no more blinking blades.
GrassCamera::radius_from_ground draws grass across the ground a high
camera sees; GrassField::with_ground_from_fn and with_density_from_fn
grow a field on terrain a game builds itself; GrassLightFrame and
GrassWindFrame::advance let a game drive grass from a sun or wind of its
own. Blades no longer flash the clear colour behind a depth prepass or
white in a low sun's glint, gusts no longer show a grid from above, and an
MSAA sample count the GPU cannot do no longer turns the window black. The
web demo has every setting of the
crate in its panel and copies what you tune as Rust.

Breaking, and what to write instead

GrassSettings has a new field, mid_range_start. A struct literal that
names every field without ..GrassSettings::HIGH (or another tier) needs
mid_range_start: 0.1 added.

GrassWindFrame no longer implements Bevy's ShaderType, and GrassCamera
and NoGrass no longer implement ExtractComponent. The public types are
plain data now, so a change to the shaders' wind layout or to Bevy's
extraction API no longer breaks them. Nothing changes for a game that writes
them; the renderer copies them into the render world itself. A game that
uploaded GrassWindFrame to a shader of its own copies its fields into a
ShaderType struct of its own.

GrassPlugin has a new field, lights (true by default). A literal that
names every field without ..default() needs lights: true added.

GrassCamera has a new field, radius_from_ground (false by default). A
literal that names every field without ..default() needs
radius_from_ground: false added.

GrassFieldOverrides has a new field, wind_scale (1 by default). A literal
that names every field without ..default() needs wind_scale: 1.0 added.

GrassWindFrame::gust_offset and previous_gust_offset now wrap at 256
repeats of the gust texture instead of 1, for the second layer of gusts (see
Fixed). A custom wind that calls GrassWindFrame::advance, or leaves the
offsets at zero, needs no change. One that wraps the offsets itself wraps
them at 256 (rem_euclid(Vec2::splat(256.0))); wrapped at 1, the gusts
jump each time they cross a repeat.

Added

  • The web demo's panel has every public setting of the crate, built from
    the types' Reflect data and their doc comments: GrassSettings,
    GrassStyle with its species, GrassWind and its presets, FarGrass,
    GrassFieldOverrides, GrassRegrowth, GrassInteractor, GrassCamera,
    a GrassBrush on the left mouse button, GrassDebug and GrassStats.
    Each section copies as Rust (only what differs from its tier or preset),
    the style also as a .grass.ron, and a .grass.ron can be pasted in. The
    address keeps the changes, so a link shares them. It replaces the
    wind-strength slider.
  • FarGrass::with_sheen(sheen), like with_fade and with_area: a struct
    literal with ..FarGrass::new(&style) does not compile outside the crate,
    so setting the sheen took a variable.
  • FarGrass::uniform(&wind, &settings) and FarGrass::GUSTS: what a
    terrain material of the game's own needs to call grass_far_color from
    its shader, the far grass FarGrassMaterial draws. Before, its uniform
    could not be built outside the crate. The new custom_terrain example is
    a material with its own shader doing it.
  • GrassFieldOverrides::wind_scale: one field moves less in the shared
    wind, or more: a courtyard, a greenhouse, a windswept ridge. The gusts
    stay in step with the fields around it. The tip flutter scales with it.
  • GrassCamera::radius_from_ground, for strategy and city-builder cameras:
    the radius is measured from the ground below the camera, so grass reaches
    the same distance across the ground at any zoom, thinned for the camera's
    height so the number of blades, and the cost, stay about the same. The
    new rts_camera example and Strategy cameras
    show the setup and its costs. Off by default; nothing changes without it.
  • GrassField::with_ground_from_fn(&mut images, cell_size, height) and
    GrassField::with_density_from_fn(&mut images, cell_size, density): a
    field on terrain built from code, from the function that built it. Each is
    sampled once into a heightmap or a mask, so terrain in chunk meshes, or
    meshes kept only on the GPU, needs no heightmap file and no field per
    chunk. A height that is not finite leaves no ground, for coastlines. The
    new code_terrain example is an island in 16 chunks with one field.
  • GpuStats::total_blades(), the blades of every level of detail together:
    stats.gpu.total_blades() == 0 after the first reading means nothing was
    drawn. The README's new "Check it works" paragraph points to it and to the
    debug gizmos, which show a field buried under hills.
  • A warning, once per camera, when a perspective camera has had grass fields
    in view for two seconds and all of them were beyond its grass radius, so
    it drew nothing. The radius is a distance from the camera itself, so a
    strategy camera high above the ground drew no grass at all, and nothing in
    the log said why. The check is one box test per camera and field a frame.
  • GrassWindFrame::advance(delta, gust_speed) scrolls the gusts along the
    frame's direction, as the built-in wind does, so a custom wind source no
    longer has to work out the gust texture's scrolling itself. The
    custom_wind example uses it.
  • GrassLightFrame, the light grass follows this frame (the shadow-casting
    light's direction and the sun's), written by the new built-in
    GrassLightPlugin from Bevy's directional lights in
    GrassLightSystems::Sample. A game whose sun is not a DirectionalLight
    builds GrassPlugin { lights: false, .. } and writes the frame itself, as
    it can the wind. The renderer reads the frame instead of querying Bevy's
    lights, so Bevy's light types are read in one place.

Fixed

  • A camera asking for an MSAA sample count the GPU does not support no
    longer turns the window black. Bevy stops rendering at the first
    unsupported count, and WebGPU supports only 1 and 4 samples, so
    Msaa::Sample2 and Msaa::Sample8 stopped every browser. Grass now logs
    one warning naming the camera and uses the most samples below it that the
    device has (8x becomes 4x, 2x becomes off). The web demo offers only Off
    and 4x.
  • With a depth prepass (which SSAO, depth of field, outlines and contact
    shadows need), a few blades a frame flashed the clear colour, often a
    pale sky blue, all over the screen. The main pass draws a blade only where
    its depth equals the prepass's, and the two shaders were free to round a
    blade's position differently; where the main pass came out a hair
    farther, neither the blade nor the ground behind it was drawn. Both now
    mark the position @invariant, so they place blades identically. In a
    game scene, 21 such pixels a frame became none. The goldens differ by a
    mean of at most 0.7 of 255 as blade edges move by a rounding step.
  • Blades with sheen (GrassBlade::specular, 0.3 in MEADOW) flashed white
    for a frame, a few at a time all over the screen, when the sun was low and
    in front of the camera: a blade a pixel or two wide that swayed into the
    sun's mirror angle caught a glint hundreds of times its usual brightness,
    which bloom spread. The sheen now widens where a blade's normal turns
    within a pixel (specular antialiasing), and a blade's reflectance stops at
    0.3, below where Bevy's grazing reflectance reaches full. Looking into a
    low sun, MEADOW flashes about 0.2 pixels a frame instead of 36. Elsewhere
    the look is unchanged to the eye; the goldens differ by a mean of at most
    0.5 of 255, and the forward and deferred paths change alike. Styles
    without sheen pay nothing for it.
  • Seen from a strategy camera, the gusts rolled across the field in squares.
    The gust texture repeats every gust_scale metres (25 m in BREEZE), and
    from 100 m up dozens of repeats show at once, in a grid along the world's
    axes. The gusts are now two layers of it, the second turned 36.8° and 1.17
    times as large, moving together; the sum does not repeat. Gusts keep their
    size, speed and depth, but their shapes change, and so do the goldens.
  • FarGrassMaterial's gust sheen, the paler undersides of bent grass, now
    shows where the ground is seen at a glancing angle and fades as the camera
    looks down on it, as on real grass. From a strategy camera the far ground
    showed gust patterns with no grass in sight, and through the gaps between
    blades. Ground-level views are unchanged (the meadow example differs by
    at most 2 of 255). Where the sheen would change the colour by less than
    0.4%, the gust texture is not read.
  • A perspective camera high above the ground, a strategy game's, kept its
    trample window (64 m across) centred under itself, while the ground it
    showed began 65 m or more ahead, so units in view flattened no grass. The
    window now moves ahead onto the nearest ground in view, never past where
    grass is drawn. It moves only once that ground is about 11 m away (a sixth
    of the window), so first- and third-person cameras keep the window around
    themselves as before, and the trail behind a walker with it.
  • GrassSampler reported grass where the ground has a hole, past the edge
    of a GrassGround::Mesh or where a height function gave no height, while
    the GPU draws none there. Its density is now 0 there, so footsteps and
    stealth checks agree with what is drawn.

Changed

  • The mid-range thinning (mid_range_density, half the blades, the rest
    wider) now begins at its own distance, mid_range_start: 10...
Read more

bevy_grass 0.20.5: grass in the browser

Choose a tag to compare

@GEMI GEMI released this 22 Oct 18:34

Grass in the browser: it draws through WebGPU, in 10 storage buffers per
shader stage, and a device that cannot draw it gets one warning instead of a
stopped renderer. A web demo and
docs/web.md come with it. Tested in Chrome, Edge and Brave on
Windows and in Chrome on macOS, and natively on macOS (Metal). Safari 27
cannot run Bevy 0.20's standard material yet, with or without grass
(docs/web.md). 0.20.4 was not published; its changes
are in this release.

Breaking, and what to write instead

GrassStyleFileError::Parse no longer holds ron's SpannedError, so a
ron upgrade is no longer a breaking change of this crate. It is now
Parse { line, column, message }: where the parser stopped, counted from 1,
and why. Match Parse { message, .. } instead of Parse(error), and read
message or the error's Display (unchanged apart from showing only the
start of the span) instead of the ron error.

Added

  • The examples build for the browser (wasm32-unknown-unknown, WebGPU):
    python tools/web_example.py builds and serves them, and CI checks the
    wasm build. docs/web.md lists the browsers tested.
  • A web demo for GitHub Pages (web/, a package of its own that is not
    published): a meadow with a quality switch, starting at MEDIUM, and a
    preset picker. python tools/web_demo.py builds it for size, serves it,
    and smoke-tests it in a browser.
  • examples/webgpu_minimal.rs: the quick start, ready for the browser
    (a canvas that fills the page, LOW on the web, and a message on screen
    when GrassSupport says the device cannot draw grass). The README has a
    Platforms section and links it.
  • docs/web.md, running in the browser: how to build for
    WebGPU, the browsers tested, what a device needs, the shadow and timing
    caveats, and frame times per tier on the devices measured.
  • GrassSupport, a resource: whether this GPU can draw grass, and if not,
    why (no compute shaders, as in WebGL2; no indirect draws; no storage
    buffers in the vertex shader; too few storage buffers per shader stage; too
    small a storage binding). Decided once when the renderer starts.
  • The examples' --no-grass flag: the same scene without grass.
  • The web demo's and examples' pages log each shader a browser rejects, with
    the compiler's reason and line.
  • The examples' --max-storage-buffers N flag: ask the GPU for at most N
    storage buffers per shader stage, as a weaker device or browser offers.

Fixed

  • On a device that cannot draw grass, the grass pipelines were still created,
    and the wgpu validation error stopped Bevy's renderer. Now grass logs one
    warning saying why and draws nothing, and the rest of the scene renders.
    Grass binds 10 storage buffers in its busiest shader stage, so a device
    that allows fewer gets the warning.

Changed

  • Grass fits in 10 storage buffers per shader stage, down from 14, so it
    draws on devices that offer 10 to 13: most browsers on macOS, Samsung
    Internet and some of Firefox. No picture changes. Each family of compute
    passes has its own layout, the masks and heightmaps share one buffer, and
    the draws bind storage buffers in the vertex stage only (see
    docs/rendering.md). Fields whose masks and
    heightmaps together are larger than one storage binding (128 MiB in
    WebGPU) log a warning and draw no grass, where before each had a binding
    of its own.
bevy_grass = "0.20.5"