[ForgeLab] Lumen Forge, Now with the All-New SkyRay Atmospheric Volumetric Lighting

Plugins + Scripts [ForgeLab] Lumen Forge, Now with the All-New SkyRay Atmospheric Volumetric Lighting

Download [<1 MB]
πŸͺ„ LUMENFORGE v1.3
A ninth plugin, and it is the outdoors. Full-screen sun and moon shafts, carved by the scene you actually built.

SkyRay01.jpg



In one screen

What changedWhat it gives you
β˜€οΈ New : SkyRayVolumetric shafts across the whole frame, with your scene's own geometry cutting them. Stand in a forest and the leaves cut the light where the leaves are.
πŸŒ— Sun or moonNothing in it is solar. Set the colour cold and dim and it is moonlight, same code, same cost.
☁️ Cloud shadows without a skyA skybox never enters a depth map, so painted clouds cast nothing. Sky mask is an independent layer with coverage, feature size, softness and drift.
πŸ“Š SkyRay ships its own measurement documentPERF_SkyRay.md, inside the package : conditions in full, every row, the caveats, and a 10.5 hour soak.
πŸŒ… EnvRay is now named as the indoor halfNothing about it changed. The page just stops asking it to be two things.
πŸ” Reviewed before releaseTwo independent code audits, two re-verification rounds, a save-load round trip and five field checks, all before this went out.

Nothing was removed, no existing setting changed its meaning, and your saved scenes are untouched. The only behaviour change outside SkyRay is a one-directional correction inside SkyRay itself, described under Known limits.



Where it came from

v1.2 gave EnvRay a 15 metre beam radius so one shaft could cover an outdoor space. That was the right fix for the wrong shape, and it took a while to see why.

EnvRay is built around an opening. A window, a doorway, a skylight : a volume you place by hand, with a lot of local detail inside it and a defined direction through it. Widening that volume until it covers a courtyard makes it bigger. It does not make it the right thing, because outdoors the light does not arrive through an opening at all. It arrives from everywhere above, and what shapes it is not a frame you positioned but every tree, rock and roofline that happens to be standing there.

That is a different problem, and it wanted a different plugin rather than a longer slider.



β˜€οΈ SkyRay : the whole sky, and the whole scene shadows it

SkyRay02.jpg


What it does

It marches the entire screen and asks, at every step along every ray, whether the sun or the moon actually reaches that point. The answer comes from a depth capture of your scene taken along the light direction, so what carves the shafts is what is really standing there. Not a painted texture, not a noise field, not a cone you aimed.

Add an Empty, load SkyRay.cslist, set an azimuth and an elevation, thicken the air. That is the whole setup.

It aims by eye, and that is a decision rather than a gap

SkyRay binds to no light in your scene and reads nothing from one. That will look like a missing feature until you count how many outdoor scenes on the Hub are lit by baked lighting, or by an ambient colour, or by nothing at all : a plugin that demanded a Directional Light would simply not work in most of them.

So the direction is two sliders, and matching them to whatever is lighting your shot is a creative act. It is also the part nobody else can do for you, because only you can see the shot.

Put it on an Empty, not on a Person

The shadow capture is baked around a core box. Hosted on a figure that box follows her, so every step she takes re-bakes it, even under STATIC. The plugin's own Report warns you when you have done it, in words, rather than leaving you to wonder why a static scene is not static.

Sky mask is not your skybox, and it cannot be

A skybox is not geometry. It never enters a depth map. So the clouds you can see painted above your scene cast nothing at all, and no amount of work on our side changes that.

Sky mask is a separate layer of cloud shadow with its own coverage, feature size, edge softness and drift. It is deliberately not called Clouds, because it cannot match the sky you are looking at : it is its own weather. It also supplies the large-scale haze variation that stops an outdoor scene reading as uniformly foggy, which is most of what makes it worth switching on.



⚑ What it costs

Bench, 7680x4320, one RTX 5090, camera on open sky so 100% of rays are unobstructed, three passes of ten seconds per row with the VAM panel closed.

March stepsFrame timeAbove the floor
512 (amplifier, not a setting)28.43 ms25.20 ms
25614.92 ms11.69 ms
1288.07 ms4.84 ms
483.44 ms0.21 ms
24 (factory)3.23 ms0.00 ms
empty scene, everything off3.23 msfloor

0.0530 ms per march step. Linear to three decimals from 512 down to 128.

At factory settings it reads at the floor, and that is not the same as free

24 march steps with cloud shadows on measures 3.23 ms against a 3.23 ms empty-scene floor, in all three passes, with the mask verifiably running. It is tempting to print that as zero.

It is about 1.3 ms of GPU work sitting under 1.95 ms of headroom, on a frame that is CPU-bound in this bench. Absorbed, not absent. On a machine that is already GPU-bound, or in a scene heavy enough to have used that headroom already, those 1.3 ms are real and you will feel them. We would rather say that than let you find out.

The 1.95 ms absorption is not assumed. It is the intercept of the step ladder above, and it independently reproduces the 2.22 ms this same bench measured on EnvRay, a completely unrelated subject, two days earlier. Two subjects, one floor.

What is free, and what is not

Measured at 512 steps so the differences are readable at all.

SettingReadingWhat it means
Penumbra 0 / 1 / 327.40 / 28.34 / 28.34 msThe second shadow tap costs 0.94 ms. Its width is free.
Bake resolution 512 / 1024 / 204828.42 / 28.46 / 28.50 msFree per frame. It buys shadow sharpness with VRAM. No reason to lower it chasing frames.
STATIC / LIVE28.50 / 28.75 ms0.25 ms in this room. See the warning below.
Framing : near / room / open sky3.23 / 15.12 / 28.46 msCost tracks how much of the screen the shafts cover, not how big your world is.
Reach 300 / 6026.32 / 28.71 msLonger reach is cheaper : bigger cells make transmittance decay faster, so the ray gives up sooner.
Haze density 0 / 0.02 / 0.327.75 / 28.48 / 28.48 msFlat. The height falloff thins the air along an upward ray before the early-out can fire.

⚠ The one number on this page that does not transfer

LIVE re-renders your whole scene into the shadow capture every frame. Its cost therefore scales with how many objects you have, not with anything on SkyRay's panel. It measured 0.25 ms in a bench room; a dense forest will be far more than that, and no number we print here would tell you yours.

STATIC is the default for exactly this reason. Use LIVE only when the scenery itself moves.



πŸ”¬ One finding worth passing on, because it is not only ours

Halfway through pricing the cloud shadows, the same configuration started returning two different answers several milliseconds apart. It was neither.

Toggling a shader uniform that gates a branch makes the GPU driver throw away its specialised version of that shader. The frame gets several milliseconds dearer, and it does not come back when you toggle the switch off again. It clears only when the driver rebuilds its specialisation, which nothing on our side can trigger.

Isolated with identical probes on both sides of a single toggle : 27.99 ms before, 35.17 and 35.22 after, 28.15 once other work had run. It tracks the toggle row rather than the position in the table, at 19 seconds into one run and 4 seconds into another. The feature being toggled costs 0.02 ms by itself, so it is not the work. It is the act of switching.

Measured with that designed out, using two complete ladders with exactly one toggle between them and settling on both sides, the cloud shadows cost 0.00503 ms per step, about 2.57 ms at 512.

Which means the sky mask has two costs, and both of them are true.

What you did this sessionAt 512 stepsThe mask appears to cost
Never touched the switch28.4 msbaseline
Switched it on at some point36.3 ms+7.9 ms
Both halves on an already-generic shader33.7 to 36.3 ms+2.6 ms

At the factory 24 steps every one of these reads at the floor, so it changes nothing about how you should use it. It is on this page because if you run high step counts and compare notes with somebody else, this is why two people can measure the same setting and honestly disagree.



πŸ•™ Ten and a half hours, unattended

634 one-minute buckets, deliberately not at shipping settings : 128 march steps, LIVE re-baking every frame, cloud shadows drifting. The night answers stability. Cost was already settled above.

  • πŸ“ˆ Frame time drifted +0.018 ms across 10.5 hours. p95 drifted +0.032 ms, so it tracked the mean : no stutter grew in, which is the shape a soak exists to catch.
  • 🧠 No leak. The managed heap saw-tooths as any Unity app does, so the number that matters is the floor it returns to after each collection. It was 890 MB at the start of the night and 890 MB at the end. The floor is the thing a leak would move.
  • πŸͺΆ 3 bytes per frame. Measured against the bench's own 119 B/frame floor : SkyRay at shipping settings reads 122, and at 128 steps 123, so the march itself allocates nothing.
  • πŸ” State held. The same source flags, step count and capture size in all 634 buckets, and rendering advanced in every one of them. No re-specialisation step anywhere overnight, so the state we measured is the state it held for ten and a half hours.

The heap collects roughly every 65 minutes and about seven of those ten collections carried a frame near 150 ms. That is VAM's garbage collector, the same one documented on the main page, and SkyRay's contribution to reaching it is 3 bytes a frame.



⚠️ Known limits, stated rather than claimed fixed

  • Under a heavy sky the far distance is slightly darker than perfect. Past the wash-out distance the shafts blend into flat haze at the sky's average brightness, and SkyRay estimates that average rather than measuring it. The estimate runs dark : at 50% cloud cover the distance is about half as bright as a perfect one would be. It is capped so it can never come out brighter than the near field, which is the way round that reads as wrong. If you want more distant glow, raise Reach or push Wash-out starts at further out. The correct fix replaces the estimate outright and moves the picture at every cloud setting, which is not something to do three days after a code review.
  • Cloud drift does not survive a save. A reload starts the clouds at their origin rather than where they had drifted to. Clouds are weather, and that is the intended behaviour rather than an oversight. If you need a fixed sky, set drift to 0.
  • The Ray plugins share Unity's camera depth texture and hand it back together. If a third-party effect that also needs depth is switched on after them, and they are then the last of the group to be switched off, that effect can lose depth until it next asks for it again. Most re-ask every frame and you will never see it. Line-wide behaviour, not specific to SkyRay.



🚫 What we did not change

  • EnvRay. Not a line of it. It is described differently on the main page because it is one half of a pair now, and describing something accurately is not the same as changing it.
  • CUARay stays experimental. Same reason as v1.2 : an experimental tier should not have its factory values tuned as though it were settled.
  • The shared capture rig. Two things about it are known and neither is repaired here : LIVE's cost scales with scene complexity, and a large capture volume degrades what SMART can save. The honest fix is a change inside a class that three shipped products depend on, and making it unreviewed on a release day is a worse risk than the thing it fixes. Filed, not forgotten.
  • An optimisation we wrote and then removed. Skipping work for samples whose contribution is about to be multiplied away sounded free : "it costs one compare". Measured, it was a 7% regression, roughly five hundred times more expensive than the work it skipped. A branch on 100% of steps, wrapped around a texture fetch, is not free on a GPU. It is gone.



πŸ” Reviewed before release

SkyRay went through two independent code audits and two re-verification rounds before this page existed, followed by a save-load round trip and five field checks.

The part worth reporting is not that they found things. It is what kind of things:

  • Defects inside fixes. Two of the first round's findings were bugs in repairs for earlier findings, including one where a comment described the intent and the code beneath it did the opposite.
  • The far-field limit above was found by writing down the acceptance criterion. A reviewer asked us to check that a heavily clouded distance does not read brighter than the near field. Turning that into a number rather than a look showed the estimate was wrong, and wrong in the direction that reads as a defect. The check found it; the eye had not.
  • One check could not have failed as written. Another asked us to verify that a wall a metre away still shows haze rather than black. At factory air density a one-metre path scatters about 2% of the light : the correct frame and the broken one are both nearly black. It was rebuilt as a comparison between two step counts against the same wall, which either matches or does not, and it matched to within one least significant bit across 3.4 million pixels.

Every test that was run, and what it returned

TestWhat it askedResult
Instrument checkCan this bench read the differences it is about to printβœ… 0.09 ms spread across 9 rows spanning 24 table positions
Step ladderIs cost linear in march stepsβœ… 0.0530 ms/step, linear to three decimals 512 down to 128
Factory settingsIs there a measurable cost at 8Kβœ… 3.23 ms against a 3.23 ms floor, all three passes
PenumbraDoes a softer shadow edge cost moreβœ… The second tap costs 0.94 ms. Its width is free
Bake resolutionDoes sharper shadow capture cost frame timeβœ… 0.08 ms across 512 to 2048. Free per frame
Update strategyWhat does re-baking every frame cost⚠ +0.25 ms in this room. Scales with your object count, not with the panel
FramingDoes cost follow screen coverageβœ… 3.23 / 15.12 / 28.46 ms for near / room / open sky at one step count
ReachIs a longer march dearerβœ… No, cheaper : 26.32 ms at 300 m against 28.71 at 60 m
Haze densityDoes zero density short-circuit the marchβœ… Flat, and documented as such rather than gated
Sky mask costWhat do cloud shadows cost per stepβœ… 0.00503 ms/step, two ladders with one toggle between them
Shader de-specialisationWhy did one configuration return two answersπŸ”¬ The toggle, not the work : 27.99 to 35.2 to 28.15 ms
Cloud tile seamDoes the cloud field wrap without a visible joinβœ… Opposite edges agree to 2e-16, proved offline
Soak : driftDoes it get slower over a nightβœ… +0.018 ms across 10.5 hours
Soak : stutterDoes a hitch grow in that the mean would hideβœ… p95 +0.032 ms, tracking the mean
Soak : memoryDoes it leakβœ… Heap floor 890 MB at both ends of the night
AllocationHow much garbage per frameβœ… +3 B/frame over the bench floor. The march itself: none
VRDoes it match desktop in a headsetβœ… Identical, on the shipping build, in a moonlit forest
Load the shipping buildDo twenty commits of review fixes still load and renderβœ… Bundle resolved, lazy cloud bake correct, no rebuild
Save and reloadDoes every parameter come backβœ… All 34, floats within 1e-6. Diagnostics correctly not restored
Disabled pluginCan a trigger build a frozen box while it is switched offβœ… No, by three routes, and the work resumes on re-enable
Master offDoes the Report say it is downβœ… Every source flag clear, volume released
Overcast horizonDoes the far field read brighter than the near fieldπŸ”΄ Defect found at high cloud cover. Capped, and written up under Known limits
Wall one metre awayDoes a ray that ends inside the first march cell still drawβœ… 99.12% of 3.4 million pixels bit-identical to a 512-step reference
Teardown orderDoes unloading one Ray plugin break anotherβœ… Reference count correct across add, remove, and both orders

Twenty-four tests, one defect, and it is on this page rather than in a changelog next quarter. Two of them are worth reading twice : the wall test could not have failed as first written, and the horizon test found something the eye had not.

Everything they found is either fixed or written down as a limit. Nothing was closed by agreeing with it.



πŸ”¬ How it was measured, and where it failed

  • Three passes of ten seconds per row, with a four second settle, every setting written by the harness rather than by hand. Instrument quality on the run of record : 0.09 ms spread across nine rows spanning 24 table positions, and two identical configurations seventeen rows apart agreeing to 0.09 ms.
  • The panel was closed. VAM's own open plugin panel costs roughly 11 ms a frame. Measuring with it open measures the panel.
  • The rehearsal is not optional. Every table was dry-run first and read before the real pass was allowed to start. That is what caught the run below.
  • A whole 23-row pass was thrown away. The table's first row did not reset one switch, so twenty-three rows were measured with cloud shadows on that were supposed to be off. It was fixed at the class rather than in the table : every control now enrols itself for reset when it is created, so a control added next year cannot be forgotten.
  • The cloud shadow cost took three answers before it was right, and that is recorded rather than tidied away. Three answers to one question means the question was under-specified, which is exactly what it turned out to be : see the de-specialisation section above.
  • The numbers were taken on the development build. The march changed afterwards, in the fix for a reviewer's finding : the per-cell step count now rounds up rather than down, and a partial last cell is integrated over its true length. Sample positions along the ray are unchanged for any ray not cut short by geometry, which is every ray in these open-sky rows, so the figures stand. Two reviewers checked that argument rather than accepting it.
  • One later change only makes LIVE cheaper : the volume no longer marches itself into the shadow capture, once per bake. The 0.25 ms above is therefore an upper bound. It has not been re-measured, so it is printed unchanged rather than adjusted by an estimate.

Nothing here was estimated. Where a number could not be measured honestly it is not on this page, and where the instrument ran out we said so instead of extrapolating past it.



v1.2 made EnvRay's beam wide enough for the outdoors.
This one admits that was the wrong shape, and builds the right one.
πŸͺ„ LUMENFORGE v1.2
A quality-of-life and performance update. The same picture, a lot faster, and three controls that used to be out of reach.



In one screen

What changedWhat it gives you
⚑ Raymarch steps now ships at 24 instead of 48 (EnvRay and HeroRay)A 46% shorter frame with the shaft filling the screen, and no visible difference. Measured both ways.
🎯 HeroRay: Aim smoothing is a slider nowTracking can be as sharp or as soft as the shot needs. 0 = instant, for a light bolted to something that moves.
πŸŒ… EnvRay: Beam radius to 15 m (was 4)One shaft can cover an outdoor space instead of a window.
πŸŒ… EnvRay: Bake resolution (512 / 1024 / 2048)Keeps a wide beam's shadows sharp. Costs VRAM and nothing per frame.
πŸ“Š SMART now prints the corridor it is watchingYou can see what the strategy is actually doing instead of guessing.

Nothing was removed, no setting changed its meaning, and your saved scenes are untouched. Every slider keeps the range it had; only the value a newly added instance starts at has moved.



Where it came from

A creator working on real scenes asked for two things, and both were the same kind of ask: a value we had chosen for him that he wanted to choose for himself.

HeroRay's tracking carried a fixed amount of smoothing, tuned to read as a follow spot operator. It is a good default and a bad law, and he had a shot that wanted the opposite. EnvRay's beam radius reached 4 m, which is the right range for a window and the wrong one for a beach.

Opening the first of those led somewhere neither of us expected, and that turned into the main item on this page.



⚑ Raymarch steps now ships at 24 instead of 48

The problem, stated plainly

Every marching plugin in this line ships its quality knob at the slider's top : EnvRay at 48, HeroRay at 48, CUARay at 128 slices. That is the safe way to ship something whose look has never been priced, and it is the wrong place to leave it once it has been. Maximum quality had been standing in for "correct", and this update replaces the guess with a measurement.

What it costs

Bench, 7680x4320, one RTX 5090, shaft filling the frame, median of six passes:

Raymarch stepsWhole framefpsFrame time it addedThe shaft's own GPU cost*
48 (old default)10.86 ms927.62 ms9.84 ms
409.18 ms1095.95 ms8.20 ms
327.53 ms1334.29 ms6.56 ms
24 (new default)5.86 ms1712.63 ms4.92 ms
164.28 ms2341.05 ms3.28 ms
123.55 ms2820.32 ms2.46 ms

* Added frame time plus 2.25 ms. That is what this bench absorbs before anything shows up : the empty room is CPU-bound, so GPU work smaller than the slack changes nothing you can read. The figure is fitted from two completely different subjects measured on the same bench, whose lines came back with the same offset (-2.22 and -2.46 ms), and it is the reason rows at 8 steps and below report nothing at all.

Corrected for that, the shaft's cost is 0.205 ms per step. Exactly linear, with no offset left over.

What it looks like

Stepped through by eye, in several different real scenes, on EnvRay and HeroRay both:

Between 20 and 48 there is no perceptible difference.
Below about 18 it falls off sharply.

So the old default was charging four times over for a difference nobody can see.

What you actually gain

A 46% shorter frame in the bench above, where the shaft fills the screen. Your figure depends on how much of your frame the shaft covers, and that is something only you can see: a shaft crossing the whole view gains the most, and one over in the corner was never costing much to begin with.

Why 24 and not 20

The cliff is around 18. 24 keeps a third of a margin over it; 20 keeps a ninth. The failure modes are not symmetric: too high only spends performance you can see and turn down. Too low means the plugin looks wrong in somebody's scene and they cannot tell why. The scenes it was checked in are not all scenes.

There is also something non-obvious about that slider, worth knowing before you judge a value by eye. The number on it is not the number of samples inside the beam. The steps are spread along the whole camera ray, and only the ones landing inside the shaft do any work, so a framing that views the beam from outside spends a good share of them on empty air. Standing inside the beam is the one framing where the slider does equal the sample count, which makes it the framing that flatters a low value. Judge from outside, with something thin casting a shadow into the beam: fine shadow detail is the first thing to go.

The maximum is still 48 and nothing was taken away. Saved scenes are untouched, because a scene stores the value it was saved with : only newly added instances get the new default. If you preferred 48, drag it back and nothing on this page applies to you.



🎯 HeroRay : the damping is a slider now

Aim smoothing (s, 0 = instant), measured in seconds: how far behind the target the beam runs.

  • 0.083, the default, is the feel the plugin has always had. Your existing scenes do not change by a single frame.
  • 0 snaps every frame. This is the one for a light bolted to something that moves : a vehicle window, a swinging rig, an animation pattern you keep speeding up. There is no operator behind a moving window, so there should be no operator smoothing.
  • Anything between is a spring you can dial.

Two things worth knowing about what that damping was actually doing. It only ever applied to the aim, never to the position : the light itself was always instant. That is why the symptom grew the faster the light travelled, because the faster it moves the more angle it has to cover per frame. And the old form drifted with framerate, so the same setting felt different at 60 and at 90. The new one does not, which matters most in VR.

HeroRay's steps default moved to 24 in the same way, and that was checked by eye on HeroRay directly. The millisecond figures on this page are EnvRay's : HeroRay's own rows would not move our bench, so we are not going to print a number for it that we did not measure.



πŸŒ… EnvRay : the beam can be an outdoor space now

Beam radius goes to 15 m, up from 4. The default is unchanged at 1.5 m.

What we measured out there is not what we expected, so here it is:

Beam radiusThe shaft's share of the framevs the row above
4 m4.74 msβ€”
8 m5.52 ms+16%
15 m5.46 ms+0%

Cost saturates, and it saturates early. 4 m to 8 m adds 16%. 8 m to 15 m adds nothing at all, because once the shaft fills the frame there is nothing left to fill : the extra metres are free precisely because you are already paying the maximum. The jump you can feel is from a narrow beam to one that covers the view, and it happens well before the slider runs out.

⚠ Past about 7 m our bench room cannot see any further : a 15 m beam does not fit inside a 14 m room, so those last two rows are measuring a beam larger than its container rather than a wider beam. That is where the instrument stops, and we would rather say so than extrapolate past it.

Bake resolution : its companion, and it is free

A wide beam bakes soft. The capture is an orthographic camera as wide as the beam, so texel density is resolution divided by twice the radius: 1024 over a 1.5 m beam is 341 px per metre, and over a 15 m beam it is 34. Bake resolution (512 / 1024 / 2048) buys that back.

Measured at all three: within 0.11 ms of each other. It costs VRAM and nothing else per frame, which is what you would hope for from something the baked engine only does when it re-bakes.

SMART, in a wide beam

SMART wakes while a person is inside the beam corridor, and that corridor is the beam. At 15 m radius the corridor is 30 m across and nobody is ever outside it, so SMART settles into behaving like LIVE.

We deliberately did not clamp it. A narrower corridor would stop carving a person standing off-axis inside a wide shaft, which is a visible defect and a worse trade than SMART saving less. Instead the status line now prints the corridor width it is actually watching, so you can see it rather than guess at it.

And measured at 8K with an empty corridor, STATIC, SMART and LIVE differ by 0.00 ms. The v1.1 page said the strategy dropdown was the wrong knob for framerate. It is measured now rather than asserted.



🚫 What we did not change

  • CUARay's slice count stays at 128. It is the same "default pinned at the maximum" pattern, and it is staying there on purpose: CUARay still carries an EXPERIMENTAL banner, and an experimental tier should not have its factory values tuned as though it were settled. It is also a slice stack rather than a raymarch, so EnvRay's curve does not transfer to it.
  • HeroRay's per-frame timing. There is reason to think the tracking could be up to one frame tighter still, but proving it needs a step-input test we have not run. Shipping a timing change on reasoning alone is how you get a regression nobody can reproduce.



πŸ”¬ How it was measured, and where it failed

  • The same code-generated bench room as v1.1, camera snapped to numbered positions by the harness. Six passes of ten seconds per row, every setting written by the harness rather than by hand. The 48-step row was measured twice in every pass, once at the start and once after the whole ladder: 7.62 and 7.66 ms. That gap is the drift across the run.
  • 7680x4320, forced from the command line rather than chosen in the launcher, because the launcher's choice failed to reach the player once before and cost a whole run at the wrong resolution.
  • The step ladder ran first on purpose, because it is also the instrument's own calibration. It separates cleanly from 48 down to 12 and then closes completely at 8 and 4, so this bench cannot see anything below roughly 0.2 ms and nothing under that may be called free. That floor is measured rather than assumed, and assuming it is exactly what went wrong earlier the same day.
  • It could not measure two of our own plugins. HeroRay's and CUARay's rows came back flat end to end. For CUARay the reason is known: the bench window is one metre square, so its slice volume covers almost no screen. For HeroRay it is not yet known. Those blocks produced no data, and this page prints no numbers for them.

Nothing here was estimated. Where a number could not be measured honestly it is not on this page, and where the instrument ran out we said so instead of extrapolating past it.



Thanks to the creator who asked whether the tracking could be sharper.
One question about a follow spot, and the whole line got faster.
Why this update exists

On the official VaM Discord, @AlphaCentauri reported a noticeable framerate drop from a baked EnvRay shaft in an apartment scene with no Person atoms in it at all, and asked whether he had set it up wrong.

He had not. Our documentation was wrong.

v1.0 described the baked engine as having "zero running cost". What is actually free is the light slot and the re-bake. The shaft itself is a volumetric raymarch and it is re-integrated every frame, from wherever your camera is, whether or not anyone is standing in it. Baking freezes the bake, not the beam.

That report is the reason for everything below. The wording is corrected, and then the thing the wording was wrong about got measured properly and rebuilt around what the measurement said.



⚑ What it costs now

Framingv1.0v1.1Frame timeThe shaft's own cost
Shaft crossing the frame (typical)13.51 ms6.51 ms-51.8%10.28 β†’ 3.28 ms (3.1x)
Standing inside the shaft (worst case)23.13 ms13.71 ms-40.7%19.90 β†’ 10.48 ms (1.9x)
Shaft entirely behind a wallnearly full price0.00 msfree19.6 β†’ 0.00 ms

The last row is the one that matters most in a real interior. A shaft in the next room used to cost almost as much as one filling your screen, because the volume was still being integrated behind the wall. It now exits before doing any work at all.

What that means for your framerate

The shaft got cheaper by roughly three times. Your framerate gain depends on how much of your frame the shaft was, which is something only you can see:

If you were at 60 fps and the shaft was...expect roughly
30% of your frame time~75 fps
50% of your frame time~91 fps
70% of your frame time~114 fps

⚠ Reference only, and here is what that qualifier actually covers. Every number on this page was measured on a single RTX 5090 at 7680x4320, in a purpose-built empty test room where the shaft is nearly the whole cost. Your GPU, your resolution and your composition all move these figures, and the largest of the three optimisations trades arithmetic for texture lookups, which is a bargain whose value depends on GPU architecture. We have no data from any card other than that one. If your result differs, that is information we want.



πŸ”§ What actually changed

1. The noise is now a lookup table (most of the win)
Haze and Dust turned out to be 88.9% of the shaft's entire cost. Both were procedural value noise, evaluated three times per raymarch step, each evaluation eight hashes and seven blends. They are now one texture fetch each from a small tiled 3D table built at load.

v1.0 proceduralv1.1 lookup
Haze11.40 ms2.61 ms4.4x
Dust6.16 ms2.22 ms2.8x

Side effect worth having: the old code fed world position plus elapsed time into the noise, and after a few hours at dust frequencies that coordinate ran out of floating point precision and the grain slowly degraded. The table tiles, so the coordinate can be wrapped exactly. That failure mode is now structurally gone.

If you prefer the original look, Noise quality on the EnvRay panel has a Legacy hash option. It costs about twice as much and, in a still frame, the two differ by 1.3% in average brightness.

2. The march no longer walks the empty parts of the ray
The shader stepped from the camera to the far side of the beam volume in a fixed number of steps, so a large share of them landed in empty air outside the shaft, doing nothing. It now solves where the ray enters and leaves the volume and visits only the steps in between. Same sample positions, same image : the difference is that the empty ones are skipped.

Worth 8-14% depending on framing, and worth nothing at all when you are standing inside the shaft, because then there is no empty part to skip.

3. A hidden shaft exits immediately
Falls out of the same solve. If the ray meets solid geometry before it reaches the volume, the pixel returns black without entering the loop.

4. StageLightGroup : flare offset fixed
With several fixtures in one group and Flare enabled, the flare billboards collapsed onto a single position. Unity's two merging paths both require the merged renderers to share a material, so each fixture now carries its own flare material, which rules the merge out by construction rather than by hoping.



🚫 What we tried and did not ship

  • Drawing only the back faces of the beam volume. On paper this halves the work. Measured: no gain at all : the front-face pixels were already exiting almost immediately. It would have carried a small risk to the shaft's silhouette for nothing, so it is not in the build.
  • A smaller lookup table for the haze. Faster when Haze runs alone, but its repeat period in metres would then be short enough to see inside a long shaft. Rejected on looks, not on speed.

Both are listed because "we measured this and decided against it" is a more useful thing to know than a list of wins.



🎚️ If it is still too expensive

In the order that actually helps:

  1. Haze off, then Dust off. Even after this update they are most of the shaft's cost. Together they are worth more than everything else on this list combined.
  2. Raymarch steps 48 β†’ 24. Cost is exactly linear in steps, so this is exactly half.
  3. Make the shaft smaller in frame. Beam radius, throw depth, or simply not composing it across the whole view. Screen coverage is a straight multiplier.
  4. Fog softening off. Smallest of the four.

Changing STATIC / SMART / LIVE will not help. Measured, the three differ by 0.05 ms, which is 0.2% of the shaft. They decide how often the bake re-runs, and the bake was never the expensive part. This is the same misunderstanding the v1.0 wording encouraged, so it is worth stating twice.



πŸ”¬ How it was measured

  • A purpose-built test room, generated by a plugin rather than hand-assembled, so the fixture is identical every run and on anyone's machine. The camera is snapped to numbered positions by code, because "same seat as last time" is not a measurement.
  • 8 passes of 10 seconds per configuration, every setting written by the harness rather than by hand. Scatter per row 0.01 to 0.08 ms; drift across an hour under 0.2%.
  • 7680x4320, forced with DSR. An earlier attempt at 4K produced a completely flat table : the shaft was cheaper than the CPU floor, so every row read the same and Dust and Fog looked free when they were not.
  • Every configuration was checked both ways. If a change should make one row move and another row stay still, both halves were required before it was believed. Two conclusions were thrown away during this work because the row that was supposed to stay still had never been able to move in the first place.

Nothing here was estimated. Where a number could not be measured honestly it is not on this page.



Thanks to @AlphaCentauri, who reported the issue after wondering if he'd set something up wrong.
He had not, and the question was worth more than a bug report.
Back
Top Bottom