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

Uniform Light Sampling Inconsistency Across PBRTv3 and PBRTv4 #471

Open
pedrovfigueiredo opened this issue Dec 28, 2024 · 5 comments
Open

Comments

@pedrovfigueiredo
Copy link

pedrovfigueiredo commented Dec 28, 2024

Upon testing the uniform light sampler for Bitterli's standard Classroom scene, I noticed a very significant difference in the rendered images for v3 and v4. The issue is more pronounced when using triangle mesh light sources. This happens even when simplifying all materials to only use pure lambertian diffuse in both cases.

As seen in the attached renderings of 256spp, the main discrepancy happens near the light sources that appear to be handled very differently in both versions. For this example, I'm using the path integrator with maxdepth=1 to mainly test the direct light sampling; and I'm using the simplest independent/random sampler.

v3-256spp
v4-256spp

Intuitively, it would make more sense to believe the v3's results since there are roughly 1.2k triangle lights in this scene, so randomly sampling 256 of those per pixel would still generate significant noise in the ceiling given that most triangle lights would not be visible.

Is there a special handling on the light sampling that makes it smoother in v4, or is it really a bug?

I'm attaching the slightly modified classroom scene used in the renderings above (diffuse materials + lighting from the light tubes) to make reproduction easier.
classroom.zip

@pbrt4bounty
Copy link
Contributor

As far I know, the area and mesh lights have a bit different implementation from pbrt 3 vs 4
A variety of light sampling improvements have been implemented. "Many-light" sampling is available via light BVHs ([Conty and Kulla 2018](http://aconty.com/pdf/many-lights-hpg2018.pdf)). Solid angle sampling is used for triangle ([Arvo1995](https://dl.acm.org/doi/10.1145/218380.218500)) and quadrilateral ([Ureña et al. 2013](https://www.arnoldrenderer.com/research/egsr2013_spherical_rectangle.pdf)) light sources. A single ray is now traced for both indirect lighting and BSDF-sampled direct-lighting. Warp product sampling is used for approximate cosine-weighted solid angle sampling ([Hart et al. 2019](https://onlinelibrary.wiley.com/doi/abs/10.1111/cgf.14060)). An implementation of Bitterli et al's environment light [portal sampling](https://benedikt-bitterli.me/pmems.html) technique is included.

@pedrovfigueiredo
Copy link
Author

pedrovfigueiredo commented Dec 29, 2024

Thanks for your comment, @pbrt4bounty. I'm aware of the advertised changes in v4's page.

For choosing which light source to cast a ray in NEE, the comparison above does not use the new [Conty and Kulla 2018] method, rather it still uses the same uniform light sampling as available in v3, v2 and v1.

I agree that there could be improvements when sampling within a triangle due to the improvement in solid angle sampling for triangles, but it still doesn't explain the huge discrepancy seen above. The very high noise in v3 seems to be coming from the lack of visibility check when doing uniform light sampling (expected). In v4, this appears to be improved upon somehow, but I'm not sure where that happens and how to turn it off so we can try to get something closer to v3's result.

Maybe @mmp will have some time in the near future to shed a light on this.

@mmp
Copy link
Owner

mmp commented Dec 30, 2024

I agree that there could be improvements when sampling within a triangle due to the improvement in solid angle sampling for triangles, but it still doesn't explain the huge discrepancy seen above.

Are you sure of that? Solid angle sampling is quite helpful for nearby lights. Try modifying pbrt to go back to area sampling for triangle lights and to not use the warp product sampling for the cosine. Those are the main things I can think of that would make a difference like this, even if BVH light sampling isn't being used.

@pedrovfigueiredo
Copy link
Author

pedrovfigueiredo commented Dec 30, 2024

First of all, I truly appreciate you taking the time on a Sunday to pitch on this, @mmp.

Are you sure of that?

Indeed, I should be way more careful when making claims like that! The hope with this issue is to find out the reasons that contribute to such a drastic change from v3 to v4 so I can more confidently make comparisons on the many-lights sampling project I'm working on.

Try modifying pbrt to go back to area sampling for triangle lights and to not use the warp product sampling for the cosine.

I modified the Sample() and PDF() functions on the Triangle class in v4 to perform uniform area sampling by reusing the chunks of the original code that handled edges cases where triangles were too small or too large:

pstd::optional<ShapeSample> Sample(const ShapeSampleContext &ctx, Point2f u) const {
        // Sample shape by area and compute incident direction _wi_
        pstd::optional<ShapeSample> ss = Sample(u);
        DCHECK(ss.has_value());
        ss->intr.time = ctx.time;
        Vector3f wi = ss->intr.p() - ctx.p();
        if (LengthSquared(wi) == 0)
            return {};
        wi = Normalize(wi);

        // Convert area sampling PDF in _ss_ to solid angle measure
        ss->pdf /= AbsDot(ss->intr.n, -wi) / DistanceSquared(ctx.p(), ss->intr.p());
        if (IsInf(ss->pdf))
            return {};

        return ss;
}
Float PDF(const ShapeSampleContext &ctx, Vector3f wi) const {
        // Intersect sample ray with shape geometry
        Ray ray = ctx.SpawnRay(wi);
        pstd::optional<ShapeIntersection> isect = Intersect(ray);
        if (!isect)
            return 0;

        // Compute PDF in solid angle measure from shape intersection point
        Float pdf = (1 / Area()) / (AbsDot(isect->intr.n, -wi) /
                                    DistanceSquared(ctx.p(), isect->intr.p()));
        if (IsInf(pdf))
            pdf = 0;

        return pdf;
}

To the best of my knowledge, the above implementation is equivalent to the implementation in PBRTv3, described here (no solid angle sampling and no warp product sampling to approximate the cosine).

After applying the above changes, we get noisier estimations around the lights, but it is still not close to what we see in v3.
v4-256spp-nosolidanglesampling

The effect is also distinguishable in this more straightforward cornell-box scene rendered with 4spp.

v3

v3-cornellbox-4spp

v4 without solid angle sampling

v4-cornellbox-4spp-nosolidanglesampling

v4

v4-cornellbox-4spp

Any other hypotheses on what could be contributing to this change?

@mmp
Copy link
Owner

mmp commented Jan 30, 2025

I've thought about this more and reviewed the code and the only things left I can think of are a) a difference in materials (e.g. pbrt-v4 has CoatedDiffuse vs pbrt-v3's plastic material, or b) pbrt-v4 has a bilinear patch primitive, which also has specialized solid angle and warp product sampling code. If the lights are being rendered as bilinear patches, that would make a difference.

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

3 participants