Problem understanding Material::Sample () behaviour

Discussion related to the LuxCore functionality, implementations and API.
julesmay
Posts: 7
Joined: Wed Dec 16, 2020 5:32 pm

Problem understanding Material::Sample () behaviour

Post by julesmay » Wed Dec 16, 2020 5:59 pm

Hi.
I'm currently developing a modified glass material for Luxcore on behalf of a client of mine, but I'm getting some behaviour out of the Sample function which I don't understand, and I wonder if you can provide some advice?

Inside Sample, I have code like this (rather like that from glass.cpp):

Code: Select all

    if (passThroughEvent < threshold) {
        // Transmit
        *localSampledDir = -localFixedDir;
        *event = SPECULAR | TRANSMIT;
        *pdfW = threshold;
        return kt; // kt is the precomputed transmit colour
    } 
    else if (passThroughEvent > threshold) { // reason for this will become apparent in a moment.
        // Reflection
        //again, standard code, elided for clarity
    } 
    else return Spectrum ();
This behaves exactly the way I'd expect, and I'm comfortable with it. But if I modify it thus:

Code: Select all

    if (passThroughEvent < threshold/2.f) {
        // Transmit
        *localSampledDir = -localFixedDir;
        *event = SPECULAR | TRANSMIT;
        *pdfW = threshold;
        return kt * 2; 
    } 
    else if (passThroughEvent > threshold) { 
        // Reflection
        // ...
    } 
    else return Spectrum ();
then the transmitted colour is half of what it should be. Multiplying the colour by two doesn't compensate for the halved number of samples. But it gets weirder. If I do this:

Code: Select all

   if (passThroughEvent < threshold/2.f) {
        // Transmit
        *localSampledDir = -localFixedDir;
        *event = SPECULAR | TRANSMIT;
        *pdfW = threshold;
        return kt * 20; // Loads of gain
    } 
    else if (passThroughEvent > threshold) { 
        // Reflection
        // ...
    } 
    else return Spectrum ();
then rays from the eye through the material and onto a background remain darkened, but rays from a light through the material and onto a diffuse surface seem to get the benefit of the brightness.

As far as I can see, the only place where the transmitted colour is set is in this function, so I don't understand how the additional gain can be used some of the times and not others. So, may I ask, please:

1. Why/how is the brightness of the colour treated differently depending on whether we're looking through it or shining light through it?
2. What do I need to do to create high-gain transmission?
3. What is the role of pdfW here? I fiddled with it, but couldn't see any noticeable effect.

Thanks

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Wed Dec 16, 2020 10:25 pm

julesmay wrote:
Wed Dec 16, 2020 5:59 pm
then rays from the eye through the material and onto a background remain darkened, but rays from a light through the material and onto a diffuse surface seem to get the benefit of the brightness.
Material::Sample() is used to bounce a path (from eye or light source) over the surface. Material::Evaluate() is used to evaluate direct light sampling.

If you dont keep the two implementations aligned, you get results that don't make any sense. I can not say with seeing all the code but I have the feeling your problem is there.
Support LuxCoreRender project with salts and bounties

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Wed Dec 16, 2020 10:32 pm

The above is the answer to question #1.

#2 => why don't you use a volume emitting light ? It seems to be your case.

#3 => it is the used the Probability Density Function (https://en.wikipedia.org/wiki/Probabili ... y_function). But it is already factored in the Spectrum returned (you can see that in the original code).
Support LuxCoreRender project with salts and bounties

julesmay
Posts: 7
Joined: Wed Dec 16, 2020 5:32 pm

Re: Problem understanding Material::Sample () behaviour

Post by julesmay » Thu Dec 17, 2020 1:22 pm

Hi, Dade, thanks for the very quick reply.

#1: I just copied the GlassMaterial::Evaluate. It returns just Spectrum (). That being so, I don't understand where else my transmission colour is coming from.

#2: I'm trying to model a coated glass material whose properties have been measured physically. Thus, I know what the transmission is (at various incidence angles), and I know what the reflection is (at various angles). I know, because these numbers came from physical measurement, they must sum to less than 1. So, I know that at some angle the transmission is kt. In Luxcore I can use the threshold to decide whether the photon is transmitted or reflected. I can transmit always (and attenuate by kt), then the transmission is right but I have no room left for a reflected photon. On the other hand, if the photon is transmitted (with probability mean (kt)), then the attenuation should be kt/mean(kt), not kt. That's why I'm trying to adjust the gain on the transmission. (Similar on the reflection - but there are actually two different reflection components).

#3: I understand what a probability density function is! What confuses me is:
  • there are lots of places where you refer to pdfs, but they're floats, not functions. I'm guessing that you're presupposing the pdf is a standard distribution, like a normal, and what the numbers are actually measuring is either the width of the distribution (inverse height), or the height of the distribution (inverse width). Does that sound right?
  • Why, when the event is purely specular, is there any probabilistic component at all? (I tried frobbing the numbers, but couldn't see any effect.)
  • Even in a diffuse event (not my use case, but I'm asking anyway), what is being randomized? The diffuse ray is invented inside ::Sample(), by CosineSampleHemisphere(), isn't it?
Thanks again for helping.

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Thu Dec 17, 2020 2:39 pm

julesmay wrote:
Thu Dec 17, 2020 1:22 pm
#1: I just copied the GlassMaterial::Evaluate. It returns just Spectrum (). That being so, I don't understand where else my transmission colour is coming from.
If you are using the same glass code, it is a "Delta" material (i.e. as safety-check, look the Material::IsDelta() method returned value). So you can safely ignore the Evaluate() method because direct light sampling is disabled for "Delta" materials (i.e. pure specular one).
julesmay wrote:
Thu Dec 17, 2020 1:22 pm
In Luxcore I can use the threshold to decide whether the photon is transmitted or reflected. I can transmit always (and attenuate by kt), then the transmission is right but I have no room left for a reflected photon.
This is exactly what pdf is for. For instance, if you transmit 75% of times and reflect the other 25%, you have two possible implementations:

1) you trace 2 rays, one for the transmitting direction and one for the reflected. This requires recursion support, doesn't work on GPUs, etc. It is sometime called "branching" ray tracing or ray "splitting".

2) you pick only one path (transmission or reflection) but you "compensate" with the PDF. You return "<transmitted color>/<transmittingPDF>" or "<reflected color>/<reflecting PDF>".
julesmay wrote:
Thu Dec 17, 2020 1:22 pm
there are lots of places where you refer to pdfs, but they're floats, not functions.
It is the PDF of picking transmission or reflection and the picked direction (however, it is a specular material so the direction PDF is just 1).
julesmay wrote:
Thu Dec 17, 2020 1:22 pm
Why, when the event is purely specular, is there any probabilistic component at all?
There is a probabilistic component between the option to transmit of reflect (see my answer to #2).
Support LuxCoreRender project with salts and bounties

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Thu Dec 17, 2020 2:44 pm

BTW, the implementation of your material should be quite simple: it should look exactly like current glass code but instead of using a constant "transmitted" color you should have a look up table where you look up the value by the angle (I assume you can interpolate the value between 2 consecutive entries in the table).
Support LuxCoreRender project with salts and bounties

julesmay
Posts: 7
Joined: Wed Dec 16, 2020 5:32 pm

Re: Problem understanding Material::Sample () behaviour

Post by julesmay » Thu Dec 17, 2020 7:19 pm

Thanks for your advice.

I can confirm, I had ::IsDelta() set correctly. Didn't seem make a difference either way.

In the course of re-arranging my code to create a more obvious test case, suddenly it came right! As far as I can tell, it makes no difference to the result no matter how pdfW is set, and I think there's some oddity in the handling of the return value (increasing the value doesn't brighten the image, but decreasing it darkens it - is there a clamp somewhere downstream?)

But: it's working. Don't really understand how, but it's working. Thanks for your help.

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Thu Dec 17, 2020 11:48 pm

julesmay wrote:
Thu Dec 17, 2020 7:19 pm
In the course of re-arranging my code to create a more obvious test case, suddenly it came right! As far as I can tell, it makes no difference to the result no matter how pdfW is set, and I think there's some oddity in the handling of the return value (increasing the value doesn't brighten the image, but decreasing it darkens it - is there a clamp somewhere downstream?)
The pdf is incorporated in the returned result: https://github.com/LuxCoreRender/LuxCor ... s.cpp#L256

However it also returned as separate value for some additional computation like MIS (Multiple Importance Sampling): https://github.com/LuxCoreRender/LuxCor ... r.cpp#L614

In your case, you are lucky because there is no MIS between direct light sampling and BSDF sampling for a Delta material and so it doesn't really matter. There is only BSDF (aka material) sampling.
Support LuxCoreRender project with salts and bounties

julesmay
Posts: 7
Joined: Wed Dec 16, 2020 5:32 pm

Re: Problem understanding Material::Sample () behaviour

Post by julesmay » Fri Dec 18, 2020 2:57 pm

MIS == Multiple Importance Sampling. Ah! Thank you.

I'd like, if I may, to show you some results I'm getting. First, what I think is the canonical case. Code (inside ::Sample() ) is:

Code: Select all

    const float threshold = 1.f;
    if (passThroughEvent < threshold) {
        // Transmit
        *localSampledDir = -localFixedDir;
        *event = SPECULAR | TRANSMIT;
        *pdfW = 1.0f;
        return kt;
    }
    else return Spectrum (); // no reflection
And the result (for kt = 1 .9 .8) is:
Correct behaviour
Correct behaviour
image (1).png (55.13 KiB) Viewed 1005 times
When I change the code thus:

Code: Select all

    const float threshold = 0.5f;
    if (passThroughEvent < threshold) {
        // Transmit
        *localSampledDir = -localFixedDir;
        *event = SPECULAR | TRANSMIT;
        *pdfW = 0.5f; // as you say, not relevant, but done for completeness
        return kt / 0.5f;
    }
    else return Spectrum (); // still no reflection
the result is:
Incorrect behaviour
Incorrect behaviour
image (2).png (58.45 KiB) Viewed 1005 times
If I'm understanding you, you'd expect these two images to be identical?

User avatar
Dade
Developer
Posts: 4974
Joined: Mon Dec 04, 2017 8:36 pm
Location: Italy

Re: Problem understanding Material::Sample () behaviour

Post by Dade » Fri Dec 18, 2020 3:19 pm

julesmay wrote:
Fri Dec 18, 2020 2:57 pm
If I'm understanding you, you'd expect these two images to be identical?
Nope, in the first case you are always returning "kt". In the second case you are returning BLACK half of the times. How can the results be identical ?

"passThroughEvent" is random variable between 0.0 and 1.0 (i.e. [0, 1[).
Support LuxCoreRender project with salts and bounties

Post Reply