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

Periodic Radiance Values for Spectral Render #424

Open
Youre opened this issue May 20, 2024 · 7 comments
Open

Periodic Radiance Values for Spectral Render #424

Youre opened this issue May 20, 2024 · 7 comments

Comments

@Youre
Copy link

Youre commented May 20, 2024

Hi, I am wondering if anyone could provide some insight into what might cause periodic radiances to appear in spectral renders.

I rendered several data cubes using the spectral film type and all pixels show a periodic radiance pattern. The pattern is more clear in some pixels compared to others, but the pattern is always present. The graphic below is a plot of the radiance values from a selected pixel. In this case, the sample was taken from a zero-day render with 63 spectral channels and the Sobol sampler -- but the pattern appears regardless of those parameters.

PeriodicRadiance

Overall magnitude is low because of the dark scene and the peaks are very clear for illustrative purposes. Again, I do not think this is really connected to the core problem since the pattern is present across the whole scene. For reference, RGB=[13, 13, 16] for this pixel, according to the respective 'R,' 'G,' and 'B' channels in the .exr.

My guess is that is has to do with RGB getting mapped into spectral space, which has ambiguity. For the very few materials that have spectral definition, their contribution would be much less than the RGB. So we would see artifacts of that mapping more clearly than with a purely spectrally defined scene. It is always four repetitions, regardless of the number of channels.

Also considered whether the wavelengths list and how I am extracting the data is not organized the same way.

Maybe I am missing something fundamental? Or maybe this is expected behavior when the scene isn't purely defined with spectral materials?

Any insight would be appreciated. Thanks!

Additional information:
Spectral samples were extracted using Python, OpenEXR, and Imath.

# Python code to get wavelength channels
file = OpenEXR.InputFile(exr_path)    
channels = list(file.header()[ 'channels'].keys())
lambda_channels = channels[3:] # BGR are the first three channels, so we skip

# Extracting the values
float_Type = Imath.PixelType(Imath.PixelType.FLOAT)
for lambda_idx in range(len(lambda_channels)):
   channel_name = lambda_channels[lambda_idx]                        
   channel_str = file.channel(channel_name, float_Type)
   spectral_image[:,:,lambda_idx] = np.frombuffer(channel_str, dtype=np.float32). reshape(size[0],-1)
@mmp
Copy link
Owner

mmp commented Oct 26, 2024

That is quite odd. Which integrator are you using? If it's one of the ones that splats samples to the image (SPPM, MLT, BDPT), then a bug that was just fixed may be causing that: #450. Can you try with the latest version?

@Youre
Copy link
Author

Youre commented Jan 28, 2025

Sorry, I lost track of this issue and moved over to a different project.

This build was from Jan '24 and using the VolPath integrator. Now, I am working with VIS-LWIR spectral scenes (i.e., everything has spectral definitions) and am seeing the same pixel pattern from VolPath and BDPT. Every 100 nm or so, there is a triplet of peaks - a medium one followed by a slightly smaller and then a larger one. Still 100 nm after changing nbuckets to a larger number.

I will pull the latest code and see if the issue persists.

Edit: Issue persists after pulling latest code (which includes #450 fix)

@Youre
Copy link
Author

Youre commented Jan 29, 2025

I may have discovered a fix. #450 was a good start and solved the access violation errors mentioned in the issue, but it did not get to the root of the unexpected behavior.

First, I noticed the pixel-level spectra had zero radiance at some wavelengths in my test scene. EXRs are written with a half-bucket offset in film.cpp.

std::string lambda = StringPrintf("%.6fnm", Lerp((i + 0.5f) / nBuckets, lambdaMin, lambdaMax));

So, I attempted to adjust the bucketing logic in film.h to ensure the EXR buckets are centered between wavelengths:

PBRT_CPU_GPU int LambdaToBucket(Float lambda) const { DCHECK_RARE(1e6f, lambda < lambdaMin || lambda > lambdaMax); // Use bucket centers for consistent mapping Float bucketWidth = (lambdaMax - lambdaMin) / nBuckets; int bucket = (lambda - (lambdaMin + bucketWidth / 2)) / bucketWidth; return Clamp(bucket, 0, nBuckets - 1); }

With this change, I can get beautifully smooth pixel-level spectra. The spectra now look as expected. I have yet to verify quantitatively.

Still, nBuckets should not exceed NSpectrumSamples. So, for anyone getting deep into the spectral film, I recommend cranking up the NSpectrumSamples or adjust it dynamically.

@Youre Youre closed this as completed Jan 29, 2025
@mmp mmp reopened this Jan 31, 2025
@mmp
Copy link
Owner

mmp commented Jan 31, 2025

Reopening this. There is something unexpected going on here that I'd like to chase down. Can confirm spiky spectra with e.g. 4 spectral samples and smooth with spectral samples==n buckets.

I don't think that change to LambdaToBucket is actually correct. For a randomly-chosen zero day pixel, 64 spectral samples, 64 buckets, I get this with top-of-tree pbrt:

Image

and this with that change:

Image

I think the dip at the end is because the last bucket is getting half as many samples as the other ones, as would happen from shifting everything down half a bucket.

In principle, I think the number of spectral samples shouldn't matter (given enough pixel samples) since these spectral bucket measurements are yet another thing pbrt is Monte Carlo integrating. However, even with ridiculous numbers of pixel samples, I'm not seeing smooth results with 4 pixel samples, so there is either a bug in the code or a misunderstanding on my side. Digging...

@mmp
Copy link
Owner

mmp commented Feb 1, 2025

This has been an interesting investigation (still not complete).

Let's take this as a test scene: a unit sphere, a red diffuse material, point light at the origin, camera at the origin:

Camera "perspective"

Film "spectral" "integer xresolution" [ 1 ] "integer yresolution" [ 1 ] "integer nbuckets" 63

Integrator "volpath" "integer maxdepth" 1

PixelFilter "box"

Sampler "halton"

WorldBegin

LightSource "point" "blackbody I" [ 6500 ]

Material "diffuse" "rgb reflectance" [.7 .3 .1 ]
Shape "sphere" "float radius" 1

With 4 spectral samples and a mere 32spp (and 63 spectral buckets) we get a pretty nice curve, suggesting that Monte Carlo is fine and dandy for generating these bucketed spectral images:

Image

But that's just with direct lighting, so there's no Monte Carlo noise. Let's try some global illumination. With maxdepth of 3, we get:

Image

Let's go up to 10 bounces maximum:

Image

Now 10 bounces with 65536 samples per pixel(!):

Image

So, in short, I think we're "just" seeing Monte Carlo noise when spectral samples != buckets in spectral film. I think the problem is exacerbated by how we sample wavelengths: a first wavelength is sampled uniformly, then the remainder are chosen to be at fixed deltas from that so that they equally sample the wavelength range. This means that the buckets are effectively being partitioned into groups where each one ends up with its own set of Monte Carlo estimates of what's seen in the pixel. I believe that this is why you're seeing 4 groups of spikes with 4 wavelength samples.

I'm not sure that's the whole story. Here's 10 bounces with 1024 samples per pixel:

Image

It's not clear to me why there's still that much variation on the upper end. With 1024spp and an 100x100 RGB image, we get a pretty clean result for this scene:

Image

One theory might be that there's some correlation between dimensions in the sampler, such that when some groups of wavelengths are sampled, they don't get random samples that cover the full domain. But I still see similar results with the "independent" sampler, which should be immune to that...

@mmp
Copy link
Owner

mmp commented Feb 1, 2025

Here we go: the answer is that the error is correlated but there's still error:

63 spectral film buckets, 63 spectral samples, "independent" sampler:

Image

Same deal, using a different RNG seed:

Image

Similar curves, but that the y axis legend is different, which is surprising. And if we look at the numbers, with the first, the last bucket has a value of 1.51074 and with the second it is 1.42285. So there's still plenty of sampling error in the final result, it's just correlated across all of the buckets when the number of spectral samples equals the number of buckets, so the curves are both smooth.

@Youre
Copy link
Author

Youre commented Feb 1, 2025

I was able to reproduce variations of your plots. Thanks for looking at it. Your analysis helps explain why we see what we see. Very interesting. Looks like I need to test the balance of spp and depth with run time.

Could the variation at the upper end be due to the RGB material used on the sphere? The irradiance distribution on the film will depend on the scene geometry and scales with intensity. Your plot shows a sharp increase in variation on the upper end, but I wonder if a different seed shows the same kind of transition -- if it does, maybe it's something with the RGB to spectrum conversion.

I tried running with lambdamin = 360 and lambdamax = 460 with the original RGB= [0.7 0.3 0.1] color in your test scene, and nBuckets=64, spp=128, 6500K blackbody point light, maxdepth=3, and NSpectrumSamples=4. In this window, we see smooth with spikes. Not much noise at the darker end of the spectrum.

Image

And jagged in the 730 to 830 window where reflections are more intense.

Image

For the flipped color case, [0.1 0.3 0.7]: Starting with the 360 to 460 window.

Image

And here for the 730 to 830 window.

Image

Cranking up the spp gives us a smoother result all around. That's good.

The amount of sampling error between seeds is interesting. I would like to experiment with seeing the distribution across 30 or so seeds. I have not played around with changing seeds much until now. Consistency between runs with high spp/depth would be important if someone wanted to model real sensors in dynamic scenes. The error can be managed using scale-invariant analyses. For my application, I will need to take a crack at it eventually.

Thanks again!

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