LUMENFORGE v1.3
A ninth plugin, and it is the outdoors. Full-screen sun and moon shafts, carved by the scene you actually built.
In one screen
| What changed | What it gives you |
|---|
New : SkyRay | Volumetric 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 moon | Nothing in it is solar. Set the colour cold and dim and it is moonlight, same code, same cost. |
Cloud shadows without a sky | A 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 document | PERF_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 half | Nothing about it changed. The page just stops asking it to be two things. |
Reviewed before release | Two 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
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 steps | Frame time | Above the floor |
|---|
| 512 (amplifier, not a setting) | 28.43 ms | 25.20 ms |
| 256 | 14.92 ms | 11.69 ms |
| 128 | 8.07 ms | 4.84 ms |
| 48 | 3.44 ms | 0.21 ms |
| 24 (factory) | 3.23 ms | 0.00 ms |
| empty scene, everything off | 3.23 ms | floor |
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.
| Setting | Reading | What it means |
|---|
| Penumbra 0 / 1 / 3 | 27.40 / 28.34 / 28.34 ms | The second shadow tap costs 0.94 ms. Its width is free. |
| Bake resolution 512 / 1024 / 2048 | 28.42 / 28.46 / 28.50 ms | Free per frame. It buys shadow sharpness with VRAM. No reason to lower it chasing frames. |
| STATIC / LIVE | 28.50 / 28.75 ms | 0.25 ms in this room. See the warning below. |
| Framing : near / room / open sky | 3.23 / 15.12 / 28.46 ms | Cost tracks how much of the screen the shafts cover, not how big your world is. |
| Reach 300 / 60 | 26.32 / 28.71 ms | Longer reach is cheaper : bigger cells make transmittance decay faster, so the ray gives up sooner. |
| Haze density 0 / 0.02 / 0.3 | 27.75 / 28.48 / 28.48 ms | Flat. 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 session | At 512 steps | The mask appears to cost |
|---|
| Never touched the switch | 28.4 ms | baseline |
| Switched it on at some point | 36.3 ms | +7.9 ms |
| Both halves on an already-generic shader | 33.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
| Test | What it asked | Result |
|---|
| Instrument check | Can this bench read the differences it is about to print | 0.09 ms spread across 9 rows spanning 24 table positions |
| Step ladder | Is cost linear in march steps | 0.0530 ms/step, linear to three decimals 512 down to 128 |
| Factory settings | Is there a measurable cost at 8K | 3.23 ms against a 3.23 ms floor, all three passes |
| Penumbra | Does a softer shadow edge cost more | The second tap costs 0.94 ms. Its width is free |
| Bake resolution | Does sharper shadow capture cost frame time | 0.08 ms across 512 to 2048. Free per frame |
| Update strategy | What does re-baking every frame cost | β +0.25 ms in this room. Scales with your object count, not with the panel |
| Framing | Does cost follow screen coverage | 3.23 / 15.12 / 28.46 ms for near / room / open sky at one step count |
| Reach | Is a longer march dearer | No, cheaper : 26.32 ms at 300 m against 28.71 at 60 m |
| Haze density | Does zero density short-circuit the march | Flat, and documented as such rather than gated |
| Sky mask cost | What do cloud shadows cost per step | 0.00503 ms/step, two ladders with one toggle between them |
| Shader de-specialisation | Why did one configuration return two answers | The toggle, not the work : 27.99 to 35.2 to 28.15 ms |
| Cloud tile seam | Does the cloud field wrap without a visible join | Opposite edges agree to 2e-16, proved offline |
| Soak : drift | Does it get slower over a night | +0.018 ms across 10.5 hours |
| Soak : stutter | Does a hitch grow in that the mean would hide | p95 +0.032 ms, tracking the mean |
| Soak : memory | Does it leak | Heap floor 890 MB at both ends of the night |
| Allocation | How much garbage per frame | +3 B/frame over the bench floor. The march itself: none |
| VR | Does it match desktop in a headset | Identical, on the shipping build, in a moonlit forest |
| Load the shipping build | Do twenty commits of review fixes still load and render | Bundle resolved, lazy cloud bake correct, no rebuild |
| Save and reload | Does every parameter come back | All 34, floats within 1e-6. Diagnostics correctly not restored |
| Disabled plugin | Can a trigger build a frozen box while it is switched off | No, by three routes, and the work resumes on re-enable |
| Master off | Does the Report say it is down | Every source flag clear, volume released |
| Overcast horizon | Does 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 away | Does 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 order | Does 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.