Join the Shadow Venom's Workshop Discord Server!
Check out the Shadow Venom's Workshop community on Discord - hang out with 3292 other members and enjoy free voice and text chat.
ForgeLab
Things VAM did not have, built so that dropping them on an atom is the whole setup.
Things VAM did not have, built so that dropping them on an atom is the whole setup.
Or maybe some of these things did exist before, but even for me, they were simply too complicated to actually use.
Which is also why nothing here is a prop pack, and nothing here is a simplified version of something else: there was nothing to simplify. These are capabilities added to VAM, generated in code at runtime, with no Unity in the loop and nothing extra for you to install.
The design philosophy: two halves, and the whole point is that you never have to pick one
Plugins usually choose. Either it is simple and you find its ceiling in an afternoon, or it is powerful and you spend that afternoon reading before anything appears on screen. Refusing that choice is the shape of this entire line.
If you just want to play, it is already done. The test we hold ourselves to is specific: load the plugin, do nothing else, and the right thing is on screen. No asset to find, nothing to model, nothing to point at anything. If you have to configure it before it works, we got the defaults wrong, and that is a bug on our side rather than a step on yours.
If you are building something serious, the ceiling is a long way up. Nothing is hidden to keep a panel tidy, and no control gets deleted because the list got long. Every parameter is an ordinary VAM storable, so all of it is drivable from Timeline, from triggers, and from other plugins.
A player never meets the complexity. A creator never has to fight the simplicity.
| Plugin | What it is |
|---|---|
| Volumetric light you can see. Nine plugins across whole-sky shafts, window god rays, follow spots, full stage rigs and cold pyro, generated in code, spending none of VAM's six pixel lights unless you ask. | |
| Every kind of tail, in one plugin, with real physics. Drop it on a Person and it is already moving. | |
| Steam off her skin, water running down it, and room-scale volumetric fog that takes the shape of anything already in your scene. | |
| Glass that reflects the actual room, bends what is behind it, fogs over so you can wipe it with a hand, and breaks when you hit it. Flat, curved, or a tube around her. | |
| A beach that remembers. Sand that takes her weight and keeps the shape, a sea that wets it, foams on it and comes back for the prints, and sand on her skin that falls when she moves and comes off under your hand. Generated from one Empty atom, no asset. |
More in the line. Each one ships finished, plug and play, and measured.
V1.0
Every beach so far has looked like one. This one feels like one.
Sand that takes her weight and keeps the shape. A sea that comes back for it.
Sand on her skin that falls when she moves and comes off under your hand.
Two plugins. Drop them in and the beach starts remembering.
Part of ForgeLab by Shadow Venom.
She comes up out of the water toward you, unhurried. The sea does not want to let her go: it runs off her in sheets, it climbs her calves one last time, the surface behind her is still ringed where she pushed through it. Every step she takes up the beach leaves a print, and the prints change as she comes: sharp and shallow in the wet band, then deeper and softer where the sand is dry and loose and gives under her.
She lies down beside you and the sand takes her. Not on top of it, into it: the beach sinks under her hips and her shoulders, the edge of the hollow slumps a little as it settles, and the shape she makes is the shape of her.
When she rolls toward you the sand comes with her. It is on her back, the backs of her thighs, packed dark and heavy where her skin is still wet, dusted fine and pale where she has dried. You reach over and brush her shoulder. Where your hand goes, the sand goes. She laughs and shakes it out of her hair, and for a second the air between you is full of it, catching the light.
Then a wave you did not see coming reaches all the way up. It slides under her feet and stops, lace-edged, and when it pulls back it takes half of her footprints with it and leaves a line of foam on the sand that will be gone by the time the sun is.
Tomorrow the beach will be smooth again. Tonight it remembers.
Now count how much of that we could never do before. The ground did not move: lie down on a CUA beach and you lay on stone with a sand texture. The sea never came up the beach and never took anything back. Nothing stuck to her, nothing fell off her, and your hand had never once changed what was on her skin.
The scene looks like a beach. It never feels like one.
SeaForge is the sand, the sea, and what her body leaves in both.
Two plugins. One goes on the beach, one goes on her. Neither asks you to model anything, bake anything, or find a single asset. Add them and it is already happening.
And every part of it can be switched off independently, including all of it.
It takes her weight, and it keeps the shape
EnvBeach Β· goes on an Empty atom. The atom is the middle of the beach.
Sand, until now, was a CUA: a texture on a floor. This is a field: a layer of sand with a thickness, sitting on your scene's floor, that every part of a body pushes into.
Prints where she touches, as deep as she pushes. Feet, shins, hips, the length of a thigh or a forearm. Dry sand gives about 7 cm under a foot; wet sand, packed by the sea, gives about 1.5 cm and holds a sharper edge. Nothing is stamped from a library. The mark is the body, where the body was.
Then the sand does what sand does. The rim of a print slumps to the angle of repose, dry and wet at different angles; loose edges creep; the sharp shape she left goes soft the way it does on a real beach after a minute. Settling runs on its own clock, so a print an hour old and a print a minute old do not look the same.
Lie down and the beach takes you. The whole body prints, not just the feet. Get up and the hollow stays, edges settling, until the sea reaches it.
A beach, not a plane. It rises inland, falls seaward, undulates a little, and every one of those is a slider. Terrain relief off gives you a perfectly flat one if that is the shot.
It reads as sand at every distance. Grain, glitter, the dark minerals, the broad wet sheen: the surface is built so that a close-up and a wide shot are both the same sand. Colour it, dry and wet, with two pickers.
The line the sea draws on the sand
EnvBeach, same plugin. One band of beach that is not the same sand as the rest.
Look at any beach and the first thing you read is not the sea. It is the line where the sand changes: dark and packed and shining below it, pale and loose above it. That line is what tells you where the water has been, and it is the thing a sand texture can never have, because it moves.
The sea writes it. Every wave that runs up the beach leaves a band of wet sand behind it, and the wet climbs a little above where the water stopped, the way it does when sand draws water up into itself. The band is wherever the sea has actually reached, wave by wave, not a stripe you painted.
And the sand remembers it. Wet sand dries out over the minutes after the water leaves, from the top of the band down, so a beach that the sea has pulled back from shows exactly how long ago it was there. Wetness memory is a switch; off, the band snaps to the water and forgets.
Wet sand looks like wet sand. Darker, glossier, smoother: the grains pack, the surface loses its fine ripple, and it takes a sheen off the sky and your lights. Dry sand next to it stays matte and loose and paler. Two colours, two speculars, and the boundary is where the water decided it is.
It changes what a body does to it. A foot sinks about 7 cm into dry sand and holds a soft edge; the same foot in the wet band goes in about 1.5 cm and holds a crisp one. Walk from the water up the beach and your own trail shows the change.
And what it does to her. Sand from the wet band packs onto her skin dark and heavy; sand from the dry beach dusts on pale. SkinBeach reads the same wetness the beach does.
It comes back for everything
EnvBeach, same plugin. Draw the sea is one switch.
The sea, until now, was a CUA surface you placed next to the sand and hoped nobody walked to the edge. This one is part of the same field. It knows where the sand is, the sand knows where it is, and the two of them keep a running account of who was where.
The swash comes up the beach on its own rhythm, cusped along the shore the way real run-up is, reaching further on some waves than on others, and every wave leaves the wet band above described a little higher or a little lower than the last.
It erases. Prints inside the wet band are worn down a little by every wave that crosses them. Walk along the waterline and watch the sea take your trail back, one wave at a time.
Foam is a field, not a sticker. It is born where the wave breaks, carried in on the bore, left behind as lace when the water drains, and it dries on the sand or is picked up again by the next wave. It piles up where the water slows, exactly where foam piles up on a beach, and the front of it is never a hard line.
When she is in the water, the water answers. Bubbles stream off her limbs and cling to her for a while, rings spread from wherever she moves, and a fast move throws a sheet of spray. Each is its own switch, and each is free when it is off.
Swell, breakers, glitter, a horizon. The surface itself: rolling swell with a direction and a spread, breaker foam on the crests, sun glitter you can tighten or loosen, three colours from the shallows to the horizon, and a render queue slider for when it has to sort against glass.
The beach comes with her
SkinBeach Β· goes on a Person atom. Finds the beach by itself.
Sand does not stay on the beach. It gets on her, and it gets on her where she touched it.
Contact is what sticks it. Wherever her skin goes below the surface of the sand, sand comes back up with it. The longer she stays, the more she carries. Nothing is painted on; it is the beach's own height field read against her body, so a shoulder that never touched the ground stays clean and the hip that took her weight does not.
Wet skin holds it darker and heavier. Where she is still wet from the sea the sand packs into clumps and darkens; where she has dried it sits as a fine pale dust. The wet look is hers to tune: darkening, colour, how much it clumps.
It is sand up close. Grains with a size and a relief, clumps with a scale, dust between them, lit by your scene's lights. Coverage is one slider if you want less of it.
Clothing keeps it off. Scan what she is wearing once and covered skin stays clean, per item and per part, with a soft edge at the hem. Untick a wet T-shirt and it collects sand like skin does.
It falls when she moves. It comes off under your hand.
SkinBeach, same plugin. Three switches, all off until you want them.
This is the part you will keep coming back to, because it is the part that answers you.
Wipe-off. Her hands, or yours, sliding over her skin take the sand with them. Brush her shoulder and the shoulder is clean where the hand went and sandy where it did not. In VR that hand can be your own. It is the first thing in this line where somebody else's touch changes what is on her.
Shake-off. Motion sheds sand: a fast rise, a turn, a shake of the hair, and it leaves her, more from dry skin than from wet, which holds on. Leave it off while she lies in the sand collecting it, then switch it on the moment she gets up. That is a shot, and the switch is on the panel for exactly that reason.
Airborne sand. What comes off her does not vanish. It becomes grains in the air, with weight and drag and the light through them, thrown in the direction the limb was moving.
Everything above is an ordinary VAM storable, so all of it is a Timeline cue. The ones worth knowing by name:
Shake-off ON at the frame she rises. Everything she gathered leaves her at once.
Clear all marks (EnvBeach) between takes; Clear the sand off the skin (SkinBeach) likewise.
Stamp the whole body now: she was posed onto the sand already lying there, and you want the beach to know it without her moving.
Level the sand under the person: she lies on a slope and you want the beach flat where she works, and free to rise beyond her.
Time of day: day, night, or your own. One dropdown moves the built-in light, the sky and the exposure together.
The five-minute beach.
Empty atom where the beach should be β EnvBeach. Sand, sea and surf are there. Rotate the Empty so +Z points inland; the shoreline follows.
Walk her onto it. Prints appear. Lie her down. The hollow appears.
SkinBeach on her. It finds the beach by itself. Sand is on her where she touched it.
Time of day on the LIGHT page, or leave it on custom and light it yourself. Name one or two of your scene's lights as Accepted light A/B and the sand takes their colour and their shadow.
Switch on Shake-off and Wipe-off when the scene has motion in it. They ship off because a beach where nothing has happened yet has nothing to shed.
Then wait for a wave. The beach is at its best after the sea has been over it once.
LumenForge SkyRay puts the sun over it: full-screen shafts cut by your palms and rocks, and cloud shadows drifting across the sand. The shot at the top of this page is the two of them together. EnvBeach's own built-in light and SkyRay's direction are set by eye to match, and a beach is where that takes ten seconds.
SteamForge SkinWater on the same Person: water running down her, sand packing where it is wet. Two Skin plugins, one body, and both carry a Render queue slider for the order they draw in.
Where this is going next.
Everything on this page happens from the beach looking out. The sea is a surface, and the camera stays above it.
That is a choice for v1, not the shape of the product. The same field that knows where the sand is and where the water reaches also knows how deep the water is at every point, and that is the floor of the next thing: going under. Light falling through the surface in moving shafts, the sand ripples on the bottom, her hair and the bubbles she trails up toward the light, and the sea seen from where she is instead of from where you stand.
No date, no promises. It is not finished and it is not being announced yet. But when the sea in v1 seems to stop at the waterline, that is where it stops, and this is why.
EnvBeach β add an Empty where the middle of the beach should be β Plugins β Add β EnvBeach.cslist. The beach appears around it, sea included.
There is no beach asset. The whole beach is generated from that one Empty. No mesh to import, no prefab, no CUA to find, nothing to keep in sync: the sand, its relief, the waterline, the sea and the surf are all built in code at load from the sliders on the panel. Which is the point rather than a curiosity. A prefab beach is the shape somebody else chose; this one is whatever you set, and every one of those settings is a storable. Wider, steeper, flatter, a different waterline, a different sea, a different colour, at run time, from a trigger, halfway through a scene. You are not decorating a beach. You are specifying one.
SkinBeach β select the Person β Plugins β Add β SkinBeach.cslist. It finds EnvBeach on its own. With no EnvBeach in the scene, set Sand height from to the manual slider and give it a floor height.
Same grammar as the rest of the line. Env-plugins go on an Empty, Skin-plugins go on a Person, exactly like EnvFog and SkinWater. If you have used SteamForge you already know where everything is.
Master Switch on every page, and one switch in front of each thing that costs: the sea, the foam, the bubbles, the splash, the ripples, the thrown sand, the relief. Off is off.
Several people? Tick All Person atoms and everyone in the scene leaves prints, up to four. Off, it is the one you pick in the dropdown.
Everything is a normal VAM storable, so every switch and slider is drivable from Timeline and triggers.
Both plugins have a Copy report button. If something does not appear, paste that into the thread instead of describing it. It prints the version, what is bound, every gate, every control's current value and the live counts, and it usually answers the question in one message.
Reference for scene creators building on top of the plugins. If you only play, nothing below is needed: the panel is the whole interface.
Every control below is an ordinary VAM storable on the plugin: address it from Timeline, triggers or another plugin as
EnvBeach (on the Empty atom)
SAND
FOOTPRINTS
SEA
SURF
LIGHT
Actions (buttons; fire them from a trigger)
Also registered, not on the panel (instruments, kept as API):
SkinBeach (on the Person atom)
SAND SHELL
STICKING
SHEDDING
CLOTHING MASK
Actions (buttons; fire them from a trigger)
Also registered, not on the panel (instruments, kept as API):
Every control below is an ordinary VAM storable on the plugin: address it from Timeline, triggers or another plugin as
plugin#N_ShadowVenom.EnvBeach / plugin#N_ShadowVenom.SkinBeach followed by the id. Floats are sliders, bools are switches, choosers take the option string exactly as written (the leading digit is the part that matters). Shown while is the panel section the control belongs to; the parameter works whether or not it is currently on screen. Ids are frozen: a scene saved today will restore in every later version.EnvBeach (on the Empty atom)
SAND
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
beachReliefOn | bool | true | Terrain relief | |
sandColourDry | color | Dry sand colour | ||
sandColourWet | color | Wet sand colour | ||
sandCompactionDarkening | float | 0.32 | 0 .. 1 | Compaction darkening (dry sand) |
sandCompactionPaling | float | 0.7 | 0 .. 2 | Compaction paling (wet sand) (wet) |
sandGrainDetail | float | 1 | 0 .. 1.5 | Grain detail (0 = flat) |
sandGrainSizeM | float | 0.0006 | 0.0002 .. 0.004 | Grain size (m) |
sandGrainColourVar | float | 0.55 | 0 .. 1.6 | Grain colour variance |
sandDarkMineralFrac | float | 0.05 | 0 .. 0.3 | Dark mineral fraction |
sandMicroOcclusion | float | 0.12 | 0 .. 0.8 | Micro-occlusion |
sandGlitterStrength | float | 4 | 0 .. 14 | Glitter strength (0 = off) |
sandGlitterFraction | float | 0.3 | 0 .. 1 | Glittering grain fraction |
sandGlintSize | float | 0.34 | 0.05 .. 0.9 | Glint size (fraction of a grain) |
sandGlintThreshold | float | 0.86 | 0.3 .. 0.995 | Glint threshold (higher = rarer) |
sandFacetTiltSpread | float | 0.55 | 0.05 .. 1.5 | Facet tilt spread |
sandDepthM | float | 0.12 | 0.07 .. 0.2 | Sand depth (m) |
sandDetailWavelengthM | float | 0.09 | 0.005 .. 1 | Coarsest detail wavelength (m) |
sandDetailSlope | float | 0.135 | 0 .. 1.2 | Detail slope |
sandDetailOctaves | float | 8 | 1 .. 8 | Detail octaves |
sandDetailRolloff | float | 0.86 | 0.4 .. 1 | Slope roll-off per octave |
wetSmoothing | float | 0.88 | 0 .. 1 | Wet sand smoothing (0 = off) (wet) |
sandSwashMarks | float | 0 | 0 .. 2 | Swash marks (0 = off) |
sandSwashMarkWavelengthM | float | 0.22 | 0.03 .. 1.5 | Swash mark wavelength (m) (swashmarks) |
sandSwashMarkStretch | float | 9 | 1 .. 30 | Swash mark stretch along the shore (swashmarks) |
sandRimPower | float | 3 | 0.5 .. 12 | Rim power |
sandRimStrength | float | 0.35 | 0 .. 2 | Rim strength |
sandSheenPower | float | 12 | 2 .. 80 | Broad sheen power |
sandSheenStrength | float | 0.1 | 0 .. 1 | Broad sheen strength |
beachRiseM | float | 0.5 | 0 .. 3 | Beach rise inland (m) |
beachLevelBandM | float | 4 | 0 .. 16 | Level band from the water (m) |
beachRiseRunM | float | 14 | 1 .. 30 | Rise runs over (m) |
beachRiseCurve | float | 1.6 | 0.5 .. 4 | Rise curve (higher = flatter near the water) |
beachReliefRise | float | 0.19 | 0 .. 1 | Relief: rise intensity (relief) |
beachReliefDip | float | 0.3 | 0 .. 1 | Relief: dip intensity (relief) |
beachUndulationWavelengthM | float | 2.2 | 0.2 .. 12 | Undulation wavelength (m) (relief) |
beachUndulationOnsetM | float | 0.7 | 0.2 .. 20 | Undulation reaches full at (m inland) (relief) |
fieldCoreSizeM | float | 24 | 2 .. 32 | Core size (m) |
guideShowCore | bool | true | Show the core's edge | |
fieldBackdropM | float | 120 | 24 .. 1000 | Backdrop: sand reaches (m across) |
fieldResolution | float | 1536 | 128 .. 2048 | Field resolution |
fieldMeshResolution | float | 512 | 32 .. 2048 | Ground mesh resolution |
sandMaxAnisotropy | float | 12 | 1 .. 24 | Max anisotropy |
FOOTPRINTS
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
printTraceMode | chooser | 1 live | 0 OFF (no marks) / 1 live | Marks in the sand |
bodyAllPersons | bool | false | All Person atoms | |
person | chooser | (atom uids) | Person (oneperson) | |
bodyLeavesPrints | bool | true | Body leaves prints | |
bodyVRHands | bool | false | VR hands touch the sand | |
bodyVRHandRadiusM | float | 0.055 | 0.01 .. 0.2 | VR hand radius (m) (vrhands) |
bodyVRHandOffsetM | float | 0.03 | -0.1 .. 0.2 | VR hand contact offset (m) (vrhands) |
printRimHeightM | float | 0.02 | 0 .. 0.2 | Rim height (m) (trace) |
printSinkDryCm | float | 7 | 0.2 .. 20 | Sink depth in dry sand (cm) (trace) |
printSinkWetCm | float | 1.5 | 0.2 .. 20 | Sink depth in wet sand (cm) (trace) |
printHalfWidthM | float | 0.05 | 0.01 .. 0.3 | Print half-width (m) (trace) |
printHalfLengthM | float | 0.075 | 0.01 .. 0.4 | Print half-length (m) (trace) |
printFeatherM | float | 0.03 | 0.001 .. 0.3 | Print feather (m) (trace) |
printRimWidthM | float | 0.05 | 0 .. 0.4 | Print rim width (m) (trace) |
printEdgeRoughnessM | float | 0.02 | 0 .. 0.2 | Print edge roughness (m) (trace) |
bodyContactBandM | float | 0.05 | 0.002 .. 0.15 | Contact band (m above ground) (trace) |
bodyFootPrintHalfLengthM | float | 0.075 | 0.01 .. 0.3 | Foot print half-length (m) (trace) |
bodyContactWidthX | float | 1 | 0.2 .. 3 | Body contact width (x collider radius) (trace) |
settleMode | chooser | 3 both | 0 OFF (raw stamp) / 1 repose / 2 creep / 3 both | Settling (trace) |
settleCohesion | chooser | 1 by depth | 0 uniform / 1 by depth | Cohesion (settle) |
settleReposeDryDeg | float | 34 | 5 .. 85 | Repose angle, dry sand (deg) (reposedry) |
settleReposeWetDeg | float | 75 | 5 .. 88 | Repose angle, wet sand (deg) (settle) |
settleAvalancheRate | float | 0.35 | 0 .. 0.9 | Avalanche rate per step (settle) |
settleRimSlumpRate | float | 0.25 | 0 .. 0.9 | Rim slump rate per step (settle) |
settleCreepRate | float | 0.15 | 0 .. 0.9 | Creep rate per step (creep) |
settleStepsPerSecond | float | 120 | 0 .. 480 | Settle steps per second (settle) |
settleCreaseResidualDeg | float | 53 | 0.2 .. 70 | Crease residual (deg per texel) (trace) |
sprayOn | bool | true | Thrown sand | |
sprayQueue | float | 3001 | -1 .. 5000 | Render queue (-1 = shader default, above the sea) (spray) |
sprayAmount | float | 1 | 0 .. 3 | Amount (spray) |
sprayLiftMS | float | 0.8 | 0 .. 4 | Lift off the ground (m/s) (spray) |
sprayLifeS | float | 0.45 | 0.1 .. 2 | Time in the air (s) (spray) |
sprayBurstSpreadS | float | 0.06 | 0 .. 0.3 | Burst spread (s) (spray) |
sprayGrainSizeM | float | 0.004 | 0.001 .. 0.02 | Dry grain size (m) (spray) |
sprayClumpSizeM | float | 0.012 | 0.002 .. 0.06 | Wet clump size (m) (spray) |
sprayDragGrain | float | 9 | 0.5 .. 20 | Air drag, dry grain (1/s) (spray) |
sprayDragClump | float | 1.4 | 0.2 .. 20 | Air drag, wet clump (1/s) (spray) |
sprayWaterDrops | float | 0.22 | 0 .. 1 | Water drops (wet sand only) (spray) |
sprayDragWater | float | 3 | 0.2 .. 20 | Air drag, water drop (1/s) (spray) |
sprayBrightness | float | 0.75 | 0 .. 2 | Brightness (spray) |
sprayClumpSheen | float | 0.16 | 0 .. 1 | Wet clump sheen (spray) |
sprayGlintFrac | float | 0.18 | 0 .. 1 | Glittering grain fraction (spray) |
sprayGlintStrength | float | 1.4 | 0 .. 4 | Glint strength (spray) |
SEA
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
seaOn | bool | true | Draw the sea | |
seaReachM | float | 4 | -16 .. 16 | Water reaches (m inland) |
guideShowWaterReach | bool | false | Show the high-water line | |
seaCapillaryFringeM | float | 0.5 | 0.02 .. 4 | Capillary fringe (m) |
seaSwashRangeM | float | 6 | 0 .. 20 | Swash run-up (m) |
seaSwashCuspM | float | 20 | 0.5 .. 60 | Swash cusp spacing (m) |
seaSwashPeriodS | float | 90 | 5 .. 300 | Swash period (s) |
seaWashErasureMS | float | 0.00097525 | 0 .. 0.05 | Wash erases marks (m/s, 0 = off) |
wetMode | chooser | 1 from the field | 0 OFF (no wetness) / 1 from the field | Wet sand |
wetMemoryMode | chooser | 1 remembers (dries over time) | 0 none (snaps to the band) / 1 remembers (dries over time) | Wetness memory (wet) |
wetDryingPerSecond | float | 0.01 | 0 .. 1 | Drying per second (dry) |
wetMemoryBeyondCore | bool | true | Wet memory beyond the core (wet) | |
wetFresnelAmount | float | 0.55 | 0 .. 2 | Wet fresnel amount (wet) |
wetFresnelPower | float | 4 | 1 .. 12 | Wet fresnel power (wet) |
wetDirectSpecular | float | 0.25 | 0 .. 2 | Wet direct specular (wet) |
seabedReliefAmount | float | 0.6 | 0 .. 0.8 | Seabed relief (0 = planar bed) |
seabedBarHeightM | float | 0.7 | 0 .. 2 | Longshore bar height (m) |
seabedBarSeawardM | float | 30 | 5 .. 200 | Longshore bar distance (m seaward) |
seabedFallPer100M | float | 3 | 0 .. 15 | Shoreface slope (m per 100 m) |
seaQueue | float | -1 | -1 .. 5000 | Render queue (-1 = shader default, 3000) (sea) |
seaColShallow | color | rgb 0.42 0.60 0.60 | Shallow water colour (sea) | |
seaColDeep | color | rgb 0.0842 0.3249 0.4453 | Deep water colour (sea) | |
seaColHorizon | color | rgb 0.55 0.66 0.72 | Horizon colour (skypick) | |
seaLevelM | float | 0 | -1 .. 1 | Water level offset from the shoreline (m) (sea) |
seaDepthScale | float | 0.8 | 0.05 .. 5 | Water clarity (m of water) (sea) |
seaHorizonM | float | 70 | 10 .. 400 | Horizon band begins at (m) (sea) |
seaExtentM | float | 0 | 0 .. 2000 | Sea reaches (m across, 0 = follow the sand) (sea) |
seaEdgeThin | float | 0.35 | 0.02 .. 2 | Swash tongue thins over (m) (sea) |
seaTongueSlope | float | 0.06 | 0.005 .. 0.4 | Swash sheet thickens by (m per m) (sea) |
seaDeepAlpha | float | 1 | 0 .. 1 | Deep water opacity (sea) |
seaSwellAmp | float | 0.5 | 0 .. 1 | Swell amplitude (m) (sea) |
seaSwellLen | float | 11 | 1 .. 60 | Swell wavelength (m) (sea) |
seaSwellAngle | float | 28 | -60 .. 60 | Swell approach angle (deg) (sea) |
seaSwellSpread | float | 18 | 0 .. 45 | Swell directional spread (deg) (sea) |
seaBreakFoam | float | 0.85 | 0 .. 1 | Breaker foam (0 = off) (sea) |
seaRippleAmp | float | 0.55 | 0 .. 3 | Ripple amount (sea) |
seaRippleScale | float | 0.9 | 0.1 .. 6 | Ripple scale (m) (sea) |
seaRippleSpeed | float | 0.35 | 0 .. 4 | Ripple speed (m/s) (sea) |
seaGlitterPow | float | 220 | 4 .. 800 | Glitter tightness (sea) |
seaGlitter | float | 2.2 | 0 .. 8 | Glitter strength (sea) |
seaSheen | float | 0.2 | 0 .. 2 | Broad sheen (sea) |
seaMaxBright | float | 0.85 | 0.2 .. 8 | Max brightness (bloom feed) (sea) |
SURF
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
foamOn | bool | true | Foam (waterfx) | |
foamBirth | float | 7 | 0 .. 24 | Born per second at the bore (foam) |
foamBirthW | float | 0.35 | 0.05 .. 3 | Birth band width (m) (foam) |
foamBirthBreak | float | 5 | 0 .. 200 | Born at the breakers (foam) |
foamBreakPulse | float | 1 | 0 .. 1 | Birth pulses with each crest (foam) |
foamPerWave | float | 0.6 | 0 .. 2 | Deposited per wave (foam) |
foamFadeS | float | 30 | 2 .. 180 | Deposit fades over (s) (foam) |
foamFrontLipM | float | 0.5 | 0 .. 2 | Front lip (m) (foam) |
foamLifeS | float | 12 | 0.5 .. 60 | Life on water (s) (foam) |
foamLifeDryS | float | 1 | 0.1 .. 20 | Life once drained (s) (foam) |
foamInertiaS | float | 1.2 | 0 .. 6 | Coasting (s) (foam) |
foamInertiaPush | float | 0.25 | 0 .. 6 | Pick-up by the water (s) (foam) |
foamFrontConfine | float | 1 | 0 .. 1 | Tail never retreats (foam) |
foamFrontReach | float | 24 | 0.2 .. 24 | Swash breathing reach (x the excursion) (foam) |
foamArrivalMin | float | 12 | 0 .. 40 | Arrivals merge within (m) (foam) |
foamBoreGain | float | 1 | 0 .. 4 | Conveyor speed (x) (foam) |
foamBoreRelief | float | 1 | 0 .. 2 | Bore front relief (foam) |
foamBoreWidthM | float | 0.4 | 0.05 .. 1.5 | Bore roller width (m) (foam) |
foamBoreTonguesM | float | 0.1 | 0 .. 0.5 | Bore front tongues (m) (foam) |
crestWidthM | float | 2.5 | 0.3 .. 12 | Crest foam band width (m) (foam) |
crestTail | float | 0.7 | 0 .. 2 | Crest keeps its foam (the trail) (foam) |
crestTailS | float | 1.1 | 0.1 .. 4 | Crest foam lingers (s) (foam) |
crestGive | float | 0.45 | 0.05 .. 1 | Crest hands foam to the swash (1 = instant) (foam) |
crestMinAmpM | float | 0.06 | 0.01 .. 0.6 | Crest needs a wave this tall (m) (foam) |
crestBreak | float | 0.55 | 0 .. 1 | Crest line break-up (foam) |
foamRagM | float | 0.9 | 0 .. 4 | Lobes reach past the front (m) (foam) |
foamRagScale | float | 2.6 | 0.5 .. 12 | Lobe scale along the shore (m) (foam) |
foamNetCellM | float | 0.3 | 0.1 .. 6 | Net eye size (m) (foam) |
foamNetWarp | float | 0.15 | 0 .. 1 | Filament wiggle (m) (foam) |
foamNetPull | float | 0.6 | 0.05 .. 8 | Net pull limit (m) (foam) |
foamLaceCarry | float | 24 | 0 .. 24 | Filament aspect ratio (0 = pinned) (foam) |
foamMicroSlope | float | 0.2 | 0 .. 1 | Surface slope (RMS) (foam) |
foamMicroCM | float | 0.4 | 0.05 .. 12 | Surface grain (cm) (foam) |
foamMicroPull | float | 0.3 | 0.02 .. 2 | Surface pull limit (m) (foam) |
foamGloss | float | 2.5 | 0 .. 3 | Wet gloss and sky mirror (0 = the sand's) (foam) |
foamTrans | float | 0.8 | 0 .. 1 | Translucency (foam) |
bubOn | bool | true | Bubbles (waterfx) | |
bubAmount | float | 1 | 0 .. 1 | Amount (bubbles) |
bubSizeMM | float | 1.5 | 0.2 .. 20 | Bubble size (mm) (bubbles) |
bubRiseMS | float | 0.02 | 0.005 .. 0.3 | Rise speed (m/s) (bubbles) |
bubLifeS | float | 20 | 0.5 .. 60 | Life (s) (bubbles) |
bubClingS | float | 4 | 0 .. 15 | Clings to the body for (s) (bubbles) |
bubRateHz | float | 30 | 1 .. 120 | Emission rate (per limb, /s) (bubbles) |
bubDriftMS | float | 0.55 | 0 .. 2 | Carried by the water (m/s) (bubbles) |
splashOn | bool | true | Splash (waterfx) | |
splashAmount | float | 0.25 | 0 .. 2 | Amount when the body moves through water (splash) |
splashRiseM | float | 0.3 | 0.05 .. 2.5 | Splash height (m) (splash) |
splashLifeS | float | 0.25 | 0.1 .. 2 | Splash life (s) (splash) |
ripOn | bool | true | Ripples (waterfx) | |
ripAmp | float | 0.4 | 0 .. 0.6 | Amount (slope) (ripples) |
ripLenM | float | 0.25 | 0.05 .. 1.5 | Ripple wavelength (m) (ripples) |
ripSpeedMS | float | 0.9 | 0.2 .. 4 | Ring speed (m/s) (ripples) |
ripLifeS | float | 1 | 0.3 .. 10 | Ring life (s) (ripples) |
LIGHT
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
lightTimeOfDay | chooser | 0 custom | 0 custom / 1 DAY / 2 NIGHT | Time of day |
lightSource | chooser | 1 built-in only (self-lit) | 0 scene lights only / 1 built-in only (self-lit) / 2 both (built-in as a floor) | Light source |
lightYawDeg | float | 145 | 0 .. 360 | Built-in light yaw (deg) (studio) |
lightPitchDeg | float | 38 | -10 .. 89 | Built-in light pitch (deg) (studio) |
lightKeyColour | color | Built-in key colour (studio) | ||
lightFillColour | color | Built-in fill colour (studio) | ||
skyColourSource | chooser | 0 built-in fill x gain (tracks day/night) | 0 built-in fill x gain (tracks day/night) / 1 Sea horizon colour picker | Sky colour source |
skyGain | float | 1.9 | 0 .. 6 | Sky brightness (x the built-in fill) |
lightExposure | float | 1 | 0 .. 2 | Light exposure |
lightAmbientBoost | float | 1 | 0 .. 4 | Scene ambient boost |
lightAcceptA | chooser | (atom uids) | Accepted light A | |
lightAcceptAColour | bool | true | A: take its colour (lighta) | |
lightAcceptAShadow | bool | true | A: take its shadow (lighta) | |
lightAcceptB | chooser | (atom uids) | Accepted light B | |
lightAcceptBColour | bool | true | B: take its colour (lightb) | |
lightAcceptBShadow | bool | true | B: take its shadow (lightb) | |
lightShadowOnlyDepth | float | 3 | 0 .. 8 | Shadow-only depth (m) (lightany) |
lightRejectAtSource | bool | true | Reject lights at the source | |
sandCastsShadows | bool | true | Sand casts shadows |
Actions (buttons; fire them from a trigger)
| action | what it does |
|---|---|
RescanPersons | Rescan for Person atoms |
StampBodyNow | Stamp the whole body now |
WalkShoreline | Walk the shoreline (wet to dry) |
RescanLights | Rescan for light atoms |
AlignKeyToLight | Align the built-in key to the named light |
LevelSandUnderPerson | Measure the bound Person and level the beach profile out to them |
Bench Setup | Puts the plugin into its measurement configuration (used by the bench tooling) |
ClearSand | Clear every mark from the sand (same as the Clear all marks button) |
RebuildSand | Rebuild ground, field and sea from the current settings (the Rebuild Env button) |
Also registered, not on the panel (instruments, kept as API):
beachSeawardDropM, beachSeawardDropRunM, foamBoreSharpness, benchPoolBubbles, benchFillBubbles, benchFillSplash, benchFillRipples, devBubbleLadder, devSplashLadder, crestSharp, foamMicroMix, benchFragmentLoad, benchOverdrawCopies, benchOverdrawShadows, benchSandVisible, settleDirtyRect, devShowSpraySpecies, devDepthMode, devDrawVertexGrid, devShowField, devShowWetFlat, devShowTierDiff, devShowAlbedoFlat, devShowNormalFlat, devShowAttenuation, devShowLightTerms, seaShowEdge, seaShowSample, seaShowFlat, foamShowField, foamShowAhead.SkinBeach (on the Person atom)
SAND SHELL
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
skinShellOn | bool | true | Sand shell | |
skinRenderQueue | float | 2390 | 2000 .. 3200 | Render queue |
skinSandColour | color | rgb 0.976 0.908 0.745 | Sand colour | |
skinCoverage | float | 1 | 0 .. 1 | Coverage |
skinClumpSizeCm | float | 5 | 0.5 .. 40 | Clump size (cm) |
skinClumpContrast | float | 1.8 | 0.3 .. 4 | Clump contrast |
skinClumpStrength | float | 0.9 | 0 .. 1 | Clump strength |
skinGrainSizeMm | float | 0.9 | 0.1 .. 5 | Grain size (mm) |
skinGrainFatness | float | 0.55 | 0.1 .. 1 | Grain fatness |
skinGrainToneSpread | float | 0.55 | 0 .. 1 | Grain tone spread |
skinGrainRelief | float | 0.85 | 0 .. 1 | Grain relief |
skinGrainLayers | float | 2 | 1 .. 3 | Grain layers |
skinGrainSizeSpread | float | 0.6 | 0 .. 1 | Grain size spread |
skinDust | float | 0.3 | 0 .. 1 | Dust between grains |
skinUseSceneLight | bool | true | Use scene light | |
skinAmbientFloor | float | 0.35 | 0 .. 1 | Ambient floor |
skinWetDarkening | float | 0.6 | 0 .. 1 | Wet sand darkening (wetlook) |
skinWetColour | color | rgb 0.42 0.30 0.20 | Wet sand colour (wetlook) | |
skinWetColourAmount | float | 0.55 | 0 .. 1 | Wet colour amount (wetlook) |
skinWetClumping | float | 0.9 | 0 .. 2 | Wet clumping (wetlook) |
skinRampOn | bool | false | Height ramp (bind-pose Y) | |
skinRampCentreM | float | 0.5 | -1 .. 2 | Ramp centre (bind Y, m) (ramp) |
skinRampWidthM | float | 0.25 | 0.01 .. 1 | Ramp width (m) (ramp) |
skinShellOffsetMm | float | 0 | 0 .. 10 | Shell offset (mm) |
skinViewOffsetMm | float | 10 | 0 .. 50 | View offset (mm) |
STICKING
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
skinLedgerOn | bool | true | Sand sticks to the skin | |
skinSandSource | chooser | Sand height from | Sand height from (ledger) | |
skinSandBaseYM | float | 0 | -2 .. 3 | Sand base Y (m) (sandy) |
skinSandThicknessM | float | 0.05 | 0 .. 1 | Sand thickness (m) (ledger) |
skinStickRate | float | 3 | 0.1 .. 20 | Stick rate (per s) (ledger) |
skinStickBandM | float | 0.03 | 0.001 .. 0.2 | Stick ramp band (m) (ledger) |
skinStickMode | chooser | Sticks | Sticks (ledger) | |
skinEdgeBreakup | float | 0.35 | 0 .. 1 | Edge break-up (ledger) |
skinEdgeBreakupCm | float | 2.5 | 0.3 .. 20 | Edge break-up size (cm) (ledger) |
SHEDDING
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
skinShakeOn | bool | false | Shake-off (motion sheds sand) | |
skinShakeThresholdMS | float | 0.5 | 0 .. 8 | Shake threshold (m/s) (shake) |
skinShakeRate | float | 0.04 | 0 .. 1 | Shake rate (per m/s per s) (shake) |
skinShakeResidueFloor | float | 0.2 | 0 .. 0.8 | Shake residue floor (coverage) (shake) |
skinWetResistsShaking | float | 1 | 0 .. 1 | Wet sand resists shaking (shake) |
skinMotionAverageFollow | float | 0.15 | 0.01 .. 1 | Motion average follow (per frame) (shake) |
skinWipeOn | bool | false | Wipe-off (hands or colliders sliding over the skin) | |
skinWipeReachMm | float | 12 | 0 .. 80 | Wipe reach (mm) (wipe) |
skinWipeMinSlideMS | float | 0.05 | 0 .. 2 | Wipe min slide (m/s) (wipe) |
skinWipeRate | float | 6 | 0 .. 40 | Wipe rate (per m/s per s) (wipe) |
skinWipeResidueFloor | float | 0 | 0 .. 0.5 | Residue floor (coverage) (wipe) |
skinWipeHandsOnly | bool | true | Wipe source: the person's own hands only (wipe) | |
skinHandTagRadiusMm | float | 10 | 1 .. 60 | Hand-skin tag radius (mm) (hands) |
skinEmitOn | bool | false | Airborne sand (what comes off) | |
skinEmitGrainsPerUnit | float | 60 | 0 .. 400 | Grains per unit removed (emit) |
skinEmitSpreadM | float | 0.07 | 0.01 .. 0.3 | Spawn spread (m) (emit) |
skinEmitLifeS | float | 1.1 | 0.2 .. 4 | Grain life (s) (emit) |
skinEmitGrainSizeM | float | 0.0022 | 0.0005 .. 0.02 | Dry grain size (m) (emit) |
skinEmitClumpSizeM | float | 0.006 | 0.0005 .. 0.05 | Wet clump size (m) (emit) |
skinEmitUpKickMS | float | 0.5 | 0 .. 3 | Shake upward kick (m/s) (emit) |
skinEmitThrow | float | 0.45 | 0 .. 1 | Shake throw (fraction of limb speed) (emit) |
skinEmitDragFine | float | 9 | 0 .. 30 | Drag, fine grains (emit) |
skinEmitDragCoarse | float | 1.2 | 0 .. 30 | Drag, wet clumps (emit) |
skinEmitBrightness | float | 1 | 0 .. 3 | Grain brightness (emit) |
CLOTHING MASK
| id | type | default | range / choices | what it does |
|---|---|---|---|---|
skinMaskOn | bool | false | Exclude clothing-covered skin | |
skinMaskFitMm | float | 12 | 1 .. 60 | Clothing fit distance (mm) (mask) |
skinMaskFeatherMm | float | 4 | 0 .. 30 | Mask edge feather (mm) (mask) |
skinMaskNormalDotMin | float | 0 | -1 .. 1 | Cover normal dot min (mask) |
Actions (buttons; fire them from a trigger)
| action | what it does |
|---|---|
RescanSand | Rescan for EnvBeach |
ClearLedger | Clear the sand off the skin |
ScanHands | Scan hands (with the hands clear of the body) |
ScanRegions | Scan body regions |
ScanClothingMask | Scan the clothing mask (wear it first) |
RefreshClothingList | Refresh the clothing list |
ClearClothingMask | Clear the clothing mask |
Report | Report |
CopyReport | Copy report |
Also registered, not on the panel (instruments, kept as API):
skinEmitThreshold, skinDevMaxVelocityMS, skinMaskSkipList, liveness, skinShowDev, vertsource, skinSkinMaterialsOnly, skinDevDrawTiming, skinDevTwoSided, skinDevPaintFailures, skinDevCpuPlane, skinDevZTestAlways, skinDevDepthOffsetFactor, skinDevDepthOffsetUnits, skinDevShowCoverage, skinDevShowWetness, skinDevRainbow, skinDevLagProbe, Bench sand load, Bench sand verts, Bench sand coverage, Bench sand wetness, Bench motion, Bench hand slide (m/s), Bench cost ladder, Bench shell copies, Bench Measure (write a new number).The same guidance ships inside the package as README files, in English, Traditional Chinese, Japanese and Korean.
The bench, the method, the numbers, and where a number is arithmetic rather than a reading, it says so.
A beach is a lot of screen. Sand fills the frame from the foreground to the horizon, and the sea fills the rest, so this is the kind of product that is easy to make expensive and easy to lie about. What follows is the data that drove the defaults, including the rows that are not flattering.
Every row below is marked. MEASURED is a reading off the bench. INFERRED is arithmetic on a reading: useful for planning, not a benchmark.
The machine
8K is deliberate. Sand and sea are fill-rate, so the stress test is pixels, and 8K is four times 4K and sixteen times 1080p. A number taken at a comfortable resolution says nothing about the resolution somebody else runs.
The method
A measuring tool drives the run unattended: it sets a configuration, lets it settle 4 seconds, samples 10 seconds, writes a row, restores the previous state explicitly, and moves on. Six passes per configuration; the spread between passes is what decides whether a difference is readable at all.
The honest part
At 8K this bench was GPU-bound for the sand and the sea, which is where their cost lives, and CPU-bound for the body work, which is where its cost lives. That is the useful arrangement, but it means the two families of numbers travel differently to your machine; the next spoiler is about that.
| Item | Value |
|---|---|
| GPU | NVIDIA RTX 5090 |
| Render resolution | 7680 Γ 4320 (DSR 8K), fullscreen |
| MSAA | 4Γ |
| Pixel lights | 6 |
| VSync | off |
| Scene | One Person, lying still at the waterline, camera fixed on a mid framing (sand β 80 % of the frame), VAM UI closed |
| Baseline frame time | 3.41 ms with every SeaForge switch off |
8K is deliberate. Sand and sea are fill-rate, so the stress test is pixels, and 8K is four times 4K and sixteen times 1080p. A number taken at a comfortable resolution says nothing about the resolution somebody else runs.
The method
A measuring tool drives the run unattended: it sets a configuration, lets it settle 4 seconds, samples 10 seconds, writes a row, restores the previous state explicitly, and moves on. Six passes per configuration; the spread between passes is what decides whether a difference is readable at all.
Frame time, never FPS. Frame time is linear, so it can be added and compared.
Ablation with explicit restore. Each feature off individually, everything else held, every step putting the previous toggle back by name.
Pass/fail written before the numbers were read. Every "must change" paired with a "must not change".
The factory configuration is the measured configuration. The overnight matrix ran on a bench pin; a separate three-pass A/B/A then compared that pin against the exact shipping defaults: β0.04 ms, inside Β±0.1. The numbers below are for the beach as it loads, not for a tuned-down version of it.
UI closed, camera parked. VAM's own open panel costs frames.
The honest part
At 8K this bench was GPU-bound for the sand and the sea, which is where their cost lives, and CPU-bound for the body work, which is where its cost lives. That is the useful arrangement, but it means the two families of numbers travel differently to your machine; the next spoiler is about that.
The single most important thing on this page is not a number, it is which way each cost scales.
Screen coverage is the multiplier, and it was measured rather than assumed
Same scene, same settings, four framings. MEASURED
Pull the camera back and the bill drops to a third. Move in and every pixel gets dearer as well as more numerous: 0.028 ms per percent of screen at the far framing, 0.052 at the close one. If you are hunting frames on a beach, framing is the first lever and it is free.
What we are not going to do
Print a 4K or 1080p number for a card we do not have. The fill-rate rows scale with your pixel count and your card's fill-rate, and the body rows do not scale with either. Read the table above for the direction, and the next spoiler for the milliseconds on this machine.
| Effect | Where the cost lives | Lower resolution helps? | Weaker GPU costs more? | Basis |
|---|---|---|---|---|
| Sand (surface, relief, prints) | GPU fill-rate | Yes, roughly with pixel count | Yes | MEASURED |
| Sea (surface, swash, foam field) | GPU fill-rate | Yes | Yes | MEASURED |
| Body work (prints, thrown sand, bubbles, splash, ripples) | CPU, per Person | No | No | MEASURED |
| SkinBeach (shell, sticking, shedding) | GPU, small and fixed | Barely | Barely | MEASURED |
Screen coverage is the multiplier, and it was measured rather than assumed
Same scene, same settings, four framings. MEASURED
| Framing | Sand in frame | Ξ frame time |
|---|---|---|
| Far | 48 % | +1.33 ms |
| Grazing | 53 % | +1.48 ms |
| Mid (the bench framing) | 81 % | +4.15 ms |
| Close | 100 % | +5.24 ms |
Pull the camera back and the bill drops to a third. Move in and every pixel gets dearer as well as more numerous: 0.028 ms per percent of screen at the far framing, 0.052 at the close one. If you are hunting frames on a beach, framing is the first lever and it is free.
What we are not going to do
Print a 4K or 1080p number for a card we do not have. The fill-rate rows scale with your pixel count and your card's fill-rate, and the body rows do not scale with either. Read the table above for the direction, and the next spoiler for the milliseconds on this machine.
All figures are frame-time increase over the same scene with every SeaForge switch off. Bench conditions as above, mid framing, one Person.
The ranking
The beach itself is the bill. Everything that happens on and around a body, the prints, the thrown sand, the bubbles, the splash, the sand on her skin, adds up to less than a fifth of a millisecond at 8K. Leave all of that on. If a beach scene is heavy, it is the sand and the sea, and both have a give-back list.
Several people
The body work is per Person, so with All Person atoms on it grows with the head count. The bench had one. Four people are four times the body row, which is still under a millisecond, and the sand and sea do not care how many there are. INFERRED from the one-person row; we have not benched a crowd.
| What | Ξ frame time | Basis |
|---|---|---|
| The whole beach, factory settings (sand + sea + surf + prints) | +4.18 ms (3.41 β 7.59, about 130 fps at 8K) | MEASURED |
| Sand only, sea switched off | +2.23 ms | MEASURED |
| The sea itself (surface, swash, foam) | +1.93 ms | MEASURED |
| Plugin loaded, Master Switch off | +0.01 ms (the noise floor) | MEASURED |
| A person in the water: bubbles, splash, ripples, at a held population of 2048 / 12 / 12 | +0.085 ms (ripples 0.065, bubbles β€ 0.02, splash β€ 0.02) | MEASURED |
| Two more per-pixel scene lights on the sand | +0.035 to 0.055 ms | MEASURED |
| SkinBeach, factory (shell + sticking) | +0.065 ms | MEASURED |
| + Shake-off + Wipe-off (hands, 44 capsules) | +0.01 to 0.025 ms | MEASURED |
| + Airborne sand | +0.000 ms on the frame; it polls at 9.6 Hz | MEASURED |
| Clothing mask | 0 | MEASURED |
| SkinBeach, everything on | +0.105 ms (about 1 % of the frame) | MEASURED |
The ranking
Sand > Sea β« everything on her body
The beach itself is the bill. Everything that happens on and around a body, the prints, the thrown sand, the bubbles, the splash, the sand on her skin, adds up to less than a fifth of a millisecond at 8K. Leave all of that on. If a beach scene is heavy, it is the sand and the sea, and both have a give-back list.
Several people
The body work is per Person, so with All Person atoms on it grows with the head count. The bench had one. Four people are four times the body row, which is still under a millisecond, and the sand and sea do not care how many there are. INFERRED from the one-person row; we have not benched a crowd.
Each row is what you save, cumulatively, at the bench framing. MEASURED
And one you should not touch for frames
Field resolution is free: 1024, 1536 and 2048 measure within Β±0.04 ms of each other. It decides how sharp a print's edge is, nothing else. Lowering it buys you nothing and costs you the prints. MEASURED
Ground mesh the other way is not free: 1024 costs +1.73 ms over 512. The factory value is the one on the knee of that curve.
| Step | Saves | What you give up |
|---|---|---|
| 1. Ground mesh resolution 512 β 256 (SAND Β· Field) | β0.96 ms | Print geometry is softer. Prints still read. |
| 2. Detail octaves 8 β 4 (SAND Β· Surface detail) | β0.71 ms | The finest surface ripple. Hard to see past arm's length. |
| 3. Grain detail β 0 | β0.16 ms | The per-grain relief. Close-ups get flatter. |
| 4. The small ones: crest foam, the surf switches, the sea's surface ripple | β0.01 to β0.06 each | Each is its own look. |
| All of it | about β2.3 ms | The beach is still there, and still remembers. |
And one you should not touch for frames
Field resolution is free: 1024, 1536 and 2048 measure within Β±0.04 ms of each other. It decides how sharp a print's edge is, nothing else. Lowering it buys you nothing and costs you the prints. MEASURED
Ground mesh the other way is not free: 1024 costs +1.73 ms over 512. The factory value is the one on the knee of that curve.
No leak. A 2.7-hour soak with everything on: the heap floor after each collection was flat from the first hour to the last. Nothing accumulates. MEASURED
The part we will say out loud: at factory settings the beach produces about 2 KB of garbage per frame, which is steady state, not growth. That means Mono runs a full collection roughly every 50 minutes in a scene that is doing nothing but the beach. LumenForge's page describes the same VAM-wide hitch at 43 minutes with a heavy scene and none of our plugins in it; ours moves that clock a little. A typical VAM scene does not run 50 minutes without a load, so for almost everyone this never happens, but if it does, that quarter-second hitch is what it was.
Reloading the plugin does not stack. Ten reloads in a row were measured because an earlier build left 23 MB behind on each; the shipping build does not.
The part we will say out loud: at factory settings the beach produces about 2 KB of garbage per frame, which is steady state, not growth. That means Mono runs a full collection roughly every 50 minutes in a scene that is doing nothing but the beach. LumenForge's page describes the same VAM-wide hitch at 43 minutes with a heavy scene and none of our plugins in it; ours moves that clock a little. A typical VAM scene does not run 50 minutes without a load, so for almost everyone this never happens, but if it does, that quarter-second hitch is what it was.
Reloading the plugin does not stack. Ten reloads in a row were measured because an earlier build left 23 MB behind on each; the shipping build does not.
Frame it. Sand in frame is the multiplier on everything. A far or grazing shot costs a third of a mid shot, and it is usually the better picture anyway.
Ground mesh 512 β 256. The biggest single saving, and the prints survive it.
Detail octaves 8 β 4.
Leave the surf, the prints and SkinBeach alone. There is nothing to win there.
Crowds: the body work is per person. Four people is fine. Twelve is a decision, and the plugin stops at four on purpose.
VR
Not benchmarked in a headset. The shape of the cost carries over: the sand and the sea are fill-rate, and VR is where fill-rate hurts, so start with Ground mesh at 256 and raise it only if the prints ask for it. The body work is CPU and should behave as it does on the desktop.
Numbers you can check: both plugins print a full state report with a one-press copy button.
If yours disagrees with this page, that report is the fastest way to show us.
If yours disagrees with this page, that report is the fastest way to show us.
Up front about the edges, so nothing surprises you.
This is a shore, not an ocean. A beach with a sea in front of it, seen from the beach. The sea is a surface with swell on it, not a volume: there is no underwater yet, and the camera is not meant to go below the waterline in v1. See Below the waterline above for where that is going.
Prints come from the body's joints, not from every vertex. Twelve body segments, each a capsule between two controllers, print the sand: torso, head, thighs, shins, upper arms, forearms, feet. That gives a foot, a shin, a thigh, a hip, a forearm; it does not give individual toes or the crease of a palm. Up close the print is the shape of a limb, which is what a real one is.
Thin sand and thin limbs. The sand is a layer with a thickness, 12 cm by default, on top of your scene's floor. A forearm pressed into 7 cm of sand reaches the floor underneath it, and VAM's floor is whatever collider your scene has there. Thicker sand (SAND Β· Sand depth) gives deeper prints; that is a slider, not a limit.
Four people, and the body work is per person. All Person atoms tracks up to four; the rest are counted in the report rather than tracked. The bench was one person; see the numbers section.
VR hands are a switch, and it ships off. With it on, the player's own hands print, throw sand and wipe her. On a desktop it does nothing and the report says so.
A garbage collection about every 50 minutes in a scene that does nothing else, at a quarter of a second. Steady-state garbage, measured; not a leak, also measured. Most scenes never get there.
Reject lights at the source edits your scene lights' culling masks. It is how the sand keeps every light but the two you named off itself. Do not combine it with another plugin that edits culling masks (Light Atom Excluder, for one); one of them will win and it will not be obvious which. Leave it off and both are fine.
Transparent against transparent has no correct order. Sea against glass, thrown sand against the sea. Both carry a Render queue slider. Author's call, per scene.
The Empty is the centre of the beach, and the beach is finest near it. Prints far from the centre, out in the backdrop sand, are coarser than the ones at her feet. Move the atom to the action.
v1.0
Initial release. Two plugins. EnvBeach: sand that prints and settles, a sea that wets, foams and erases, surf that answers a body, day and night. SkinBeach: sand that sticks where she touched the beach, packs where she is wet, falls when she moves and comes off under a hand, with a clothing mask.
Reviewed before release. Two independent code audits and a third-party read before this went out, an overnight bench at 8K, and a re-measure of the exact factory configuration. What they found is fixed or written above.
Thanks to @C&G-STUDIO for the ideas and encouragement!
Made so a beach can finally feel like one,
not just look like one.

not just look like one.