# storm_sheet
Additive upright sheet of slanted rain streaks in patchy gust bands, turned to face the camera about
the vertical (`zero_billboard_axis` with world Y). Dozens of them sweep across the scene on the
wind, which makes the rain read as weather. On each sheet three layers of fine lines run along a
wind-slanted fall direction; every line has its own length and phase and falls with time, so they
read as individual drops. A value-noise gust pattern, drifting with time, makes the sheet thick in
gusts and threadbare between them. The sheet fades in and out over its life and toward its edges.
The streaks are a cool blue `(0.46, 0.56, 0.78)` lifted by `ambient + flash * flash_gain`, scaled by
`density` and faded with distance by `exp(-distance * fog)`.
The `storm` graph (`storm/storm.procGraph`) draws its `sheets` output with it (additive, `flash`
from the storm schedule's flash expression, `flash_gain` 0.9, `ambient` 0.16, `fog` 0.005, and
`density` animated as `0.55 + 0.25 * sin(t * 0.9) + 0.14 * sin(t * 2.3 + 1.7)`): up to 90 sheets 46
to 92 wide, born on a wide disc upwind and carried across on the gusts for 7.5 to 9.5 s each. The
graph writes `data0` as (life, a random seed, 0, 0). In the Procgen Effects Showcase world, the `fx_storm` scene plays the graph.
The shader includes `@builtin::shaderModules.billboard`.
## Properties
| Property | Type | Default | Meaning |
|---|---|---|---|
| `flash` | float | 0.0 | Current strike brightness (range 0 to 8). |
| `flash_gain` | float | 1.0 | Multiplies `flash` (range 0 to 20). |
| `ambient` | float | 0.3 | Base brightness of the streaks (range 0 to 8). |
| `density` | float | 1.0 | Multiplies the streaks; the storm graph swells it with the gusts (range 0 to 8). |
| `fog` | float | 0.004 | Distance fade density (range 0 to 0.1). |
| `vertexBoundsDilation` | float | 46 | How far the vertex hook moves a vertex past the mesh, in world units: the culling bounds grow by it so a moved vertex stays inside them. |
## Lanes
| Lane | Carries |
|---|---|
| `data0.x` | Life, 0 to 1: the fade in and out. |
| `data0.y` | Per-sheet seed: offsets the lines and the gust pattern. |
# lightning_bolt
Additive unlit surface shader for lightning ribbons: a hot white core, a blue-violet halo, and
crawling flicker along the channel. On the ribbon `uv.x` runs along the bolt and `uv.y` across it.
Across the ribbon three falloffs stack: a tight white-hot core `(1.0, 0.97, 0.92)` at 17 times
brightness, a mid glow between the core colour and blue `(0.42, 0.52, 1.0)`, and a wide blue halo,
all softened to zero at the ribbon's edges. Hot spots crawl along the channel from a value noise
over `uv.x`, the storm clock `gt` and the bolt's own `phase`. Everything is scaled by `flash`, the
strike envelope (0 dark, 1 the return stroke), and by `gain`; while `flash` is below 0.003 the
fragments are discarded.
The `storm` graph (`storm/storm.procGraph`) draws one forked bolt per strike in the storm schedule
with it, each as its own output and material (additive, culling off, depth writes off): `flash` is
that strike's envelope expression, `gt` the graph time, `phase` the strike's index, and `gain` is
1.0. Each bolt is a jittered, smoothed, resampled line from the strike's top to its target, branched
three generations deep by `curve.branch`, and drawn as two crossed ribbons 3.4 wide that thin with
each generation. In the Procgen Effects Showcase world, the `fx_storm` scene plays the graph.
## Properties
| Property | Type | Default | Meaning |
|---|---|---|---|
| `flash` | float | 1.0 | Strike envelope, 0 dark to 1 the return stroke (range 0 to 4). |
| `gt` | float | 0.0 | The storm clock in seconds; drives the crawl of the hot spots. |
| `phase` | float | 0.0 | Per-bolt offset of the crawling hot spots. |
| `gain` | float | 1.0 | Multiplies the output (range 0 to 10). |
# storm_puff
Alpha-blended smoke puff on a camera-facing card (`zero_billboard`), with wispy domain-warped edges,
a sooty body, blue-white flash lighting and an ember glow while it is young. A domain-warped
four-octave value noise, drifting with time and offset by the seed, erodes a round body. The puff
fades in over the first 10 percent of its life and out between 0.45 and 1. Its dense core reads as
soot `(0.030, 0.034, 0.045)` and its thin edges pick up a slate sky `(0.13, 0.15, 0.20)`, scaled by
`ambient`. The storm's flash adds blue-white `(0.62, 0.70, 1.0)` light times `flash * flash_gain`,
and the heat lane adds an orange ember glow `(1.0, 0.36, 0.06)`. The alpha is the body times the
fades, the lane's opacity scale and `density`.
The `storm` graph (`storm/storm.procGraph`) draws its `smoke` output with it (transparent, `flash`
from the storm schedule's flash expression, `flash_gain` 1.7, `ambient` 1.0, `density` 0.8): smoke
rising from every strike, dragged downwind and billowing as it ages. The graph writes `data0` as
(life, a random seed, a heat ramp that falls from 1 to 0 by 0.28 of the life, 0.85). In the Procgen Effects Showcase world, the `fx_storm` scene plays the graph.
The shader includes `@builtin::shaderModules.billboard`.
## Properties
| Property | Type | Default | Meaning |
|---|---|---|---|
| `flash` | float | 0.0 | Current strike brightness (range 0 to 8). |
| `flash_gain` | float | 1.0 | Multiplies `flash` (range 0 to 20). |
| `ambient` | float | 1.0 | Multiplies the soot-and-sky shade (range 0 to 8). |
| `density` | float | 0.9 | Multiplies the alpha (range 0 to 4). |
| `vertexBoundsDilation` | float | 6.5 | How far the vertex hook moves a vertex past the mesh, in world units: the culling bounds grow by it so a moved vertex stays inside them. |
## Lanes
| Lane | Carries |
|---|---|
| `data0.x` | Life, 0 born to 1 dead. |
| `data0.y` | Per-puff seed. |
| `data0.z` | Heat, 0 to 1: the ember glow. |
| `data0.w` | Opacity scale; below 0.003 the puff is discarded. |
# storm_streak
Additive billboard streak for rain, splash droplets and sparks: a card that turns about its own Y
axis toward the camera (`zero_billboard_streak`). Colour comes from the element's `data0.rgb`, which
is HDR so sparks can run hotter than 1, and an element whose colour fades to black vanishes (below
0.002 in every channel it is discarded). Across the card the streak is rounded (`1 - across^2`), so
a thin card shows a soft edge; along it a bright head gives way to a long soft tail. The storm's
lightning lifts every card by `ambient + flash * flash_gain`, which makes falling rain flicker into
view in every strike. Distance fades the streak by `exp(-distance * fog)`, the streak fades in
between 1.5 and 9 units from the camera, and the result is scaled by `gain`.
The `storm` graph (`storm/storm.procGraph`) builds three additive materials from it:
| Use | `flash` | `flash_gain` | `ambient` | `fog` | `gain` |
|---|---|---|---|---|---|
| Rain | storm flash expression | 1.0 | 0.11 | 0.010 | 1.0 |
| Splash droplets | storm flash expression | 1.6 | 0.7 | 0.0045 | 1.0 |
| Sparks and the smaller sparks they throw | 0 | 0.0 | 1.0 | 0.003 | 1.0 |
For each, the graph turns the particle onto its velocity, sets its length from its speed and writes
`data0` as a colour times a fade that takes a slow particle to black: wind-driven rain in a cool
blue at random brightness, splash droplets thrown up where rain lands, and strike sparks cooling
from `(11, 9, 6)` to black. In the Procgen Effects Showcase world, the `fx_storm` scene plays the graph.
The shader includes `@builtin::shaderModules.billboard`.
## Properties
| Property | Type | Default | Meaning |
|---|---|---|---|
| `flash` | float | 0.0 | Current strike brightness (range 0 to 8). |
| `flash_gain` | float | 1.0 | Multiplies `flash` (range 0 to 20). |
| `ambient` | float | 1.0 | Base brightness of the streak (range 0 to 8). |
| `fog` | float | 0.006 | Distance fade density (range 0 to 0.1). |
| `gain` | float | 1.0 | Multiplies the output (range 0 to 20). |
| `vertexBoundsDilation` | float | 0.05 | How far the vertex hook moves a vertex past the mesh, in world units: the culling bounds grow by it so a moved vertex stays inside them. |
## Lanes
| Lane | Carries |
|---|---|
| `data0.rgb` | HDR colour; black discards the element. |
# schedule
The storm's clock. Every strike is a pure function of the storm phase, so the storm graph (as
expressions over its own time) and the director (as Luau over the generator's clock) agree exactly,
and the whole storm loops every `PERIOD` seconds.
Two assets require it: the `storm` graph beside it (`storm.procGraph`, which builds its flash, bolt and burst
expressions from `STRIKES`, `flashExpr`, `envelopeExpr` and `windowOne`) and the `StormDirector`
component of the Procgen Effects Showcase world (lights, materials and thunder from `phase`, `envelope`, `afterglow` and `STRIKES`).
## Exports
### Values
- `M.PERIOD`: `13.6`, the loop length in seconds.
- `M.OFFSET`: `5.006`, the phase the storm is in at graph time 0. A strike has just landed there, so
a paused frame is a hero frame.
- `M.STRIKES`: the four strikes, each `{ t, kind, target, top, variant, weight }`. `t` is the phase
the strike lands at, `target` where it lands, `top` where the bolt starts, and `weight` its
brightness weight. `kind` is `"ground"` (throws sparks and smoke where it hits) or `"tower"` (hits
the finial). `target` and `top` are in the storm graph's own space, where the tower stands at the
origin; in the world they stand where the entity playing the graph does.
| `t` | `kind` | `target` | `top` | `variant` | `weight` |
|-----|--------|----------|-------|-----------|----------|
| 1.6 | ground | `{ 15, 0, -8 }` | `{ 3, 135, -2 }` | 1 | 1.0 |
| 5.0 | tower | `{ 0.05, 48.6, 0.1 }` | `{ -7, 135, 5 }` | 2 | 1.2 |
| 8.6 | ground | `{ -19, 0, 7 }` | `{ -30, 135, 12 }` | 3 | 1.0 |
| 11.0 | tower | `{ 0.05, 48.6, 0.1 }` | `{ 9, 135, 6 }` | 4 | 1.3 |
- `M.PULSES`: the return strokes of a strike, each `{ delay after the leader, strength, decay rate
per second }`: `{ 0.00, 1.00, 30 }`, `{ 0.11, 0.80, 26 }`, `{ 0.24, 0.62, 20 }`,
`{ 0.43, 0.36, 11 }`.
### Functions
- `M.phase(t)`: the storm phase at graph time `t`, `(t + OFFSET) % PERIOD`.
- `M.envelope(ph, s)`: how bright strike `s` is at phase `ph`: 0 before the strike, about 1 at the
return stroke. It sums every pulse whose delay has passed, each decaying exponentially at its rate.
- `M.afterglow(ph, s)`: the cloud glow that lingers after strike `s`, at phase `ph`:
`0.55 * exp(-d * 1.7) * min(d * 20, 1)` for `d` seconds after the strike, 0 before it.
- `M.envelopeExpr(s)`: the same envelope as `envelope`, written as an anim expression string over
`t`.
- `M.afterglowExpr(s)`: the same afterglow as `afterglow`, as an expression string over `t`.
- `M.flashExpr()`: the scene-wide flash as an expression string: the sum of every strike's
envelope times its weight, capped at 3.
- `M.windowExpr(only, lag, rise, fall)`: a burst of activity `lag` seconds after each strike, as a
bump expression in [0, 1]: it rises with `min(d * rise, 1)` and falls with `exp(-d * fall)`.
`only` limits it to strikes of one kind (`"ground"` or `"tower"`); `nil` includes every strike.
- `M.windowOne(s, lag, rise, fall)`: the same bump for strike `s` alone.
## Usage
```luau
local S = require("~.storm.schedule")
local generator = entity.find("storm").component("Generator")
local ph = S.phase(generator:getTime())
local flash = 0
for _, strike in ipairs(S.STRIKES) do
flash += S.envelope(ph, strike) * strike.weight
end
-- the same flash, as an anim expression for a procGraph
local flashExpr = proc.anim.expr(S.flashExpr())
-- a burst 0.15 s after each ground strike
local bursts = S.windowExpr("ground", 0.15, 12, 1.0)
```
# effectShowcase
The procedural effects of the Procgen Effects Showcase world. Each one is a `.procGraph` that plays
through a `Generator`, and every shader, material and module a graph draws with sits beside it in
this package, so the package carries everything its graphs need. Each graph's README says what it
makes, how to place it and what each of its inputs changes.
## Contents
| Folder | Graphs | What they make |
|---|---|---|
| `combat/` | `impact`, `muzzleFlash`, `tracer` | A round striking a hard surface, the bloom at a barrel as a weapon fires, and the glowing streak of a round in flight. They draw with `fxFlash`, `fxSmoke` and `fxSpark` in `combat/shaders/`. |
| `blast/` | `wall_blast` | A charge blowing a breach through a stone block wall, on a 9.6 s loop. |
| `storm/` | `storm`, `tower` | A thunderstorm around a lone tower: wind-driven rain, lightning striking on the schedule `schedule.module` keeps, and sparks and smoke where strikes land. `tower` is the stone keep it stands around. |
| `rift/` | `arcane_rift` | A rift tearing open in the ground, its motes and rubble moved by the compute shaders in `rift/compute/`. |
| `dragon/` | `dragon_breath` | A dragon's fire breath sweeping a courtyard: a flame jet, rolling smoke and drifting embers. |
| `launch/` | `launch` | A heavy-lift rocket liftoff on one looping timeline, with the heat-haze material `rk_haze_mat`. |
| `waterfall/` | `wf_env`, `wf_water`, `wf_spray`, `wf_foam`, `wf_mist` | A waterfall built in parts: the cliff, basin and boulders, the falling and still water, the droplets, the foam where the falls land, and the mist with its rainbow. Its water materials are in `waterfall/materials/`. |
| `shaders/` | | `fx_noise`, the noise functions the waterfall and wall blast shaders include. |
## Playing one
A graph plays on a `Generator` component bound to it:
```lua
-- `graph` is the graph's reference: the AssetRef `asset.resolve` or a search returns for it.
local e = entity.spawn("impact", { position = { 0, 1, 0 } })
e.component.add("Generator", { graph = graph, playOn = "load" })
```
Set a graph's inputs with the Generator's `params`, e.g. `params = { size = 3 }`.