<p>What you're looking for is called <em>tone mapping</em>, which is the process of mapping from HDR color values generated by a physically based renderer, to LDR color values suitable for display on a screen. It also incorporates changes in saturation, such as desaturating the color when the values get very bright. You will likely also need <em>exposure compensation</em> prior to tone mapping, in order to set the overall brightness of the image to an appropriate level.</p>
<p>There are many different tone mapping curves out there; it's an aesthetic choice, so there's no &quot;right answer&quot;, but a good one to start with might be the <a href="https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/" rel="nofollow noreferrer">ACES curve, with analytic approximation given by
Krzysztof Narkowicz</a>:</p>
<pre class="lang-cpp prettyprint-override"><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));
}
</code></pre>
<p>See also <a href="https://bruop.github.io/tonemapping/" rel="nofollow noreferrer">this blog post</a> for more background and comparisons of different tone mapping approaches.</p>
<p>You'll want to apply an exposure scaling, and limit the saturation of colors to slightly less than 100% (otherwise, perfectly saturated colors like (1, 0, 0) will not desaturate to white, but will remain saturated). Then apply this curve, and finally gamma encoding (<a href="https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation" rel="nofollow noreferrer">linear to sRGB</a>) to get the final output values in [0, 1]. In code, something like:</p>
<pre><code>float3 LinearToSRGB(float3 x)
{
    return (x &lt; 0.0031308f) ?
                x * 12.92f :
                pow(x, 1.0f/2.4f) * 1.055f - 0.055f;
}

float3 HDRToOutput(float3 hdr, float exposure)
{
    // Exposure (tune the value to set the overall brightness;
    // positive makes it brighter, while negative makes it darker)
    hdr *= exp2(exposure);

    // Limit saturation to 99% - maps pure colors like (1, 0, 0) to (1, 0.01, 0.01)
    float maxComp = max(hdr.r, hdr.g, hdr.b);
    hdr = max(hdr, 0.01 * maxComp);

    // Apply tonemapping curve
    float3 ldrLinear = ACESFilm(hdr);

    // Convert to sRGB
    float3 ldrSRGB = LinearToSRGB(hdr);
    return ldrSRGB;
}
</code></pre>

CC BY-SA 4.0
2021-07-07T03:27:36.637Z
Thanks a lot!, but what does the ACESFilm take in as the parameter... is it an array of the colours as in [r, g ,b] ? I am not very familiar with C/C++ actually...
The Light
CC BY-SA 4.0
2021-07-07T05:01:39.247Z
This code is actually HLSL, but it's meant for you to translate it to whatever language and math library you're using. Float3 in HLSL is just a vector of 3 floats, which could be xyz or (as here) rgb.
Nathan Reed
CC BY-SA 4.0
2021-07-08T12:32:32.017Z
I did try and yes it worked!!!  but I noticed that there is not input parameter for actual radiance unit, could you please tell a way to do that, I got it to work only with changing the exposure setting... Thank you so much @NathanReed
The Light
CC BY-SA 4.0
2021-07-08T20:15:07.927Z
I'm not sure what you mean by "input parameter for actual radiance unit" - the input is the HDR color which is the radiance of each color channel. Changing the exposure setting is expected, to adapt to the scene and light values that you have.
Nathan Reed
CC BY-SA 4.0
2021-07-06T18:55:18.797Z
2021-07-06T18:55:18.797Z
Nathan Reed
<p>I am new to this awesome community, I am currently writing a ray tracer but I hit a problem...</p>
<p>Let's say I have a surface(material doesn't matter) of colour [1, 0, 0] in RGB, and the radiance reaching a point on the surface is say 2000 units.</p>
<p>So my question is, after all the simulation, just before writing an output file(for example png), what operation or algorithm do I use to modify the colour using the radiance value, to give an perception of brightness.
If the radiance is to high, the output should give a somewhat white-ish colour, and if the radiance is less, it has to give a dark-ish colour.</p>
<p>Is there anyway I can do this easily and efficiently, or is it a complex process?</p>
<p>This is a rendered image from Blender...
<a href="https://i.sstatic.net/dEVz8.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/dEVz8.png" alt="enter image description here" /></a></p>
<p>As you can see in the image, the place closer to the light is white-ish(due to higher radiance reaching it) and rest is red or darker red, so what do I do to get a result like that?</p>
<p>I have no idea how to actually perform something like to get the expected RGB value...</p>
<p>Note: I want to generate a PNG image and not a .HDR or .EXR image</p>
<p>I hope the question is clear, and hopefully isn't a duplicate because I couldn't find my answer anywhere else...</p>
<p>Any help would be highly appreciated, thanks :)</p>

CC BY-SA 4.0
2021-07-08T06:05:58.280Z
I didn't quite get the question correctly. I think you just want to simulate that glossiness. Areas closer to the light should be bright and move to dark as you move away. This is just simple phong shading model? How are you shading your surface?
gallickgunner
CC BY-SA 4.0
2021-07-08T11:01:33.527Z
Actually, I want a physically accurate way of changing the colour according to the radiance falling on the surface. The radiance can be >= 0 without a limit and this is the problem and I don't want to limit it. When the radiance is something like 10,000 units or larger, regardless of the material, the output colour has to be somewhat completely white and the opposite for close to 0 radiance. I hope the question is now clear :) @NathanReed has answered what I wanted to know but I haven't tried it out yet.
The Light
<raytracing><color><physically-based><image><pbrt>
How to change a RGB value using a radiance value?
The Light
# How to change a RGB value using a radiance value?

(Asked by: username_0 on 2021-07-06)

I am new to this awesome community, I am currently writing a ray tracer but I hit a problem...

Let's say I have a surface(material doesn't matter) of colour [1, 0, 0] in RGB, and the radiance reaching a point on the surface is say 2000 units.

So my question is, after all the simulation, just before writing an output file(for example png), what operation or algorithm do I use to modify the colour using the radiance value, to give an perception of brightness.
If the radiance is to high, the output should give a somewhat white-ish colour, and if the radiance is less, it has to give a dark-ish colour.

Is there anyway I can do this easily and efficiently, or is it a complex process?

This is a rendered image from Blender...
[![enter image description here](https://i.sstatic.net/dEVz8.png)](https://i.sstatic.net/dEVz8.png)

As you can see in the image, the place closer to username_0 is white-ish(due to higher radiance reaching it) and rest is red or darker red, so what do I do to get a result like that?

I have no idea how to actually perform something like to get the expected RGB value...

Note: I want to generate a PNG image and not a .HDR or .EXR image

I hope the question is clear, and hopefully isn't a duplicate because I couldn't find my answer anywhere else...

Any help would be highly appreciated, thanks :)

**Tags:** raytracing, color, physically-based, image, pbrt

> **Comments:**
> **username_1**: I didn't quite get the question correctly. I think you just want to simulate that glossiness. Areas closer to username_0 should be bright and move to dark as you move away. This is just simple phong shading model? How are you shading your surface?  
> **username_0**: Actually, I want a physically accurate way of changing the colour according to the radiance falling on the surface. The radiance can be >= 0 without a limit and this is the problem and I don't want to limit it. When the radiance is something like 10,000 units or larger, regardless of the material, the output colour has to be somewhat completely white and the opposite for close to 0 radiance. I hope the question is now clear :) @username_2 has answered what I wanted to know but I haven't tried it out yet.  

---

## 1 answers

### Answer by username_2 on 2021-07-06. Score: 2 | Accepted answer

What you're looking for is called *tone mapping*, which is the process of mapping from HDR color values generated by a physically based renderer, to LDR color values suitable for display on a screen. It also incorporates changes in saturation, such as desaturating the color when the values get very bright. You will likely also need *exposure compensation* prior to tone mapping, in order to set the overall brightness of the image to an appropriate level.

There are many different tone mapping curves out there; it's an aesthetic choice, so there's no "right answer", but a good one to start with might be the [ACES curve, with analytic approximation given by
Krzysztof Narkowicz](https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/):

```cpp
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));
}

```

See also [this blog post](https://bruop.github.io/tonemapping/) for more background and comparisons of different tone mapping approaches.

You'll want to apply an exposure scaling, and limit the saturation of colors to slightly less than 100% (otherwise, perfectly saturated colors like (1, 0, 0) will not desaturate to white, but will remain saturated). Then apply this curve, and finally gamma encoding ([linear to sRGB](https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation)) to get the final output values in [0, 1]. In code, something like:

```
float3 LinearToSRGB(float3 x)
{
    return (x < 0.0031308f) ?
                x * 12.92f :
                pow(x, 1.0f/2.4f) * 1.055f - 0.055f;
}

float3 HDRToOutput(float3 hdr, float exposure)
{
    // Exposure (tune the value to set the overall brightness;
    // positive makes it brighter, while negative makes it darker)
    hdr *= exp2(exposure);

    // Limit saturation to 99% - maps pure colors like (1, 0, 0) to (1, 0.01, 0.01)
    float maxComp = max(hdr.r, hdr.g, hdr.b);
    hdr = max(hdr, 0.01 * maxComp);

    // Apply tonemapping curve
    float3 ldrLinear = ACESFilm(hdr);

    // Convert to sRGB
    float3 ldrSRGB = LinearToSRGB(hdr);
    return ldrSRGB;
}

```

> **Comments:**
> **username_0**: Thanks a lot!, but what does the ACESFilm take in as the parameter... is it an array of the colours as in [r, g ,b] ? I am not very familiar with C/C++ actually...  
> **username_2**: This code is actually HLSL, but it's meant for you to translate it to whatever language and math library you're using. Float3 in HLSL is just a vector of 3 floats, which could be xyz or (as here) rgb.  
> **username_0**: I did try and yes it worked!!!  but I noticed that there is not input parameter for actual radiance unit, could you please tell a way to do that, I got it to work only with changing the exposure setting... Thank you so much @username_2  
> **username_2**: I'm not sure what you mean by "input parameter for actual radiance unit" - the input is the HDR color which is the radiance of each color channel. Changing the exposure setting is expected, to adapt to the scene and username_0 values that you have.  

---

