js/grass.js: reusable single-file grass module for all our games — GPU-instanced blades (33k+ in one draw call), vertex-shader wind hashed per blade, root->tip fake-AO gradient x per-instance tint, alpha-test pixel style (nearest-filter 16x24 canvas cards, stepped sway), deterministic seed, player-push uniform. docs/GRASS.md records the technique ladder (clump maps, shell texturing, billboard LOD, MB atlas gen) for future tiers. Auto-grows on parkland + islands, PARK panel style/density, included in exported levels via buildParkScene. Test ride: grind physics — falling onto any flattened steel (rails, ledge caps, pool coping) locks a slide along the segment, pop off with Space or ride off the end; grass parts around the rider. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
60 lines
3.2 KiB
Markdown
60 lines
3.2 KiB
Markdown
# GODVERSE grass — the module and the tricks
|
||
|
||
`js/grass.js` is the canonical grass system for all our games. It's a **system, not a
|
||
mesh** — that's why it lives as a code module with style presets, not as GLBs on the
|
||
farm. Lift the single file into any three.js project:
|
||
|
||
```js
|
||
import { makeGrass } from './grass.js';
|
||
const grass = makeGrass({ heightAt, areas, style: 'lush', density: 8 });
|
||
scene.add(grass); // self-ticking wind; add and forget
|
||
grass.userData.setPlayer(x, z); // optional: grass parts around the player
|
||
```
|
||
|
||
Styles: `lush` (tapered instanced blades), `dry` (same, straw palette),
|
||
`pixel` (crossed alpha-test cards, 16×24 nearest-filter canvas texture, banded
|
||
palette, stepped sway — PS1/N64 vibe).
|
||
|
||
## The tricks it uses (why it's fast)
|
||
|
||
1. **GPU instancing** — one `InstancedMesh` = one draw call for up to 40k blades.
|
||
Per-blade cost after spawn is zero on the CPU.
|
||
2. **Vertex-shader wind** — `sin(time + hash(blade origin))`, bend scaled by
|
||
`uv.y²` so roots stay planted, tips fly. Two frequencies layered so it doesn't
|
||
look like a metronome. Pixel style floors the sway to 3 steps.
|
||
3. **Root→tip gradient with darkened base** — geometry vertex colors (0.35 at the
|
||
root → 1.15 at the tip) multiply with a per-instance tip tint. Reads as ambient
|
||
occlusion without any lighting cost. *The single biggest visual win.*
|
||
4. **Per-instance jitter** — scale, rotation, lean, hue offset. Uniformity is what
|
||
makes cheap grass look cheap.
|
||
5. **alphaTest, never alpha-blend** — no depth sorting, no overdraw melt.
|
||
6. **No per-blade shadows** — the base darkening does that job.
|
||
7. **Deterministic seed** (mulberry32) — same park, same lawn, every load.
|
||
8. **Player-push uniform** — blades within 1.2m bend away from the rider. Sells
|
||
physicality for one uniform update per frame.
|
||
|
||
## The next tiers (when a game needs bigger fields)
|
||
|
||
- **Chunked frustum culling** — split into 16m tiles, cull per-tile. Needed beyond
|
||
~100m fields; our parks don't need it yet.
|
||
- **Far-field billboard cards** — crossed quads with a grass-clump texture past
|
||
~40m, instanced blades near. Classic LOD split.
|
||
- **Clump maps** (Ghost of Tsushima GDC 2021, "Procedural Grass in Ghost of
|
||
Tsushima") — sample a Voronoi clump texture: blades in a clump share lean/height,
|
||
clump centers get taller blades. Breaks the "even lawn" look at scale.
|
||
- **Shell texturing** (Acerola's "How Do Games Render So Much Grass?") — N stacked
|
||
transparent shells; dot patterns at increasing height thresholds. Ultra cheap
|
||
retro turf/fur; a natural fourth style for the pixel games.
|
||
- **Bezier blades + tessellation** for hero close-ups (Sucker Punch use 3-4 verts
|
||
per blade too — verts are not where the money is; shading is).
|
||
- **MODELBEAST atlas textures** — flux_local can generate blade/clump atlases for
|
||
the billboard tiers (`grass blade atlas, alpha background, hand-painted style`),
|
||
then `bg_remove_local` for clean alpha.
|
||
|
||
## In SKATEMAKER PRO
|
||
|
||
Grass auto-grows on the parkland ring outside the slab and on ISLAND pads, from
|
||
`park.grass` (`{style, density}` — PARK panel when nothing is selected). Exported
|
||
levels include it via `buildParkScene`; the editor rebuilds it on committed ground
|
||
changes only (never mid-drag).
|