This package extends URP by adding custom tone mapping algorithms, providing more options for different visual requirements and workflows.
GT7 Tone Mapping Sample
Since adding new tone mapping functions to URP is not officially supported through Unity's API, integration requires either URP modifications or a renderer feature. Please see the Integration Guide for details
- Additional tone mapping algorithms
- HDR display output compatibility
- Runtime LUT generation supports variable peak brightness configurations
- Custom LUT support
- Allows use of external LogC-encoded lookup tables
- Configurable LUT size (32³ to 65³) for quality/memory tradeoff
- Found in the Additional Properties section of the Custom Tone Mapping component
- Unity 6 or later
- Universal Render Pipeline
Install via OpenUPM:
- Package is available on OpenUPM. If you have openupm-cli installed, run this in your Unity project root:
openupm add net.aki-null.tonemapping - To update, specify the version you want:
openupm add [email protected]
Install via UPM (Git URL):
- Open Window > Package Manager
- Click the + button in the top-left corner
- Select Add package from git URL
- Enter:
https://github.com/aki-null/CustomToneMappingURP.git - Follow the Integration Guide
Alternatively, add it to your Packages/manifest.json:
{
"dependencies": {
"net.aki-null.tonemapping": "https://github.com/aki-null/CustomToneMappingURP.git"
}
}- Add a Tonemapping volume component
- Set to None when using the Renderer Feature method
- Set to Custom when using the URP modification method
- Add a Custom Tone Mapping volume component and select a mode
- Add the mode-specific volume component to configure parameters
- Each mode requires its own dedicated volume component (e.g., GT Tone Mapping, GT7 Tone Mapping, AgX Tone Mapping, etc.)
Rather than performing tone mapping calculations directly on the GPU during rendering, the system pre-computes the tone mapping results into a 3D lookup table on the CPU.
The workflow operates as follows: Volume components supply tone mapping parameters, which the system uses to generate a 3D LUT. This LUT is stored as a 2D texture strip and uploaded to the GPU. The LUT is regenerated automatically when parameters change.
During rendering, URP's post-processing pipeline samples from this pre-computed LUT to apply the tone mapping.
Designed by Polyphony Digital for Gran Turismo Sport. Initially presented at CEDEC 2017 and later at SIGGRAPH Asia 2018.
Designed by Polyphony Digital for Gran Turismo 7. Presented at SIGGRAPH 2025.
Designed by Troy Sobotka. The LUT generation is based on Eary Chow's version.
When Custom LUT is selected, assign a LUT texture (Alexa LogC El1000) in the Custom Tone Mapping component. Note that Custom LUT mode uses a static LUT and does not support variable peak brightness like the other modes.
The Custom Tone Mapping Renderer Feature enables custom tone mapping without modifying URP packages. This approach is suitable for:
- Projects that cannot modify URP source code
- Testing custom tone mapping before committing to URP modifications
- Maintaining easier Unity version upgrades
- Incompatible with LDR color grading
- Less efficient than native integration due to additional rendering pass overhead
- Add Custom Tone Mapping Renderer Feature to your Universal Renderer Data
- Set URP's Tonemapping mode to None in your Volume Profile
- Add a Custom Tone Mapping volume component and select a mode
- Add the mode-specific volume component to configure parameters
- Each mode requires its own dedicated volume component (e.g., GT Tone Mapping, GT7 Tone Mapping, AgX Tone Mapping, etc.)
The renderer feature injects a tone mapping pass into the render pipeline by intercepting the color grading LUT after URP's LUT generation pass and applying custom tone mapping. This approach provides better integration with the pipeline compared to directly tone mapping the framebuffer.
Modifying the URP package provides native integration with optimal performance and full compatibility with all URP features.
File: Packages/com.unity.render-pipelines.universal/Runtime/Unity.RenderPipelines.Universal.Runtime.asmdef
Add the custom tonemapper assembly reference to the references array:
"references": [
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:69257879134bba646869b21467b3338d",
"GUID:ab67fb10353d84448ac887a7367cbda8",
"GUID:7dbf32976982c98448af054f2512cb79",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:2665a8d13d1b3f18800f46e256720795",
"GUID:4fd6538c1c56b409fb53fdf0183170ec",
"GUID:86bc95e6fdb13ff43aa04316542905ae",
"GUID:d04eb9c554ad44ceab303cecf0c0cf82",
"GUID:214c0945bb158c940aada223f3223ee8",
+ "net.aki-null.ToneMapping.URP",
"GUID:c49c619b6af2be941af9bcbca2641964"
],File: Packages/com.unity.render-pipelines.universal/Runtime/Overrides/Tonemapping.cs
Around line 27
namespace UnityEngine.Rendering.Universal
{
[Serializable]
public enum TonemappingMode
{
/// <summary>
/// Use this option if you do not want to apply tonemapping
/// </summary>
None,
/// <summary>
/// Use this option if you only want range-remapping with minimal impact on color hue and saturation.
/// It is generally a great starting point for extensive color grading.
/// </summary>
Neutral, // Neutral tonemapper
/// <summary>
/// Use this option to apply a close approximation of the reference ACES tonemapper for a more filmic look.
/// It is more contrasted than Neutral and has an effect on actual color hue and saturation.
/// Note that if you use this tonemapper all the grading operations will be done in the ACES color spaces for optimal precision and results.
/// </summary>
- ACES // ACES Filmic reference tonemapper (custom approximation)
+ ACES, // ACES Filmic reference tonemapper (custom approximation)
+ Custom
}File: Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/UberPost.shader
Around line 7:
HLSLINCLUDE
#pragma exclude_renderers gles
- #pragma multi_compile_local_fragment _ _HDR_GRADING _TONEMAP_ACES _TONEMAP_NEUTRAL
+ #pragma multi_compile_local_fragment _ _HDR_GRADING _TONEMAP_ACES _TONEMAP_NEUTRAL _TONEMAP_CUSTOM
#pragma multi_compile_local_fragment _ _FILM_GRAIN
#pragma multi_compile_local_fragment _ _DITHERING
#pragma multi_compile_local_fragment _ _GAMMA_20 _LINEAR_TO_SRGB_CONVERSIONAround line 33 (include order matters):
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
+ #include "Packages/net.aki-null.tonemapping/Runtime/URP/Shaders/TonemapParams.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/DebuggingFullscreen.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/DynamicScalingClamping.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/FoveatedRendering.hlsl"File: Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/LutBuilderHdr.shader
Around line 4 (include order matters):
HLSLINCLUDE
- #pragma multi_compile_local _ _TONEMAP_ACES _TONEMAP_NEUTRAL
+ #pragma multi_compile_local _ _TONEMAP_ACES _TONEMAP_NEUTRAL _TONEMAP_CUSTOM
#pragma multi_compile_local_fragment _ HDR_COLORSPACE_CONVERSION
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
+ #include "Packages/net.aki-null.tonemapping/Runtime/URP/Shaders/TonemapParams.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ACES.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#if defined(HDR_COLORSPACE_CONVERSION)
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/HDROutput.hlsl"
#endifAround line 202:
float3 Tonemap(float3 colorLinear)
{
#if _TONEMAP_NEUTRAL
{
colorLinear = NeutralTonemap(colorLinear);
}
#elif _TONEMAP_ACES
{
// Note: input is actually ACEScg (AP1 w/ linear encoding)
float3 aces = ACEScg_to_ACES(colorLinear);
colorLinear = AcesTonemap(aces);
}
+ #elif _TONEMAP_CUSTOM
+ #include "Packages/net.aki-null.tonemapping/Runtime/URP/Shaders/Tonemap.hlsl"
#endif
return colorLinear;
}
float3 ProcessColorForHDR(float3 colorLinear)
{
#ifdef HDR_COLORSPACE_CONVERSION
#ifdef _TONEMAP_ACES
float3 aces = ACEScg_to_ACES(colorLinear);
return HDRMappingACES(aces.rgb, PaperWhite, MinNits, MaxNits, RangeReductionMode, true);
#elif _TONEMAP_NEUTRAL
return HDRMappingFromRec2020(colorLinear.rgb, PaperWhite, MinNits, MaxNits, RangeReductionMode, HueShift, true);
+ #elif _TONEMAP_CUSTOM
+ #include "Packages/net.aki-null.tonemapping/Runtime/URP/Shaders/TonemapHdr.hlsl"
#else
// Grading finished in Rec2020, converting to the expected color space and [0, 10k] nits range
return RotateRec2020ToOutputSpace(colorLinear) * PaperWhite;
#endif
#endif
return colorLinear;
}File: Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl
Around line 108 in ApplyTonemap function:
half3 ApplyTonemap(half3 input)
{
#if _TONEMAP_ACES
float3 aces = unity_to_ACES(input);
input = AcesTonemap(aces);
#elif _TONEMAP_NEUTRAL
input = NeutralTonemap(input);
+#elif _TONEMAP_CUSTOM
+#include "Packages/net.aki-null.tonemapping/Runtime/URP/Shaders/TonemapLdr.hlsl"
#endif
return saturate(input);
}File: Packages/com.unity.render-pipelines.universal/Runtime/Passes/PostProcessPassRenderGraph.cs
Around line 1922 in RenderUberPost method:
switch (data.toneMappingMode)
{
case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break;
case TonemappingMode.ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapACES); break;
+ case TonemappingMode.Custom: CustomToneMapping.URP.UrpBridge.PrepareMaterial(material, data.cameraData.isHDROutputActive ? data.cameraData.hdrDisplayInformation : null); break;
default: break; // None
}File: Packages/com.unity.render-pipelines.universal/Runtime/Passes/ColorGradingLutPass.cs
Around line 250 in ExecutePass method:
switch (tonemapping.mode.value)
{
case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break;
case TonemappingMode.ACES: material.EnableKeyword(allowColorGradingACESHDR ? ShaderKeywordStrings.TonemapACES : ShaderKeywordStrings.TonemapNeutral); break;
+ case TonemappingMode.Custom: CustomToneMapping.URP.UrpBridge.PrepareMaterial(material, passData.cameraData.isHDROutputActive ? passData.cameraData.hdrDisplayInformation : null); break;
default: break; // None
}This project is licensed under the MIT License – see LICENSE.md.
This project includes third-party code under its own licenses. See THIRD_PARTY_NOTICES.md.