Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🐛 bug report] The Principled BSDF loses energy due to transmission lobe scaling #465

Closed
knightcrawler25 opened this issue Dec 25, 2022 · 2 comments

Comments

@knightcrawler25
Copy link

knightcrawler25 commented Dec 25, 2022

Summary

The Principled BSDF loses energy due to the way the transmission lobe is scaled for the interior case

System configuration

OS: Windows-10
CPU: AMD64 Family 23 Model 113 Stepping 0, AuthenticAMD
GPU: NVIDIA GeForce RTX 3060 Ti
Python: 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)]
NVidia driver: 527.41
CUDA: 12.0.76
LLVM: 10.0.0

Dr.Jit: 0.3.2
Mitsuba: 3.1.1
Is custom build? False
Compiled with: MSVC 19.34.31933.0
Variants:
scalar_rgb
scalar_spectral
cuda_ad_rgb
llvm_ad_rgb

Description

Mitsuba's implementation of the Disney BSDF disables the diffuse/sheen, clearcoat lobes for the interior (at 2 in the following diagram) as mentioned in this report: mmp/pbrt-v3#313. Presumably, this was done to avoid energy gain with several bounces as the Disney model is not strictly energy conserving.

However, the transmission lobe in the eval() function is scaled by bsdf = (1.0f - metallic) * spec_trans for the interior case, even though the interior is sampled as if it were a dielectric. This causes the transmission lobe to lose energy when spec_trans is set to a value other than 0 or 1.

diagram

Line 615:

// Adding the specular transmission component
dr::masked(value, spec_trans_active) +=
dr::sqrt(base_color) * bsdf *
dr::abs((scale * (1.0f - F_spec_dielectric) * D * G * eta_path *
eta_path * dr::dot(si.wi, wh) * dr::dot(wo, wh)) /
(cos_theta_i * dr::sqr(dr::dot(si.wi, wh) +
eta_path * dr::dot(wo, wh))));

Similarly, F_dielectric in the principled_fresnel code for the interior case is scaled the same way:

return dr::select(front_side, F_front, bsdf * F_dielectric);

The following image shows a furnace test with various spec_trans and metallic values. Roughness is set to 0 so that energy loss due to microfacet single scattering shouldn't be an issue. Ideally, the box should match the background, but it gets darker for some of the cases

Edit: Cycles does not appear to have significant issues with energy gain/loss even though it does not disable lobes for the interior.

Edit2: For the interior, I found Cycles evaluates the dielectric/metallic/clearcoat lobes as if the surface was hit from the outside i.e. eta is not flipped and fresnel is calculated accordingly, similar to how the twosided adapter handles backfaces. Only the specular BSDF closely couples reflection/transmission and the interface is properly handled, otherwise all other lobes just treat the inside the same way as the outer surface. I've tried doing it this way in my toy renderer and the energy gain issue mentioned in mmp/pbrt-v3#313 goes away, mainly due to the dielectric layer/lobe. Here is a .blend file and the corresponding OSL code if you wish to try it out:
Principled BSDF OSL.zip.

Edit3 (hopefully my last edit lol): The thread here shows that Arnold handles the interior the same way as Cycles i.e. the surface is evaluated as if its hit from the outside: https://groups.google.com/a/arnoldrenderer.com/g/standard.surface/c/9agBPBEkMV4

furnace_test_orig

Steps to reproduce

Following is the scene file for the furnace test

<?xml version="1.0"?>
<scene version="2.0.0">

    <integrator type="path">
        <integer name="max_depth" value="65"/>
    </integrator>

    <sensor type="perspective" id="camera">
        <float name="fov" value="45"/>
        <transform name="to_world">
        <lookat origin = "3, 3, 3"
                target = "0, 0, 0"
                up     = "0, 1, 0"/>
        </transform>

        <sampler type="independent">
            <integer name="sample_count" value="16"/>
        </sampler>

        <film type="hdrfilm" id="film">
            <integer name="width" value="300"/>
            <integer name="height" value="300"/>
            <string name="pixel_format" value="rgb"/>
            <rfilter type="box"/>
        </film>
    </sensor>

    <bsdf type="principled" id="Material">
        <rgb name="base_color" value="1, 1, 1"/>
        <float name="eta" value="1.45"/>
        <float name="roughness" value="0.0"/>
        <float name="spec_trans" value="0.5"/>
        <float name="metallic" value="0.0"/>
    </bsdf>

    <shape type="cube">
        <ref name="bsdf" id="Material"/>
    </shape>

    <emitter type="constant">
        <rgb name="radiance" value="0.3"/>
    </emitter>

</scene>
@knightcrawler25
Copy link
Author

An update:

Turns out I was very wrong about a couple of things:

  1. After cranking up the bounces in Cycles and disabling clamping, it has all the same issues with the interior when clearcoat and sheen are used. Most likely people don't experience these issues as clamping is enabled and bounces are set to a fairly low amount.

  2. My renderer wasn't handling fresnel coefficients correctly while sampling which is why I didn't have issues with fireflies (my implementation was incorrect). After spending a lot of time fixing all the issues, I have to come to the same conclusion as the project report and disabling lobes for the interior makes a lot of sense now :) Losing energy is much better than gaining more with each bounce and eventually blowing up the scene.

@njroussel
Copy link
Member

@knightcrawler25 Thanks for keeping this issue updated.
We've been getting quite a few issues concerning the principled plugin, and I've been meaning to take a deeper look into its implementation.

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

No branches or pull requests

2 participants