Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix illuminant for spectral reflectance data #294

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Fix illuminant for spectral reflectance data #294

wants to merge 1 commit into from

Conversation

linusmossberg
Copy link

@linusmossberg linusmossberg commented Jul 13, 2020

Fixes #293

Reflectance and illuminant spectral distributions are different and must be treated differently when converting to sRGB. The difference essentially stems from that emitted radiance with equal energy for all wavelengths is not considered to be white, while surfaces that reflects radiance equally for all wavelengths are.

More details

The D65 illuminant was chosen as the 'white illuminant' for the sRGB color space. Once integrated with the CIE color matching functions, the XYZ tristimulus value of this illuminant is XYZ(0.9505, 1.0, 1.0888). This value therefore represents white in the sRGB color space, so by definition XYZ(0.9505, 1.0, 1.0888) is converted to sRGB(1.0, 1.0, 1.0).

A white surface reflects all wavelengths equally, because the reflected radiance is then proportionally equal to the incoming radiance. In other words, the reflectance of a white surface as a function of wavelength is constant, R(λ)=C. Once integrated with the CIE color matching functions, the XYZ tristimulus values of this reflectance is XYZ(C, C, C). If we convert this to sRGB, which defines XYZ(0.9505, 1.0, 1.0888) as white, this reflectance is sRGB(1.2048·C, 0.9483·C, 0.9088·C) ≠ white.

To correctly convert our white reflectance to the sRGB color space, we must account for the fact that the color space considers XYZ(0.9505, 1.0, 1.0888) to be white, which is done by multiplying the XYZ reflectance values with this white point before performing the conversion to sRGB: XYZ(0.9505·C, 1.0·C, 1.0888·C) => sRGB(C, C, C). This applies to all surfaces and not only white ones, but it's easier to illustrate with one.


This PR uses the existing distinction between input illuminant and reflectance spectrums and applies the D65 white point to the integrated reflectance XYZ values before converting to sRGB.

I don't know of a scene where this makes much of a difference, because spectral reflectance distributions are only used for eta and k in metals as far as I'm aware. The difference seems to be close to eliminated in the Fresnel function for conductives when both eta and k are modified in the same way:

crown_ba
The color becomes a bit less greenish and more yellowish in places with lots of consecutive gold reflections, but it's barely noticeable. Gold is the only metal I've been able to see a slight difference with.

This is not the case if we specify k=0 however, or if we for example specify the diffuse reflectance for a material as a gray reflectance distribution:

Material "matte"
    "spectrum Kd" [ 300.0 0.5 
                    800.0 0.5 ]

spheres
Measured diffuse reflectance distributions exists and are useful for the spectral renderer, and this change should make sure that the result is the same when switching to RGBSpectrum.

Edit:

Another option for a better approximation is to use the Bradford E to D65 chromatic adaption:

static RGBSpectrum FromSampled(const Float *lambda, const Float *v, int n,
                               SpectrumType type = SpectrumType::Illuminant) {
    ...
    if (type == SpectrumType::Reflectance) {
        Float xyz_d65[3];
        xyz_d65[0] =  0.953188f * xyz[0] - 0.026590f * xyz[1] + 0.023873f * xyz[2];
        xyz_d65[1] = -0.038246f * xyz[0] + 1.028841f * xyz[1] + 0.009406f * xyz[2];
        xyz_d65[2] =  0.002607f * xyz[0] - 0.003033f * xyz[1] + 1.089253f * xyz[2];
        return FromXYZ(xyz_d65);
    }
    return FromXYZ(xyz);
}

At that point it's probably best to just include the D65 data and use it to compute the proper reflectance integral though.

Uses the existing distinction between illuminant and reflectance input spectrums and applies the D65 illuminant to the integrated XYZ tristimulus values before sRGB conversion in the reflectance case.
The D65 white point was computed using the same data and integration+interpolation method that the program uses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect illuminant when integrating spectral reflectance data to sRGB
1 participant