Skip to content

Repository files navigation

Stride GPU Grass System

Available on the Community Stride Asset Store

A GPU-driven grass renderer for Stride. One compute dispatch turns a list of seed points into hundreds of thousands of animated, LOD-culled grass blades — with procedural shading (no textures), world-space wind, and a scrolling trample field that flattens a trail under the player.

It is fully decoupled from any terrain: you decide where the grass grows by handing it seeds. Scatter them over a flat area, sample a heightmap, raycast onto colliders, or read a voxel surface — the renderer doesn't care.

What's in the box

File Role
GrassRenderer The engine. Owns the GPU buffers, the two compute passes, the material and its own Entity. Feed it seeds, call Update once per frame.
GrassField Drop-in SyncScript. Attach it to an entity, tweak the knobs in Game Studio, press play.
GrassScatter CPU helpers that turn a surface into GrassSeed[] (flat area or via a height sampler).
GrassSeed The 16-byte struct the renderer consumes: a root position + a variation hash.
Effects/*.sdsl GrassCull (build + LOD + wind sway + trample lean), GrassTrampleUpdate (trample field), GrassDiffuse (blade color).

Quick start (drop-in)

  1. Reference StrideGrassSystem from your game project.
  2. Add an empty entity, attach a GrassField component (category Grass).
  3. Set Camera to your scene camera, tweak AreaSize / CellSize / GrassDistance.
  4. Press play.

Use it directly (heightmap / voxel / streamed world)

_grass = new GrassRenderer(Services, GraphicsDevice, maxInstances: 1_000_000);
_grass.SetGrassDistance(80f);
Entity.Scene.Entities.Add(_grass.GrassEntity);

// Static field: scatter over an area, sampling your ground height.
var seeds = GrassScatter.OverArea(
    center: Vector3.Zero, sizeX: 200, sizeZ: 200, cellSize: 0.5f,
    heightSampler: (x, z) => MyTerrain.TryGetGroundY(x, z, out var y) ? y : (float?)null);
_grass.SetSeeds(seeds);

// …or stream a big world chunk-by-chunk:
_grass.SetChunkSeeds(new Int3(cx, 0, cz), chunkSeeds);   // region entered range
_grass.RemoveChunk(new Int3(cx, 0, cz));                  // region left range

// Each frame:
_grass.ClearTrampleSources();
_grass.AddTrampleSource(playerPos, radius: 1.5f);   // flatten a trail
_grass.Update(cameraPos, (Game)Game);

How it works

  • Seeds, not blades. The CPU only ever holds one point per grass cell. The GrassCull compute shader expands each seed into up to 32 sub-blades, jittered deterministically from the seed's variation hash, and writes their world matrices straight into the buffer backing a InstancingUserBuffer.
  • Free LOD. Sub-blades beyond the current distance-based LOD count are written as degenerate (zero) matrices, so the rasterizer discards them for nothing — no indirect draws, no CPU culling.
  • Trample field. A small R8 texture scrolls with the camera. Every frame the GrassTrampleUpdate pass decays it and stamps the active sources; blades sample it and bend away from the gradient, recovering after a couple of seconds.
  • No textures. Blade color, midrib, edge shading and the pointed alpha shape are all procedural in GrassDiffuse, so the asset ships without any image dependencies. SetColorScale dims it for day/night.

Performance

Measured on the demo scene (Demo.Windows, Release, 1080p, vsync off, GrassDistance 130, RTX 4090): a fixed grass-filled view, 15 s average after warmup. One compute dispatch per frame regardless of blade count — the CPU only ever uploads seeds once, so the cost is almost entirely GPU-side.

Seeds Blade instances (× 32) Avg FPS
40 000 (100×100 m) 1.28 M ~300
90 000 (150×150 m) 2.88 M ~140
160 000 (200×200 m) 5.12 M ~95

Measured with the window focused — Windows throttles background windows hard, so bench with the game in the foreground. Ctrl+Shift+P toggles Stride's profiler in the demo (F1 cycles FPS / CPU / GPU pages) to measure on your own hardware. The intended pattern for big worlds is streaming anyway: keep seeds near the camera with SetChunkSeeds/RemoveChunk instead of feeding the whole map at once — instances scale with what's in range, not with the world size.

Demo

Open StrideGrassSystem.sln, set Demo.Windows as the startup project and run. Fly around with WASD + right-mouse. The camera leaves a flattened trail through the field.

License

MIT. See LICENSE.md.

About

GPU-driven grass for Stride: one compute dispatch builds + LOD-culls hundreds of thousands of animated blades from seed points, with wind, a trample field, and procedural shading. No textures.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages