Skip to content

fix/volume detection - #730

Draft
thot-experiment wants to merge 4 commits into
LuxCoreRender:mainfrom
thot-experiment:fix/volume-detection
Draft

thot-experiment wants to merge 4 commits into
LuxCoreRender:mainfrom
thot-experiment:fix/volume-detection

Conversation

@thot-experiment

Copy link
Copy Markdown
Contributor

This is a group of fixes that irons out a few bugs when detecting camera volumes, see commit messages for more info.

Comparison Grid
grid

thot-experiment and others added 4 commits October 8, 2026 22:36
PathTracer::RenderLightSample() and the PhotonGI photon tracer started
every light path with an empty volume stack and never read the light's
volume (scene.lights.<name>.volume), unlike BIDIRCPU which already
pushes it. RenderLightSample() is used by LIGHTCPU and by the hybrid
back/forward light tracing of PATHCPU and PATHOCL (native threads).

In hybrid mode caustic paths are rendered only by the light tracer, so
for a light inside a volume the first light path segments ignored the
volume whenever they ended on an object without an exterior volume:
volume caustics went missing and surface caustics were too bright, as
the fog attenuation before the first surface was skipped.

The light paths now start inside the light volume (no change for
lights without a volume).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BlendLuxCore (and most scenes) define the volume of a light only when
the user explicitly sets one, so the light paths of a light inside a
volume started with an empty volume stack. Each segment then took its
volume from the surface it ended on, which is wrong for segments ending
on objects inside the volume without an exterior volume (i.e. a glass
sphere inside fog): the fog was ignored along those segments.

Lights without a scene-defined volume now get the volume including
their position:

- Scene::GetPointVolume() follows a ray through all the surfaces and
  collects the volumes including its origin: the interior volumes of
  the objects exited without having been entered (only objects with an
  interior volume are counted, as they must be closed meshes) and the
  exterior volume of the first surface entered, if defined. The result
  is the current volume of a PathVolumeInfo filled with them from the
  outermost to the innermost, so volume priorities are applied as for
  a path entering all of them from the outside. Without any, the
  default world volume is used.
- Scene::UpdateLightVolumes() runs after the light preprocessing (on
  geometry, material, light and image map edits). It uses the origin of
  an emitted ray as the light position; environmental lights keep no
  volume and the triangles of a mesh light share one lookup.
- LightSource::autoVolume marks the computed volumes: an explicit
  scene.lights.<name>.volume, or the exterior volume of the material of
  a mesh light, is kept as before, and computed volumes are not written
  back by ToProperties().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With scene.camera.autovolume.enable (the BlendLuxCore default), the
camera volume was taken from the first surface hit by a ray through the
center of the screen: its exterior volume when entering, its interior
volume when exiting. With a camera inside a volume and the center of
the screen on an object without an exterior volume (i.e. a glass object
inside fog), the camera got no volume: eye paths started with an empty
volume stack and the fog disappeared in front of all the objects inside
the volume, depending on where the camera was aimed.

The camera volume is now the volume including the camera position,
computed with Scene::GetPointVolume() as for light sources (including
the volume priorities).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scene::Intersect() (CPU and GPU) used the same pseudo-random number,
passThrough, for two decisions: the volume scattering along the ray
segment and, as the pass-through event of the hit point, the event
sampled by the material at the end of the segment (i.e. glass uses it
to choose between reflection and transmission). The code already noted
the "subtle correlation".

The correlation is not subtle: a segment has no scattering event only
when u >= 1 - exp(-sigmaS * distance), while glass transmits only when
u < threshold, with the threshold clamped to at most 0.75. Behind an
optical depth larger than ln(4) (~1.39), every ray reaching the glass
without scattering was reflected and refractions seen through fog
disappeared entirely (i.e. a glass sphere 3.5 units away in a fog with
sigmaS = 0.5).

The volume scattering now uses its own pseudo-random number, the first
value of a Tausworthe generator seeded with passThrough (the same on
CPU and GPU), while passThrough is still used for the pass-through
events. With absorption-only fog, the refracted image is now attenuated
by the expected exp(-sigmaA * distance).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@howetuft
howetuft requested a review from CodeFHD October 9, 2026 07:13
@howetuft

howetuft commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

For this one, I requested @CodeFHD's review, as he's much more an expert in that field than me.

@CodeFHD

CodeFHD commented Oct 9, 2026

Copy link
Copy Markdown
Member

Noted, I will review on the weekend

@CodeFHD

CodeFHD commented Oct 10, 2026

Copy link
Copy Markdown
Member

@thot-experiment Can you share your test scene for comparison purposes? I will set up my own as well.

@thot-experiment

Copy link
Copy Markdown
Contributor Author

oh i'm so sorry, i had actually made it public i just forgot to link it

https://github.com/thot-experiment/luxcore-demo-scenes/tree/main/fix_volume-detection

@CodeFHD

CodeFHD commented Oct 11, 2026

Copy link
Copy Markdown
Member

I've been looking into this today. I did find some side-effects of this, as well as other observations that seem to be unrelated.

First, a conceptual problem: the new function UpdateLightVolumes() adds meshlight volumes in a preprocessing step based on a single ray with fixed parameters:
light.Emit(*this, 0.f, .5f, .5f, .5f, .5f, .5f, ray, emissionPdfW);
The consequence of this is that when you have a mesh light (triangle), sticking out of a scattering volume, all rays originating from the light will start with the same volume. Imagine for example a light emerging from a water surface. The volume check now does not happen per ray, but there will be an all-or-nothing cutoff at some point of emergence. (Due to the nature of the rays involved, I think it will be visible only in BiDir and may be hidden behind bug #678)

Another note on this, given those fixed parameters, emissionPdfW will always be > 0.f, so the following check will always be true.

Moving on, the addition of pathInfo.volume.AddVolume(light->volume); (commit f7a508f) sounds reasonable, but on its own doesn't seem to do much.
There is a regression (not solved by the full PR) that when the light source actively has the same volume assigned as it is in, the light will render black after moving anything in the viewport. I did not yet figure out why this happens.

Another regression happens when dealing with overlapping volumes, where somehow wrong volumes are being taken. See my attached scene (description below) and move the object Glass Top in negative x-direction to overlap with the edge of VolumeBox

I also noticed some changed behavior depending on whether objects have internal and or external volumes assigned or nothing connected, even though this should imply the same clear volume.

I still need to understand the rest properly, and figure out what was a pre-existing condition and what is new.

Attached is a test-scene of my own.
It contains three lasers in a box with internal scattering volume. On the left there is a matte box as a target and a small dummy-glass plate in front so that light-tracing rays will show up for all combinations.
The lasers are at different depth so that the spot should get dimmer due to scattering losses. The bottom laser is just inside the scattering volume, as the 100% reference but still starting in the volume.
For the other two lasers, I have set up additional glass blocks inside the scattering volume, which itself have a clear internal volume. The middle one is very long, so it should effectively shorten the scattering distance and thus the dimming. You can play with their positions, lengths, and (dis-)connecting the volumes on material "Nullglass".

test_PR730.zip

Let me know if I missed anything obvious in terms of the intended changes.

@thot-experiment
thot-experiment marked this pull request as draft October 13, 2026 18:45
@thot-experiment

thot-experiment commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, I was aware of this

The consequence of this is that when you have a mesh light (triangle), sticking out of a scattering volume, all rays originating from the light will start with the same volume.

but I had thought this was the original behavior. I wasn't aware of the other bugs, I've found some other strange interactions with volume detection in further testing, for one it seems like a camera inside a bubble causes scattering to behave strangely in the outer medium.

I've moved this to a draft PR until I have some time to look into it, no need to bother investigating further unless you feel like it. Thanks again.

@CodeFHD

CodeFHD commented Oct 13, 2026

Copy link
Copy Markdown
Member

Well, at least this PR induces the specific scenario. Before, you can induce it by assigning a fixed exterior volume, which is not what you are doing in your test scene.

You have to consider the problem of automatically determining volumes in 3D modelling. If you look at it from the reality perspective, all meshes need to be manifold with well defined interior/exterior sides (no faces crossing), and only defining interior volumes. Then, for auto-detection, you might need to trace two or more rays until you hit a surface on its interior side (imagine bouncing off multiple mirrors inside a scattering volume), trace additional diagnostic rays, or probably other techniques exists to determine which volume a point is inside, I don't know.

In Blender, you may create non-manifold meshes, making it more complex. I am not sure how much that can ever be worked around though (personally I would not treat it more than really necessary, but that is my preference for spending time).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants