More OCIO Colorspaces

Hello,

Just wanted to share my recent work on my Open Color IO config (modified from vanilla Blender). It combines the amazing work done on filmic-blender with some added features, and a fixed bug. Here you go:

https://github.com/so-rose/blender-color (go to modded_blender branch)

Based on: https://github.com/sobotka/filmic-blender

What does it do? It lets you input/output/display images in any colorspace, including ACES, DCI-P3, Rec2020, and the normal sRGB, Rec709, XYZ.

  • It also integrates filmic-blender, which is a HDR log-encoded space (my analysis suggests, similar to SGamut3.cine) that simulates crosstalk (like real film) and has several very good looking filmic contrast profiles, and finally a False Color Look (works well with the filmic-blender colorspace) that helps with lighting.
  • Example pipeline: Work in Filmic High Contrast. Render to EXR (Blender hard-codes EXR to save as scene-reffered Linear). If DI is the purpose, transform it to ACES. Plugging it into the Blender compositor, load it up as ACES, and set the Color Management tab to Filmic High Contrast. Do your color grading, enjoy!

Why? The filmic OCIO config lacked more colorspaces. Simple as that :slight_smile: .

EDIT: Donā€™t render to ACES - Itā€™s not actually ACES ;). Loading ACES is fine.

:smiley:
True thatā€¦ now this finally feels as a work done properly. Respectā€¦
& thanks, darkfire :wink:

Nice work on exploring some colour transforms.

Currently, it would seem that your config uses REC.709 processing lights for rendering, so the advice to render to ACES is misplaced; the values stored from the renderer will be REC.709.

Also, your transforms are missing transfer functions on many, so I would be careful suggesting usage. In particular, to use the ā€œACESā€ term, it would require an entire transform chain, which isnā€™t included here.

i also would love to see the classic looks back too. I liked them as well.

This ā€œFilmic Blenderā€ stuff really has been an eye-opener to me recently. Although I have adopted the ā€œlinear workflowā€ like 10 years ago and never looked back, Iā€™ve only ever been using tone mapping / highlight compression as a post-process.
So my whole lighting was done using an sRGB display LUT, always keeping in mind that I could ā€œget ridā€ of too bright highlights in compositing. How stupid that was.
The results Iā€™m getting with the Filmic Blender OCIO config are on a whole new level.

I already have it working with Fusion so that the linear EXRs coming from Blender look 100% the same as when viewed in Blenderā€™s image editor with the ā€œFilmic Log Encoding Baseā€ and the ā€œLookā€ I selected. Great! :slight_smile:

Now Iā€™m trying to also adopt this to Houdini / Redshift to get it working hand in hand with Blender (a great combo BTW!).
Houdini can read ā€œ.lutā€ files so I tried baking me a LUT with ociobakelut.
I was kind of successful to a point, but I got clipping with values > 1. I already found out that I need a so called 1D shaper LUT to squeeze the linear values way above 1 inside the 0-1 range to later apply the 3D LUT to desaturate brighter colors with the ā€œdesat65cube.spi3dā€ coming with Filmic Blender.

The header of the ā€œ.lutā€ format looks like this:

Version  3
Format  any
Type  3D+1D
From  0.000031 16.000000
To  0.000000 1.000000
Black  0.000000
White  1.000000
Length  64 1024
LUT:
Pre {
    0.000000
...
}
3D {
    0.040157 0.038904 0.028316
...
}

So you can see that at first thereā€™s a 1D LUT logarithmically transforming linear values in a range from 0.000031 to 16.0 in the range from 0 to 1. After that the 3D LUT gets applied. Perfect.
But I just canā€™t get ociobakelut to output me a proper LUT with the Filmic desaturation and a 1D log shaper LUT.

Could some color wizard please help me?

Oh, and thanks Troy for opening a whole new universe to us! :wink:

Thanks for taking a look!

Missing transfer functions, huh - guess I got too caught up in finding the transform matrices; Iā€™ll get on that asap :slight_smile:

Is is even possible to render to something other than Rec709 in Blender? Either through OCIO or some kink in the Python API? Or even a patchā€¦?

With regards to ACES. Hereā€™s how I understand an ideal color pipeline:

Working Space: Anything you can dream and provide LUTs for.

Render:
Render Engine ā€“> Working (ā€œCameraā€) Space
Working Space ā€“> IDT ā€“> ACES (Linear, float) ā€“> EXR (for DI)

Comp:
ACES ā€“> Mat/LUT ā€“> Working Space
Working Space ā€“> ACES ā€“> RRT ā€“> ODT ā€“> Every format under the sun.

If this is correct, I suppose a simple gamut transform isnā€™t enough for an image to call itself ACES? If so, where does one get RRTs/ODTs? And for Cycles, what counts as an IDT?

Kind Regards,
Sofus

So you can see that at first thereā€™s a 1D LUT logarithmically transforming linear values in a range from 0.000031 to 16.0 in the range from 0 to 1. After that the 3D LUT gets applied. Perfect.
But I just canā€™t get ociobakelut to output me a proper LUT with the Filmic desaturation and a 1D log shaper LUT.

Could some color wizard please help me?

Oh, and thanks Troy for opening a whole new universe to us! :wink:

Iā€™m no wizard, but hereā€™s my thoughts on this:

The Filmic Log transforms look like this in his OCIO config:

  • !<AllocationTransform> {allocation: lg2, vars: [-12.473931188, 12.526068812]}
  • !<FileTransform> {src: desat65cube.spi3d, interpolation: best}
  • !<AllocationTransform> {allocation: uniform, vars: [0, 0.66]}

First, he squeezes a lot of stops into 0 to 1, as I understand (or else, the other way around :slight_smile: ). The cube file, as I understand it, simulates the crosstalk found in film (where bright light bleeds into other colored photocells; it desaturates bright signals subtly). Then he seems to brighten it with a uniform transform.

My point being, itā€™s not just the LUT you need. If you want to try, go for it - pylut can convert formats (a python command line interface for doing things with 3D LUTs in various formats). Unfortunately my own tool, openlut (https://git.sofusrose.com/so-rose/openlut), canā€™t yet do 3D LUTs (only 1D) :slight_smile: .

Thank you very much, darkfire! :slight_smile:

Thatā€™s exactly how I was interpreting the config, too. Now that I think about it a bit more, I notice that while I can reach the point of applying a 1D LUT to squeeze a large range of values in the 0-1 range AND I can then successfully apply the desaturation 3D LUT BUT I will never be able to brighten the result with a simple math operation afterwards ( !<AllocationTransform> {allocation: uniform, vars: [0, 0.66]} ) and then apply the look LUT all in one go. :frowning:

But thanks again for your infos. Iā€™m not script- or python-savvy, so I canā€™t even give pylut or your tool openlut a go. I guess Iā€™ll have to wait for Houdini to get proper OCIO support. :wink:

Keep up the good work, guys!

I just found http://forums.odforce.net/topic/25019-hable-and-aces-tonemapping/?do=findComment&comment=145813, especially useful for Houdini but might also contain some info for us Blender guysā€¦ :slight_smile:

Interesting stuff. Funny enough, Iā€™m designing openlut to be able to do just that - combine LUTs with simple math ops for color correctionā€¦ Iā€™m gearing it towards being a library, but later I can certainly make more accessible tools for it.

Iā€™ll definately look at the link. Thanks!

I was hoping that getting the configuration out there as a starter would spur these sorts of explorations. What I wasnā€™t expecting was folks to dive in so quickly. Given that most of the folks in this thread are of a level to understand the transforms, Iā€™ll try to explain what is going on in more detail. Also, if you havenā€™t already done so, there is a little bit more information on LUTs and their formation over at this answer at Stack Exchange.

The primary stanza in question is already provided above:


- !&lt;AllocationTransform&gt; {allocation: lg2, vars: [-12.473931188, 12.526068812]}
- !&lt;FileTransform&gt; {src: desat65cube.spi3d, interpolation: best}
- !&lt;AllocationTransform&gt; {allocation: uniform, vars: [0, 0.66]}

As most have already deduced or researched, the desaturation transform happens in a 3D LUT. This is required because only a 3D LUT is capable of taking the three unique RGB encoded values and outputting another unique set of RGB values. More specifically this permits a single channel to change all three channels.

However, given that we are dealing with zero to infinity scene referred ranges generated from within Cycles, we have a problem. 3D LUTs operate on a bounded range. This means that we need a method to compress the specific ranges we desire down to a smaller range such that the 3D LUT can operate on them effectively. If we simply scale / normalize the range, then our 3D LUT would work, but work poorly given that we are generating nonlinear output; the allocation of stepped values in the 3D LUT would be distributed linearly. This will yield an uneven distribution of our 3D LUT across the range of data.

To solve this, we need to provide a shaped form of compressed range. There are many approaches, but a power curve is the most easy and standardized method to shape our data. OpenColorIO has just such a transform designed for this purpose.

As you may have guessed, the two values in the log2 allocation transform are provided to determine the range of data. In this case, the shaper grabs ten stops below our chosen middle grey value, and fifteen stops above. Our middle grey value is arbitrarily set to 0.18 as a convention, but could be any value we wish given that no such concept of white, black, or middle grey exists in the scene referred domain.

So why the two values -12.473931188 and 12.526068812? We can speculate that the first value is the low, and the second the high, but why those particular numbers? The astute observer will notice that the two values span 25 stops, which matches the information above.

The reason is that the AllocationTransform uses base log2 values. Our low and high values are calculated as below:


Low: (2^-10) * 0.18 == 0.000175781
High: {2^15) * 0.18 == 5898.24

EDIT: This is in fact wrong. OCIO uses unscaled log2, so the proper value would be simply log2 of the low and high.

To properly nail the resultant values, we do a little bit of math to arrive at a pure base two:


Low: log(2)*LOW = log(0.000175781), Solution: LOW = āˆ’12.47393324
High: log(2)*HIGH = log(5898.24), Solution: HIGH = 12.526068812

EDIT: This is in fact wrong. OCIO uses unscaled log2, so the proper value would be simply log2 of the low and high.

That range is then fed into a 65 cube 3D LUT for the desaturaiton. Because the final encoded range uses the same base floor as the desaturation shaper range, we can simply scale the result by 0.66 to deliver the final encoded range of ten stops below middle grey to six and a half stops above. Again, middle grey remains pegged at 0.18.

Why is the final encoded value range a smaller subset of the larger range? Because that is in essence similar as to how a photographic capture device works. If we provide the entire range of the shaper, while it would work, it wouldnā€™t deliver results that are similar to the standard photographic range of film, while making it more challenging to arrive at a decent display referred white point. Conversely, if we only apply the desaturation on the final encoded range, weā€™d experience desaturation too abruptly.

The only solution to arrive at something that we would read as being vaguely photographic is to do the two step approach.

As you can see from the above discussion, LUTs arenā€™t something to be arbitrarily meddled with and have to be carefully crafted knowing the source and destination value range, as well as the intention of each transform. Hence why the existing ā€œFilm Emulationā€ looks are essentially random Instagram filters as they donā€™t properly define the filmā€™s shaper, nor do they define the log-like encoding scheme particular to each film stock.

The moral of the story is that LUTs and transforms, whether creative as in looks or technical as in proper colour space transformations, must never be arbitrarily swapped or moved around without completely understanding the contexts. Anyone that has tried this and attempted to match the output, as some folks in this thread have discovered, arrives at completely arbitrary and useless results.

Hope this helps. Keep hammering!

1 Like

Very, very informative - thank you very much @troy_s for taking the time! I continue to hammer :stuck_out_tongue: !

I came up with some theory on how to implement a proper ACES workflow, based on your insights. And how to integrate the unique features of Filmic Blender into that ACES workflow. Scroll all the way down! I think Iā€™m gonna give it a try :slight_smile: .

Default Color System First thingā€™s first. If I understand correctly, this is Blenderā€™s default (Rec709 gamut = sRGB gamut) :

Cycles Render (scene-referred [0, inf], linear encoding, Rec709 gamut)
*Colors coming out of cycles will have Rec709 primaries.
*More saturated colors (like (2, 0, 0) red) are lost on clip.

|
| * Transfer Function: sRGB on [-0.125, 4.875]
/

sRGB (display-referred [0, 1], gamma encoding, sRGB gamut)
*When put into compressed/viewing focused .jpg, .png, etc., all more saturated colors than in the limited sRGB gamut are lost.

*** Sometimes saved with an sRGB transform extra, which is undone on load.

Filmic Log Color System Default system is terrible. DR is lost, itā€™s not photographic at all. Hereā€™s how I understand Filmic Log:

Cycles Render (scene-referred [0, inf], linear encoding, Rec709 gamut)
*Colors coming out of cycles will have Rec709 primaries.
*More saturated colors (like (2, 0, 0) red) are lost on clip.

|
| * Log2 transform from [-10EV middle grey, 15EV middle grey) to [0, 1]
| * 3D LUT: Crosstalk simulation on HDR log2 encoded footage.
| * Multiply: Scale [0, 0.66] to [0, 1], such that the output covers [-10EV middle grey, ~6.5EV middle grey]
/

Filmic Log (display-referred [0, 1], log2 encoding, Rec709 gamut)

  • Itā€™s not exactly Rec709 gamut, LUTCalcā€™s analysis says. But itā€™s very, very close.
  • Filmic log gives us HDR, not wide gamut.

| * Look LUT: log2 [0, 1] to display [0, 1]
/

Filmic Output (display-referred [0, 1], gamma encoded, Rec709 gamut)

  • The gamma encoding varies based on the setting - base, high, very high contrast, etc. .
  • It is sRGB; calibrate your monitor to sRGB!

Proposed ACES Color System If all this is accurate, then wouldnā€™t an ACES transform stack look something like this:

Cycles Render (scene-referred [0, inf], linear encoding, Rec709 gamut)
*Colors coming out of cycles will have Rec709 primaries.
*More saturated colors (like (2, 0, 0) red) are lost on clip.

|
| * Matrix Transform: Since invalid (beyond Rec709, like (2, 0, 0)) colors in Rec709 are valid (<1) in ACES, we should do a simple (unbounded) matrix trasform.
| * This would form the IDT for Cycles. Quite simple, as the ā€œCameraā€'s custom gamma/gamut is well-defined in our case - itā€™s just Linear Rec709.
/

ACES Space (scene-referred [0, inf], linear encoding, ACES gamut)

  • Grades to ACES images from different cameras should look the same. Thatā€™s the whole point.
  • Supposedly, different render engines would produce the same result once converted to ACES - no matter their native gamut.

***Save to EXR! Impossible in Blender - it refuses to dump anything but Cycles base data.
*SO: Work in ACES, preview in ACES, but save Cycles data. Then use an external tool to bring the dumped EXRs in line with what you worked with.

For Display & Final Output we have the RRT and the ODT. The RRT, according to http://www.openexr.com/UsingOpenEXRandCTL.pdf, makes ā€œACES images
that are approximately scene-linear look pleasing and ā€œfilm-likeā€ when displayed on a screenā€. In other words, itā€™s a creative choice. A quote:

    "It is expected that most movie production projects will use the RRT for color rendering. However, for
    some projects, the RRT will be replaced by an alternative RT. For example, 3D computer animation is
    sometimes done such that displaying the resulting ACES images directly on the reference display yields the
    intended result. In this case color rendering is not needed and the RT is an identity transform."

**The grade happens here

ACES Space (scene-referred [0, inf], linear encoding, ACES gamut)

|
| * Clip [0, 1]. Since
| * Optional RRT 3D LUT, defined in CTL: https://github.com/ampas/aces-dev/blob/master/transforms/ctl/rrt/RRT.ctl
| * Alternatively, use nothing. Use an Identity RRT.
/

OCES Space (display-referred, [0, 1], RRT encoding, ACES gamut)

  • We now have a pleasing OCES image. But itā€™s still in ACES gamut. No monitor can display this stuff.

|
| * Mandatory ODT: Does a gamut transform, then a gamma encoding, depending on target space.
| * Matrix Transform: From ACES RRT (with wide color reproducibility) to Target Gamut.
| * Gamma Transform: From Linear Target Gamut to Target Colorspace.
/

Target Space (display-referred [0, 1], target encoding, target gamut)

Implementation of ACES-based system. Might be worth making an ACEScg version of this, as well. See http://www.slideshare.net/hpduiker/acescg-a-common-color-encoding-for-visual-effects-applications .

How would this ACES system I described be added intuitively?

  • Set scene_linear to ACES in config.ocio. reference space should be XYZ (linear XYZ, obviously), and all spaces should be defined against that (D65).
    [LIST]

  • Why XYZ? All spaces have XYZ-relative matrices because itā€™s huge. Weā€™ll never see or interact with it, of course.

  • Why ACES for scene_linear? 1) Workflow becomes intuitive & future proof, 2) So that saving ACES to EXRā€™s is possible. (hopefully. God I hope so. Itā€™s worth a patch if not.)

  • As in Filmic Blender, we keep 25 stops of DR, using the range [-12.473931188, 12.526068812] for scene_linear.

  • Viewable output gamuts like sRGB, Rec709, Rec2020, Adobe RGB, etc. will clip to [0, 1]. Unviewable DI gamuts like ACES, XYZ, S-Gamut, etc. will retain the full DR of Scene_linear.

  • ā€œDisplaysā€: contain Target Spaces. sRGB, Rec2020, Adobe RGB, S-Gamut (with log encoding), etc. and of course ACES.

  • ā€œViewsā€, which represents the context of a Target Space.:

  • Raw (no transform; just ACES space)

  • Default (see ACES Display).

    • ā€œACES Displayā€ (ACES working space ā€“> direct ODT to the Target Space)
  • ACES RRT (ACES working space ā€“> RRT and ODT baked together to the Target Space)

  • Filmic (ACES ā€“> Rec709 ā€“> log2 encoding ā€“> desat65cube.spi3d ā€“> multiply ā€“> deencode log2 Rec709 ā€“> ACES ā€“> RRT ā€“> ODT to Target Space)
    [LIST]

  • This essentially only uses the cube file for its crosstalk feature. The RRT takes the place of filmic contrast profiles.

[/LIST]

  • ā€œThe ACES displayā€, specifically has fewer options:

  • Raw

  • ACES (same as Raw. Because scene_linear is ACES. Itā€™s there for the sake of being there)

  • Filmic display, works exactly as filmic blender.

  • Raw

  • Log (Rec709 ā€“> log2 encoding ā€“> desat65cube.spi3d ā€“> multiply; itā€™s assumed that the Look does the ACES ā€“> Rec709 transform before the View)

  • Looks implement Filmic Blenderā€™s contrast profiles.

  • Contrast profiles, false color - all work as usual with Filmic/Default. Implementation hasan ā€œACES ā€“> Rec709ā€ matrix prepended.

  • In a perfect world, an ACES gamut, log2 encoded, version of desat65cube.spi3d and the other looks would remove these nasty ACES ā€“> Rec709 transforms and keep us in the realm of ACES logic, instead of exporting us to Rec709 logicā€¦ No?

[/LIST]