Thoughts on making Cycles into a spectral renderer

That’s a great question. VS Code doesn’t seem to be able to locate it either. I guess let’s wait for @kram1032’s reply

1 Like

If you use the inspect module, you can trace what module a function is from, and that will help you find the documentation.

print(function.__module__)

Or if that fails:

import inspect

inspect(function)

If there is a docstring, you can use:

help(function)

Or directly via the function itself:

function.__doc__

My son is home for the holidays. He’s a programmer for Amazon and python is one of his go-to languages. He suggests I use PyCharm. He’s going to walk me through the first baby steps of python programming! (I’m shocked to learn that indents are functional in python! Heavens, what other travesties of programming grammar are to come???)

good to know, thanks!

1 Like

Python wants you to format its code well. (Whether it succeeds is a different story)
In exchange it doesn’t require you to write pesky semicolons.

PyCharm is also what I usually use.

Thats awesome. Yep PyCharm sounds like a good choice.

Python isn’t one of my primary languages and I’m of the same opinion :joy:

I’m looking for some feedback on this.

Considering the complexity of making this change, I think I’m going to need to break out up into smaller tasks which could be incrementally included and allow for user benefit earlier on. Still not sure about a good way of splitting up the work but here are my initial thoughts.

  1. Minimal changes such that the output buffer of the render engine is XYZ*. This would almost certainly need to happen alongside some changes to colour management and I won’t be able to do all of this, I’d be looking for someone to work alongside on this bit.
  2. I feel like consolidating all the places the engine requires a colour into a single function is the hard part of the refactor and might help improve the overall structure of the code. @brecht do you have any comments here, reasons for or against it?
  3. Once the engine is outputting XYZ and extracting colour from materials is isolated to one part of the code, swapping out the RGB sampling for spectral sampling and updating that colour function should be much more manageable.
  4. If we get to this point, going around and converting other areas which make sense to be described spectrally to be spectral can happen over time as needed.

* I feel only the output buffer should be changed initially since the rec.709 primaries used currently actually behave pretty well compared to other colour spaces when simulating with three colours instead of spectrally.

Does anyone know who might be capable of doing the work of integrating colour management better into the render output, or if not this stage could be skipped for now as it’s going to potentially be quite a significant change and doesn’t necessarily block the later stages, if we are willing to accept we are still getting rec.709 out of the engine which could lead to some confusing results.

@troy_s are you or someone you know capable and willing to do some work regarding colour management mentioned above? I imagine it would be a pretty impactful change which will likely result in a bunch of bugs and complaints about things ‘not working as they used to’ but it seems like an important and inevitable change to occur eventually.

One way of starting the refactor would be to introduce color as its own type instead of going through float3 (even if it is initially defined as float3). It might also be necessary or useful to have separate types for albedo and light, and define only valid operators on them (such as you can multiply light by albedo, but not add light to albedo). This should help avoiding mistakes, making the code more readable and debuggable.

Unfortunately we can’t use C++ in OpenCL kernels, otherwise color could be a class with defined components and color space and operators could be written to do automatic conversions or throw errors upon undefined operations. In theory, one could write an assignment operator that upon assigning a Rec 709 color to a spectral type would automatically take care of the conversion, without having to write out explicit conversions every time this happens.

I think this would be a hugely beneficial change. I’m doing what I can with my current knowledge of C++ but there’s a lot I don’t know which I think is part of the reason I’m struggling so much with this relatively straightforward change.

So that refactor would change all of the current 'float3’s in the passes etc into 'Colour’s and then have functions available to convert colours between colour spaces, mix them etc?

That’s what I started to do with a SpectralColor struct, but overriding all of the operators was really tedious so I’m sure I’m doing it wrong. Really all I want is a float3 with a different name, and which can only be combined with other instances of the same type.

This also seems like a good move. This has some other interesting implications once moving to spectral rendering too, as reflectances have to be capped at 1 but emission spectra can have some extra freedom.

I think it is primarily my lack of experience in C++ limiting me here. I wonder whether there’s the capacity for any devs at Blender to chip in here. First step is that I’d love to be able to push a branch somewhere for others to review, but I can’t find any docs on where/how to do so.

That seems to answer one of my first questions when hitting the Cycles codebase - what is with those header files? :smile:

This would be lovely. Though regardless of this, the struggle I keep running into is keeping a track of all the places a colour is ‘requested’ in the kernel. In theory all I need to do is replace the float3 coming from the RGB channels with a float3 representing three wavelength intensities, but it just seems like this sort of ‘colour access’ happens all over the place and in different ways depending on where it is being used.

I’m sure there’s a sensible explanation for it but it would make the change so much easier if all those instances just referenced a single function I could adapt.

One issue I can see here is that the ‘SpectralColor’ that I implemented is just 3 intensities at 3 wavelengths, which means a conversion from rec.709 to SpectralColor needs to know which intensities to use. It would be possible to create a data type which can be ‘sampled’ at any wavelength, but I had assumed that would be much too expensive to use in this case.

BT.2020 is more ideal I would think. Fits in the luminance optimum range, and wider gamut that BT.709.

The Blender code base seems to have some poorly informed and confused individual hard coding the sRGB transfer function into a new place daily, so from a code base perspective, I’m the wrong person. Cycles has slowly been attempted to be cleaned up, but IIRC, there’s still some legacy sRGB residue hanging around, either in transfer function or primaries form. I even fixed the viewer to display alpha correctly, but some poorly informed and confused individual will always come up with some arbitrary reason not to fix things. Hence I’m not doing any code work on the speculative case that anyone cares to fix anything.

I will absolutely proof check any colour science / pipeline things if you need.

I’m happy to carve out an OpenColorIO configuration, as that’s about all that is required minus the specific shader edge cases, which should be easy to check.

If done correctly, it would be the same process as what should already be in Blender:

  • Take input, transform to the destination based on chosen transform
  • Perform math on chosen transform output
  • Transform to destination

So for an upsample it would be something like:

  • Use BT.2020 reference working space throughout in the OpenColorIO configuration
  • Take input buffer to XYZ role, which performs BT.2020 transform of primaries, which as a role provides a clean developer API grab, already implemented by @lukasstockner97
  • Upsample, which is again a problematic approach for spectral simply because it doesn’t work
  • Perform math
  • Transform back via XYZ role to the reference assumed throughout in OpenColorIO configuration
  • Transform to output context, view for display, or chosen transforms for file encodings

Come to think about it, replacing float3 with a color type would be the worst thing we could do. There must not be a single color type, but any color variable must be in a specified space - we just can’t allow color without reference any more. Colors in different spaces must be represented using different C(++) types and the compiler must not allow mixing those types without conversion. That way, color spaces are always explicit and never implied by context, and the compiler could prevent errors before they happen.

This would be a big refactor of the code base if we want to do this all the way through, but going over the entire code base once and clearly establishing what operation takes place in what space would make things so much easier in the future (and could prevent tons of future mistakes).

Seems like a great thing to do regardless of what happens regarding spectral rendering. Is this something that some Blender Institute devs would have time to do? As much as I’d like to say I’ll do it, I don’t have sufficient knowledge of the code base, language, and enough time to be able to commit to it.

This happens if every single RGB instance simply doesn’t make assumptions and instead uses the CMS.

The problem is developers skipping the CMS.

It’s as simple as that.

I believe this has been tried in some approaches, and it becomes untenable due to the infinite number of spaces. When you consider hardware such as cameras and such, it would require an update every time a new definition occurs.

Again, it’s far easier to simply have developers realize that the space for RGB is unknown, and not make assumptions and use the CMS for transforms according to the audience’s need. There’s no way to know the need, hence hard coding is flatly broken.

Even something as simple as sRGB is typically wrong, given that a majority of people aren’t using an sRGB display. While this is somewhat difficult to prove quantitatively, it’s based on a reasonable number of cases of examining cheaper displays. The primary lights are indeed frequently accurate, but the transfer function is rarely sRGB. Then couple that with Display P3s targets that are in the majority of newer displays and Apple products and you can begin to see just how ropey and brittle the whole system is.

All of this is made worse by an order of a magnitude when we consider that the reference space isn’t sRGB, and a huge chunk of input buffers won’t be.

3 Likes

Ideally a proper type would statically enforce this realization. I think that’s the idea here

It’s an unworkable solution. See GIMP as a poor example of hard coded attempts.

It simply doesn’t work, and it can’t work.

Why not just go for the easier solution and have the developers not commit broken code? It seems there’s plenty of review blockages over divide by zero or other faults, yet the fundamental pixel handling medium, and the sole goal in a majority of situations, just slips by without a peep.

That’s on the audience. The developers are only as astute as the audience.

The spectral solution here depends on it.

Typing isn’t about hardcoding anything. It’s about holding developers responsible to do the right thing instead of, as you said, committing broken code. For one, an RGB type or something like that could easily require a CMS to be provided.
Coding is a complex task and it’s easy to miss something and mess up if you aren’t telling the compiler to help you out. So what you suggest to be the easy route is, in fact, hard.

Ideally you’d want types such that only a correct implementation is allowed by the compiler. With C/C++ it’s probably near impossible to accomplish this, but approximating it as closely as possible is definitely a valuable proposition.

Now what exactly the types should be I don’t know. That’s worthy of further considerations. But generally speaking, proper typing helps rather than hinders the writing of correct code.
Just because GIMP allegedly has messed things up on that front (I don’t know that but I’ll take your word for it) doesn’t mean the approach is generally unworkable. There isn’t just one single way to build stronger types.

My 2ct: In our (proprietary) industrial colorimetric calculation framework we have running for 10 years now, the ‘typed’ approach has proven invaluable to avoid bugs, enforce correct usage, and teach the ignorant software devs. We have literally had zero bugs on colorimetry since its introduction.

1 Like

Is that an approach such as having a type for XYZColor, and a separate type for rec709Color and SpectralColor? I can see how that would fix bugs at compile time.

I think there’s an approach which solves both problems here. Things which aren’t known until runtime should (obviously) rely on configuration such as OCIO. That could be the display transform, but also the colour transforms going in to the engine, such as the colour space of colour picked colours and images. I think with the right brains such a system could be established to help the developer significantly when developing this without bugs.