1
$\begingroup$

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

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?

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 :)

$\endgroup$
2
  • $\begingroup$ 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? $\endgroup$ Jul 8 at 6:05
  • $\begingroup$ 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. $\endgroup$
    – The Light
    Jul 8 at 11:01
2
$\begingroup$

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:

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 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) 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;
}
$\endgroup$
6
  • $\begingroup$ 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... $\endgroup$
    – The Light
    Jul 7 at 3:27
  • $\begingroup$ 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. $\endgroup$ Jul 7 at 5:01
  • $\begingroup$ Alright, thanks, I would need sometime to check it out so I can finally accept the answer. Thanks a lot... :) $\endgroup$
    – The Light
    Jul 7 at 5:42
  • 1
    $\begingroup$ 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 $\endgroup$
    – The Light
    Jul 8 at 12:32
  • $\begingroup$ 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. $\endgroup$ Jul 8 at 20:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.