Foliage Pack Demo
App: apps/foliage_pack_demo/
Demonstrates the Grass / Foliage / Trees content pack (engine PLM-266): stylized,
instanced grass, foliage and trees with wind sway and back-light
translucency (leaves glow when the sun is behind them), built with zero engine
source changes. It is the fourth content pack of the Environment & Stylization
program (Toon → Sky → Weather → Foliage → Water). Like the
Toon, Sky and
Weather packs, it is pure content — the whole look is one
instanced .slang shader riding capabilities the engine already ships.
Run from root
Section titled “Run from root”% ./plume3d foliage_pack_demoA wind-swaying grass field plus a few procedural trees (a lit trunk with a canopy of
instanced leaf cards) render under the foliage shader; the sun behind the leaves makes
them glow. The demo captures a screenshot (foliage_pack.png) at frame 70 and exits at
frame 90.
The Blender-authored workflow (the production path)
Section titled “The Blender-authored workflow (the production path)”Grass, foliage and trees are authored in Blender and imported. This pack ships the
look — foliage.slang — that the Blender-imported vegetation renders with. The demo’s
procedural field only exists to exercise that shader in isolation; a real game builds its
vegetation like this:
- Author in Blender — model a grass blade / leaf card / tree (trunk + canopy) and scatter it across the terrain with geometry-nodes, particle hair, or collection instances.
- Bake Scatter — the
plume3d_wrenBlender add-on’s Bake Scatter operator (engine PLM-250) bakes the depsgraph scatter down to a baked instance table (plume3d_scatter_proto+plume3d_scatter_xforms, ADR 0087). Where things scatter is controlled by a mask (shipped, PLM-268) — a geometry-nodes density mask the bake captures automatically, or the add-on’s top-down grayscale Scatter mask image (white = spawn, black = clear, gray = sparse), resolved at bake time. - Import —
Scene.loadBlendScene(path)(orBlendResult.instantiateScatter(scene)directly) emits one GPU-instanced draw per prototype — the same enhanced-instancing path this demo fills by hand, but authored in Blender. See the Blend Scatter Demo and Foliage + Terrain Demo. - Render with this pack — load
shaders/foliageas the active shader before drawing, so the imported instanced vegetation gets wind + translucency.
Graphics.loadShader("shaders/foliage") // FIRST → the active instanced foliage shadervar blend = _scene.loadBlendScene("Models/Forest.blend") // terrain + baked grass/tree scatter// ...the baked scatter renders as instanced foliage under the active shaderAn instanced prototype must render with the active shader — a per-mesh
newMesh(.., shader) on an instanced prototype currently breaks the instance SSBO (engine
PLM-264), so the foliage look is a set-active-shader, not a per-object assignment,
until that is fixed. The Weather pack shares the same instanced
descriptor discipline.
Trees, specifically. A tree is authored in Blender as a prototype (trunk + leaf canopy) and scattered into a forest like any other prototype. The tree-specific authoring needs are roadmap, not shipped in v1:
| Need | Status |
|---|---|
| Multi-part prototype (trunk mesh + canopy) | PLM-266 follow-up (authored-tree bake) |
| Colliders so you can’t walk through trunks | PLM-267 shipped for authored objects — tag a tree/rock/prop plume3d_collider (box/sphere/capsule/convex/mesh) and instantiateUnits builds a static Jolt collider. Per-instance colliders for GPU-scattered foliage remain a follow-up (ADR 0090 non-goal — thousands of bodies). |
| Spawn masking — control where grass/trees appear | PLM-268 shipped — a geometry-nodes density mask (captured by the bake) or the add-on’s top-down grayscale Scatter mask image (white = spawn / black = clear / gray = sparse), a bake-time Blender input the engine never sees. |
| Distance LOD / billboard imposters for far trees | PLM-266 follow-up (needs the PLM-024 LOD system) |
What’s in the pack (all content — no engine change)
Section titled “What’s in the pack (all content — no engine change)”| File | Kind | What it is |
|---|---|---|
shaders/src/foliage.slang | instanced shader | Wind sway (the A#1 frame time + global wind + a per-instance phase so blades don’t move in lockstep) + a height gradient (darker at the base, brighter at the tip) + two-sided leaf lighting (thin leaves lit from both faces) + back-light translucency (leaves glow warm-green when the sun is behind them). Hand-declares its minimal descriptor set (set-0 view/proj + frame block, set-2 instance SSBO) — it must not #include "plume3d.slang". |
main.wren | example | A grass field plus a few procedural trees (a lit trunk with a canopy of instanced leaf cards), all foliage in one InstancedMesh, swaying in the wind. |
shaders/src/{scene,sky}.slang | reused | The stock lit ground/trunk shader and a gradient sky. |
How the demo drives it
Section titled “How the demo drives it”1 — Load the foliage shader FIRST so it is the active shader for the instanced foliage, then the lit ground/trunk shader and the sky:
Graphics.loadShader("shaders/foliage") // FIRST → the active instanced foliage shader_sceneShader = Graphics.loadShader("shaders/scene")Graphics.loadShader("shaders/sky")2 — Set the sun and atmosphere with the neutral A#6 knobs and a global wind the foliage bends with:
Graphics.setAmbient(0.34, 0.40, 0.46)Graphics.setFog(0.62, 0.76, 0.92, 0.0, 42.0, 130.0)Graphics.setSky("shaders/sky", 0, [/* horizon */ 0.55, 0.70, 0.92, 0.0, /* zenith */ 0.25, 0.45, 0.82, 0.0])Graphics.setWind(1.0, 0.0, 0.45, 1.1) // the foliage sways along this3 — Fill one InstancedMesh with a blade/leaf-card prototype — the grass field and every
tree’s leaf canopy are all instances in the same mesh, each carrying a per-instance
colour and a per-instance wind phase via
enhanced instancing:
_foliage = _scene.createInstancedMesh("foliage")_foliage.setMesh(_blade) // a crossed-quad blade/leaf card; no per-mesh shader → active foliage shader_foliage.useGpuInstancing = true
var idx = _foliage.addInstanceColored(x, 0.0, z, scale, r, g, b)_foliage.setInstanceParams(idx, 1.0, phase, 0.0) // params: speed, windPhase, modeEach tree is a lit trunk (MeshGen.cylinder) drawn with the scene
shader, with ~60 leaf-card instances added to the shared foliage mesh around its crown — so
a whole forest’s foliage is still one instanced draw.
The engine capabilities it stands on (all already shipped)
Section titled “The engine capabilities it stands on (all already shipped)”Nothing the pack touches is new engine surface:
- Global frame + wind uniform (Block RFX A#1) — the
timethe GPU sway reads and thewindthe foliage bends with, viaGraphics.setWind. - Enhanced GPU instancing (Block SPL) — a whole field of per-instance blades in one draw,
with a per-instance wind phase, through
InstancedMesh.addInstanceColored+setInstanceParams. - Blender scatter import (Block BLD) — the production path:
Scene.loadBlendScene/BlendResult.instantiateScatteremit the baked Blender vegetation as instanced foliage. - A#6 atmosphere knobs —
setSky/setAmbient/setFogfor the sky and haze.
How it ties into the rest of the program
Section titled “How it ties into the rest of the program”- Wind —
Graphics.setWind; the foliage bends with it. A gust curve is Weather-pack policy. - Rain / wetness — a game sets a wetness the foliage shader can darken toward (rain-density-aware; a PLM-266 follow-up).
- Colliders — shipped (PLM-267) for authored objects: an authored tree/prop blocks
movement via a Blender-authored, configurable Jolt collider
(
plume3d_collider); per-instance colliders for GPU-scattered foliage remain a follow-up. - Masking — shipped (PLM-268): control where grass/trees spawn from a geometry-nodes density mask or the Bake-Scatter add-on’s grayscale mask image, resolved at bake time.
- Audio — wind-in-foliage ambience via the shared audio hooks (PLM-272).
- LOD / billboards — distant trees swap to imposters (a PLM-266 follow-up; needs the PLM-024 LOD system).
- InstancedMesh — per-instance colour, params & culling
—
addInstanceColored/setInstanceParamsfill the foliage field. - Graphics — Global frame uniforms —
setWindand the host frame clock the foliage shader reads for its sway. - Graphics — Sky, fog & ambient (A#6) —
setSky/setAmbient/setFog, the atmosphere behind the scene. - Scene — Loading a Blender scene & BlendResult — instantiateScatter — the Blender-authored production path.
- The mesh vertex/frame bindings the instanced
foliageshader hand-declares (set-0 view/proj- the frame block, set-2 instance SSBO) — the same discipline the
Weather pack’s
precipshader uses.
- the frame block, set-2 instance SSBO) — the same discipline the
Weather pack’s
Notes and limitations
Section titled “Notes and limitations”- Content pack, not an engine feature. The whole look is one
foliage.slangon the instanced path — the engine gains nothing. Packaging and licensing for a shippable pack are owner/business decisions; this in-repo demo is the exercised, gate-covered reference for the shader. - The demo trees are procedural placeholders. A trunk cylinder with a leaf-card canopy — enough to exercise the shader. Production grass and tree art is Blender art flowing through the workflow above.
- Foliage is alpha-cutout-ready but the demo blades are opaque cards — no leaf texture yet. A leaf cutout is a set-5 custom texture a pack drops in (see the Custom Material Pack).
- Fixed key-light direction. The shader lights the leaves from one fixed sun direction; a pack could pass the direction in. Wiring a scene light into the pack shader is left to the consuming game.
- Roadmap, not shipped: multi-part authored-tree prototypes and distance LOD / billboards (needs PLM-024) are tracked as their own tickets — this v1 ships the foliage shader and the authoring path. (Both spawn masking — PLM-268 — and authored-object colliders — PLM-267 — now ship; per-scattered-instance colliders stay a follow-up.)