// Copyright Epic Games, Inc. All Rights Reserved. // urn:ampas:aces:transformId:v2.0:Lib.Academy.DisplayEncoding.a2.v1 // Display Encoding Functions // // Library File with functions used for the display encoding/decoding steps // #pragma once #include "ACESOutputTransform.ush" float3 apply_white_scale( float3 XYZluminance, ODTParams PARAMS, bool _invert) { float3 RGB_w = mul( PARAMS.OUTPUT_XYZ_TO_RGB, PARAMS.XYZ_w_limit ); float3 RGB_w_f = (1./referenceLuminance) * RGB_w; float scale = 1. / max( max(RGB_w_f[0], RGB_w_f[1]), RGB_w_f[2]); // scale factor is equal to 1/largestChannel return (_invert ? (1./scale) : scale) * XYZluminance; } float3 clamp_zero_to_peakLuminance( float3 XYZ_in, ODTParams PARAMS) { float3 rgb = mul(PARAMS.LIMIT_XYZ_TO_RGB, XYZ_in); // Clamp to relative peakLuminance in RGB space prior to white scaling rgb = clamp(rgb, 0.0, PARAMS.peakLuminance / referenceLuminance); float3 XYZ = mul(PARAMS.LIMIT_RGB_TO_XYZ, rgb); return XYZ; } float3 white_limiting(float3 XYZ_in, ODTParams PARAMS, bool scale_white, bool _invert = false) { // The white limiting step clamps the output to between zero and the peak // luminance value so that values do not exceed the maximum value of the // tonescale. // If the creative white differs from the calibration white of the display, // unequal display code values will be required to produce the neutral of // the creative white. Without scaling, one channel would hit the max value // first while the other channels continue to increase, resulting in a hue // shift. To avoid this, the white scaling finds the largest channel and // applies a scale factor to force the point where this channel hits max to // 1.0, assuring that all three channels "fit" within the peak value. In the // inverse direction, the white scaling is removed. float3 XYZ = XYZ_in; // Clamp to peak luminance XYZ = clamp_zero_to_peakLuminance(XYZ, PARAMS); // White point scaling XYZ = scale_white ? apply_white_scale(XYZ, PARAMS, _invert) : XYZ; return XYZ; }