-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Importance sampling for infinite area light is off by half a texel #62
Comments
Wow! Really impressive work working through those details. I'd like to digest this carefully, do some tests (and figure out what edits On Mon, Mar 14, 2016 at 6:44 AM, Johannes Günther notifications@github.com
|
As nicely described by Johannes Günther in issue #62, each the bucket (u,v) in the Distribution2D used by the InfiniteAreaLight for importance sampling actually represents the radiance function's value over the range (u-.5,v-.5) to (u+.5,v+.5). Therefore, after SampleContinuous() returns a uv sample position, we need to offset by half a texel in each sample dimension before performing the environment map lookup. This issue doesn't make a big difference in smooth environment maps, but it's a big deal if it's spiky.
Very nice find; 0.5 offset fix has been applied. I'm not convinced about the change to the sinTheta computation, however. Consider for example a 1x1 environment map with a constant radiance value (as is created by InfiniteAreaLight if no filename is given but a spectrum is provided); in that case, the result should be a uniform radiance function in all directions, whereas with the proposed change, PDF would be zero everywhere. Similarly, I'd argue that if the environment map only had non-black pixels in the top scanline, it should still be casting illumination. Maybe the right thing is to be computing the average value of |sin(theta)| over the continuous u range? |
Very valid counterexamples that
then the importance can be approximated by
which is what is currently implemented. Thus, Still, |
One more thing: the shift by half a texel (in the opposite direction) needs also be done in Additionally, |
Negative angles returned from atan2() weren’t being remapped to [0,2pi]. Issue #62.
Issue #62. (See discussion there for details / rationale.)
(Fixed that SphericalPhi issue in Pdf_Le--thanks!) I've been thinking about this more and think that a different fix makes more sense. (I'd love to hear your feedback on this thinking, though!) The issue that got me started on this was running into a bug where now, if a single-texel environment map is used, a negative PDF will often be returned. It turns out that this is because of the half-texel shift, which in turn causes a negative sin(theta) value to be computed. (Upon further digging, it seems that this sometimes happens for all environment maps.) In turn, this got me thinking... I'd suggest that all of the InfiniteAreaLight code should only operate on continuous coordinates and shouldn't be thinking about continuous vs discrete coordinates at all. (Or, as little as possible). For the most part, we'd just like to be thinking in terms of continuous functions over the domain, sampling those functions, etc., without having to worry about how the functions are represented. Under that assumption, almost all of the methods of InfiniteAreaLight can go back to how they were before. However, the one place where this issue does have to be addressed is in the construction of the piecewise-constant function used for importance sampling. Here is how I think that should work:
If I do all that and do the same visualization, the results seem as good or better. (I also doubled the resolution of the tabulated PDF, which helps as well, so it's not an equal comparison.) Visualizations: What do you think? |
(The first visualization was with the initial fix, the second is with the current top of tree.) |
As nicely described by Johannes Günther in issue mmp#62, each the bucket (u,v) in the Distribution2D used by the InfiniteAreaLight for importance sampling actually represents the radiance function's value over the range (u-.5,v-.5) to (u+.5,v+.5). Therefore, after SampleContinuous() returns a uv sample position, we need to offset by half a texel in each sample dimension before performing the environment map lookup. This issue doesn't make a big difference in smooth environment maps, but it's a big deal if it's spiky.
Negative angles returned from atan2() weren’t being remapped to [0,2pi]. Issue mmp#62.
Issue mmp#62. (See discussion there for details / rationale.)
use namespace GFLAGS_NAMESPACE instead namespace gflags only signalhandler_unittest.cc uses "using namespace gflags". others use "using namespace GFLAGS_NAMESPACE", so signalhandler_unittest.cc does the same way. fixes mmp#62
As explained in Section 14.6.5 and Figure 14.13, p.726 in PBRT v2 the importance of the environment texture should be computed by interpolating adjacent texels. Therefore
InfiniteAreaLight
samples the texture at the discrete coordinatesuv
. However, this means that the pdf inDistribution2D
foruv
represents the average importance for the area(uv-0.5, uv+0.5)
. Now,SampleContinuous
selects a bin according to the pdf and samples uniformly within that bin, i.e. the returned coordinates represent[uv, uv+1.0)
. To account for this difference in coordinates theSample_Li
andSample_Le
functions of the infinite area light should subtract "half a texel" fromuv
, e.g.:So, Figure 14.13 is actually misleading, because for textures their values are defined at the center of each texel (at continuous coordinates uv+0.5, see Section 10.4.3, p.538): the piecewise linear function should therefore be shifted by 0.5.
The error is subtle and only gets noticed for high-contrast environment maps. For example, using a black texture with only one white texel, half of the samples are wasted (they have zero contribution). A nice way to debug these issues is to visualize the sample contributions of the infinite area light, i.e. render
L = infiniteLight->Sample_Li(...) / lightPdf
using the (normalized) pixel coordinates as random numbers. The more constant the resulting image, the better the importance sampling.Some example images (before and after the correction) are attached:
Single white pixel, original:![single_pixel_before](https://cloud.githubusercontent.com/assets/9035336/13745787/4309cd10-e9f0-11e5-9db2-465c36b3574b.png)
![single_pixel_after](https://cloud.githubusercontent.com/assets/9035336/13745788/430a000a-e9f0-11e5-8e20-19b10bfa61a8.png)
![grace-new_before](https://cloud.githubusercontent.com/assets/9035336/13745835/7b5995ec-e9f0-11e5-9240-82fcf73834c2.png)
![grace-new_after](https://cloud.githubusercontent.com/assets/9035336/13745836/7b59896c-e9f0-11e5-8786-ada4cbf959ec.png)
Single white pixel, with fix:
Grace Cathedral HDRI, original:
Grace Cathedral HDRI, with fix:
Some more remarks on minor issues:
The environment texture is resampled by
MIPMap
to a power-of-two resolution. To avoid aliasing artefacts the importance map should be created with that resolution as well (that's why the above patch usesLmap->Width()
andLmap->Height()
):Because we want to compute the average importance for the area
(uv-0.5, uv+0.5)
the average ofsinTheta
is better approximated with "sin(uv)
":This leads to zero probability for the first row, which is actually a good thing: uv cannot become negative after subtracting half a texel. And sampling the north pole leads to artefacts anyway (because of texture wrapping light leaks from the south pole).
The computation of the filter width could be skipped. The default width of zero in
MIPMap::Lookup
already ensures that the most detailed mipmap level is used, and the triangle filter used for bilinear interpolation computes the wanted average of the four texels.The text was updated successfully, but these errors were encountered: