Thoughts on making Cycles into a spectral renderer

honestly, if I don’t care about the caustics which aren’t gonna be accurate anyways, this actually renders fairly quickly. Like, this is without denoising and 512 samples but at least to get the gist of it, really low sample rates easily suffice. Even the viewport default 32 look decent. Except for the caustic.
These took about half an hour on my machine each - I really wish I had a GPU that likes Cycles. I thought I got one now but it turned out that this was literally the best possible GPU not yet good enough to allow GPU accellerated rendering.
But anyways, if I had been happy with higher noise levels, 2 minutes per image would have been possible. And probably even then the denoiser could have done a somewhat decent job

And yeah, agreed, I love how these came out

As soon as we have a custom spectrum node, I want to see what renders look like a few tens of meters under the water. In real life the colour shift can be quite drastic and I wonder if the difference between RGB and spectral is significant for that case.

Well I guess I have no excuse then :grinning_face_with_smiling_eyes:

1 Like

Oh I’d love physically accurate under water scenes. Also, ocean water vs. drinking water can behave quite differently, as far as I know.

1 Like

Yep, the particles and chemical makeup of salt water most likely behaves quite different from pure water. Here’s an example of the sort of strangeness that can occur. Note this is captured on a gopro so can’t be trusted for accuracy, but the phenomenon is clearly there.


5 Likes

that is so cool. I love how pink never stops being bright, and so does bright green (I strongly suspect they have some flourescent colors in those?) whereas red goes as black as actual black and only makes it clear that it’s red by strongly reflecting the orange and pink.

But yeah, from what I saw, gopros are particularly bad at capturing spectra well. Like, if you take a pic of a refraction spectrum, it’ll look quanizied into a couple very clear bars. It’s weird. Even so, yeah, nice showcase

2 Likes

(this is still on the build before the blackbody change)

This chart essentially shows the same thing as the rods before, except all in one glance.

  • On the y-axis, the blackbody temperature varies from 3000K - 8000K (each bar spans 500K)
  • On the x-axis, the absorption density varies from 0 - 99999 exponentially (every two bars the density grows by a factor of 10 - each bar represents a jump of about 3.166, and once again I subtrated 1 so it starts at 0)

What you’re looking at is a 1BU thick block of a Volume Absorption material (and nothing else) backlit by a mesh light source emitting white (1 1 1) light of strength 1, 1BU below the block.

Perhaps less exciting looking than the rods before but much more informative and also faster to render.
I noticed that, if absorptions go higher than on the order of 10^6, absorption suddenly starts brightening again. I can only assume that’s some sort of floating point error.

6 Likes

It’s amazing how long green light persists in that model. I wonder whether that’s physically accurate or if it’s introduced by the normalisation process. Very cool test.

I’m working on a ‘spectrum curve’ node at the moment. Hopefully it’ll be relatively straightforward.

2 Likes

Oh looking forward to that! I assume that you could basically reuse a curve editor for these purposes. Maybe (eventually) with some fancy additions such as a backdrop of the spectrum, so as to make it easier to visually see which region to boost.

Another neat thing would be a list input of sorts. Maybe you could reuse the scripting node for that, or see how add-ons like Animation Nodes have solved that.

Either way, exciting times!

Yes this is just a temporary stop-gap measure to be able to create spectra in some way. The end goal is to have various spectrum creation/import methods such as a file loader, maybe a preset selector (if that’s useful), and eventually, maybe a spectral colour-picker/maker. This will be a lot harder as it involves some significant UI development which I know nothing about.

1 Like

Same idea as with the blackbody absorption chart before but this time for the current RGB colors.
Each vertical bar corresponds to a 15° hue shift.
Absorption is as before, every two bars represents an absoprtion density increase of a factor 10 (and then -1 so it starts at 0).

For this chart, I set saturation to a constant 0.1 and value to a constant 0.9995
Trying this with a value higher than that leads to a lot of weirdness, at least some of which probably is a result of floating point errors at very high densities. You can still see a little bit of weirndess in that very long spike at a hue of exactly 0.5. Here’s this exact same chart with a constant value of 1 instead:

The noisy region has colors drift towards infinity or negative infinity depending on the channel.

Of course this is only for insanely high absorptions. Looks like up to around 10^5 things still work out at least somewhat reasonably, but really between 1000 and 10000 depending on hue is where things start to go wrong.

2 Likes

One last batch in this series. The six most extreme colors (as per both intuition and inspection of the Hue chart) with the vertical direction instead determining saturation. Hue increments by 1/6 across these charts, and Value, in order to avoid this unstable behavior as seen in the second Hue chart, is constant at 0.9995.
The horizontal direction is the same 0-10^6 density as in the other charts.






One thing that’s curious to me is that somehow the Cyan version is somehow the brightest color when these are all based on linear combinations of a red, green, and blue response respectively (right?), and blue (looking very purple here) is by far the darkest. Shouldn’t yellow look much brighter?

1 Like

Hi @smilebags a rough question: what is the final goal of this work you’re doing? I mean, when in the future you’ll have all the spectral nodes in place, a robust implementation and roughly the same rendertimes… are you going to submit a patch to BF to make it land in master? Should it be an experimental option, of just an alternative to the regular RGB engine? Or it could be just a checkmark to tell “work in spectral mode”? I’d like to have an insight to the future of this project (if there is one).
Thank you anyway, following this thread is entertaining :woman_juggling:

6 Likes

I’m keeping the idea of merging to master open, though I won’t make big compromises just to get it merged. The goal is to have a solid spectral renderer implementation, and it does seem like merging is a possibility.

I’m confident the performance impact shouldn’t be too dramatic, it’s just a lot of work to convert everything over, which is very hard to fit into my spare time.

As for how it would be exposed, due to how Cycles is built, having it as a runtime toggle is going to be a lot of work, and mean a lot of maintenance work in the future. If it will be merged, it would likely be the only mode. I don’t see this being a big issue, as everything will continue to work as expected, and if you were unaware you probably wouldn’t notice you were using a spectral engine at all.

I would like to keep this conversation open about whether having this merged is something BF devs are interested in. There are more considerations to merging this than just ‘does it work’, namely, it means future developers of Cycles needs to be able to make sense of a spectral renderer. It isn’t a major mindset shift but it is something to consider.

Thanks for asking. It’s an important question to discuss, especially as it seems progress is speeding up lately.

7 Likes

There might be occasional good reasons for having a checkbox. Ideally that’d be how it works, I think.
However, Spectral as default, if the performance impact isn’t too big, will likely be a good idea. Certain edge cases just are handled much more gracefully with it.

I just thought of something: Since the blackbody node gives a spectral output, I was curious how the split RGB node handles it.
Turns out you get a pinkish grey material doing that, regardless of which color channel you use (it even happens if you use all three) and regardless of what color temperature.

So clearly it falls back to some sort of strange default. I wonder what it is.

However, I have a rough proposal of how to handle this in a reasonable manner:

Basically, you already have three spectra which define a red, a green, and a blue light source in sRGB.
Conveniently, they sum to 1,
So an easy solution might be to literally just multiply arbitrary spectra once with each of those sRGB extreme spectra.

This isn’t a perfect generalization. For instance, if you put the color sRGB green (0 1 0) through this process, it will end up having some contribution in both red and blue. But it’s the simplest somewhat reasonable thing I could think of.

Apart from that, however, really what ought to happen is two clearly different color swatches, one for RGB color and one for spectral color.
In fact, in principle, it could be interesting to have even more such options though that’s not the focus of this branch: For instance, how about a CMYK color model? You’d definitely also want to distinguish that from RGB colors.
Similarly, RGB in different color spaces or using different underlying spectra (say, optimized for reflectance vs. illumination etc.) should probably also be distinguished somehow. Although we can’t just have infinitely many inputs so clearly that’d require some deeper UI changes.

But anyway, all of that is of course leading too far for the moment. At least for now, spectral data could be converted back to approximate RGB data using the above-mentioned procedure.

It would be nice if that was achievable, but due to how Cycles is built that would be very difficult to do.

Separating RGB will just get the intensities of the first 3 wavelengths in the ray, and because there’s no correlation between the wavelengths and the channels they are in, you end up with a noisy ‘equal energy’ colour. That’s what that pinkish tone is.

This limitation should only exist with spectral colours though, regular colour values should still
be triplets until used in a BSDF so you should be able to keep doing regular RGB math with them. If that’s not what’s happening then I’ll just need to fix it.

There might be some process that can be done that can give you RGB values from a spectrum but it seems counter-productive. I’m not going to focus on that until a compelling use case comes up.

1 Like

Other colors absolutely work. And that does make sense for a simple fallback for spectral conversion but none for an artist, I think. I understand it’s not a focus, but probably ideally it should just do nothing.
Oh, or I suppose another thing that could be done is to simply add up the spectral contributions and derive the actual sRGB color from that. In that case you should be getting the spectra split correctly, such that any given color ends up being the correct thing in sRGB as well.

Honestly, the reason I wanted it was essentially just to get Blender’s own take on how it interprets blackbody materials as RGB, and to then directly compare those spectra, as opposed to the roundabout way I did it before, by using another thing to tell me what the values ought to be.
But it’s not that important.

Really, eventually (not now) it would be cool to have a variety of nodes such as spectral normalization (make the largest wavelength be 1), inversion (literally just take 1 - spectrum), reversion (exchange highest and lowest wavelengths), clamping, shift (make it look slightly more reddish or bluish - could be interesting for emulating, say, photonic doppler effect), or what amounts to an equalizer as it works in music (for base-boosting and such, equivalent to red-boosting here).
Basically, simple spectral manipulations equivalents to any given common RGB operation on one hand and a number of music-inspired maipulations on the other.

But of course that shouldn’t be the priority right now

Spectral utility nodes are on the list of things to add, the only thing that probably won’t happen out of those is the normalisation one, since while we conceptually think of the spectrum as continuous, at render time it is only looking at X wavelengths at a time. Knowing the maximum takes either solving it from first principles (by knowing all the equations that went into it) or solving it implicitly by stepping through it with a small interval to find the max value. Neither are practical to do, and this is the same reason a ‘normalise texture’ node doesn’t exist.

All the other things you discussed should be pretty trivial with some basic tools like spectrum add/multiply/divide and a spectrum curve node. These things are definitely very valuable to have and will be some of the first nodes I add once I figure out how to do it.

1 Like

On that note, I’m attempting to duplicate the ‘RGB Curves’ node and I’ve got it compiling but the node is called ‘Unknown’ in the UI and I receive this traceback when attempting to add it:

Traceback (most recent call last):
  File "C:\Users\chris\blender-dev-env\build_windows_x64_vc16_Release\bin\Release\2.90\scripts\startup\bl_operators\node.py", line 135, in invoke
    result = self.execute(context) 
  File "C:\Users\chris\blender-dev-env\build_windows_x64_vc16_Release\bin\Release\2.90\scripts\startup\bl_operators\node.py", line 122, in execute
    self.create_node(context) 
  File "C:\Users\chris\blender-dev-env\build_windows_x64_vc16_Release\bin\Release\2.90\scripts\startup\bl_operators\node.py", line 92, in create_node
    node = tree.nodes.new(type=node_type) 
RuntimeError: Error: Node type ShaderNodeSpectrumCurve undefined


location: <unknown location>:-1

My development branch for this is available here so if anyone has any experience with this, I’d love some advice on what might fix it. This the diff of what I’ve done to get to this point.

I don’t think spectral values should be passed between nodes, see my comments earlier in this topic.

This is incompatible with Eevee, OSL, MaterialX, etc, and problematic for importance sampling. Instead there can be emission and BSDF nodes that support generating spectral values directly.