ACES Filmic Tone Mapping Curve

Careful mapping of HDR values to LDR is an important part of a modern game rendering pipeline. One of the goals of our new renderer was to replace Reinhard‘s tone mapping curve with some kind of a filmic tone mapping curve. We tried one from Ucharted 2 and tried rolling our own, but weren’t happy with either of this solutions. Finally, we settled on the one from ACES, which is currently a default tone mapping curve in Unreal Engine 4.

ACES color encoding system was designed for seamless working with color images regardless of input or output color space. It also features a carefully crafted filmic curve for displaying HDR images on LDR output devices. Full ACES integration is a bit of overkill for games, but we can just sample ODT( RRT( x ) ) transform and fit a simple curve to this data. We don’t even need to run any ACES code at all, as ACES provides reference images for all transforms. Although there is no linear RGB D65 ODT transform, but we can just use REC709 D65 and remove 2.4 gamma from it.

Curve was manually fitted (max fit error: 0.0138) to be more precise in the blacks – after all we will be applying some kind gamma afterwards. Additionally, data was pre-exposed, so 1 on input maps to ~0.8 on output and resulting image’s brightness is more consistent with the one without any tone mapping curve at all. For the original ACES curve just multiply input (x) by 0.6.

Fitted curve’s HLSL source code:

float3 ACESFilm( float3 x )
    float a = 2.51f;
    float b = 0.03f;
    float c = 2.43f;
    float d = 0.59f;
    float e = 0.14f;
    return saturate((x*(a*x+b))/(x*(c*x+d)+e));

Fitted curve plotted against source data’s sample points:


UPDATE: This is a very simple luminance only fit, which over saturates brights. This was actually something consistent with our art direction, but for a more realistic rendering you may want a more complex fit like this one from Stephen Hill.

This entry was posted in Graphics, Lighting. Bookmark the permalink.

11 Responses to ACES Filmic Tone Mapping Curve

  1. opioidwp says:

    Should this be used like the uncharted function (U()) like this: tonemapped = U(color) / U(White); or simply like tonemapped = KN(color); and done?


  2. ProtonFactor says:

    Hey, I’m interested in running the ACES code to see how close your curve matches for myself (I like to verify data and modify it sometimes). But at the RTT function in the source code it says the colors should be in aces color space as input… Should I be using linear rgb as input for RTT? or is there a transform from linear to aces color space?


    • Yes, basically you can just use linear space RGB as input (ACES is RGB linear color space with a D60 white point). Keep in mind that my curve is shifted (pre-exposed), so it won’t match a by-the-book ACES curve.


      • ProtonFactor says:

        Ah okay, thanks for the clarification. Duly noted about the shift, I’ll account for that. And also thanks for the post, it works really well by the way. My team would just like to be able to modify the curve without straying too far from the “ground truth” and hence we have to test it against the code. Anyway, thanks again.


  3. Pingback: HDR Display – First Steps | Krzysztof Narkowicz

  4. Pingback: Image dynamic range | Bart Wronski

  5. Pingback: KlayGE 4.10中渲染的改进(二):Tone mapping - KlayGE游戏引擎

  6. Pingback: HDR Rendering With WebGL -

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s