Source code for pbrt, the renderer described in the third edition of "Physically Based Rendering: From Theory To Implementation", by Matt Pharr, Wenzel Jakob, and Greg Humphreys.
I found a problem when trying to verify my own solution to this that (at least) affects materials that uses spectral data to describe reflectance/transmittance properties. The problem is the following function:
static RGBSpectrum FromSampled(const Float *lambda, const Float *v, int n) {
...
Float xyz[3] = {0, 0, 0};
for (int i = 0; i < nCIESamples; ++i) {
Float val = InterpolateSpectrumSamples(lambda, v, n, CIE_lambda[i]);
xyz[0] += val * CIE_X[i];
xyz[1] += val * CIE_Y[i];
xyz[2] += val * CIE_Z[i];
}
Float scale = Float(CIE_lambda[nCIESamples - 1] - CIE_lambda[0]) /
Float(CIE_Y_integral * nCIESamples);
xyz[0] *= scale;
xyz[1] *= scale;
xyz[2] *= scale;
return FromXYZ(xyz);
}
Which corresponds to the Riemann sum of:
X = ∫(S(λ)·x(λ)·dλ) / ∫(y(λ)·dλ)
Y = ∫(S(λ)·y(λ)·dλ) / ∫(y(λ)·dλ)
Z = ∫(S(λ)·z(λ)·dλ) / ∫(y(λ)·dλ)
The problem for the reflectance case is that the reflectance S(λ) has to attenuate some illuminant I(λ) for these values to make sense, and the normalization factor should also take this into account:
X = ∫(S(λ)·I(λ)·x(λ)·dλ) / ∫(y(λ)·I(λ)·dλ)
Y = ∫(S(λ)·I(λ)·y(λ)·dλ) / ∫(y(λ)·I(λ)·dλ)
Z = ∫(S(λ)·I(λ)·z(λ)·dλ) / ∫(y(λ)·I(λ)·dλ)
The code implicitly uses a constant equal energy illuminant I(λ)=C that is eliminated, which is fine except that the function used to then transform the resulting XYZ tristimulus values to sRGB assumes XYZD65:
This shifts the RGB values to more reddish hues in places where SPD's are used to describe reflectance, for example when specifying eta and k for conductive materials. The following scene uses spectral data to specify eta for usual soda-lime glass:
(FrConductor generalizes to dielectrics for k=0, so this is supposed to represent the specular reflectance part of glass)
This can be fixed by either using the D65 illuminant as I(λ) when applying the CIE CMF's in the reflectance case, or by simply multiplying the XYZ values with the D65 white point before applying the transformation to sRGB:
static RGBSpectrum FromSampled(const Float *lambda, const Float *v, int n,
SpectrumType type = SpectrumType::Illuminant) {
...
Float xyz[3] = {0, 0, 0};
for (int i = 0; i < nCIESamples; ++i) {
Float val = InterpolateSpectrumSamples(lambda, v, n, CIE_lambda[i]);
xyz[0] += val * CIE_X[i];
xyz[1] += val * CIE_Y[i];
xyz[2] += val * CIE_Z[i];
}
Float scale = Float(CIE_lambda[nCIESamples - 1] - CIE_lambda[0]) /
Float(CIE_Y_integral * nCIESamples);
xyz[0] *= scale;
xyz[1] *= scale;
xyz[2] *= scale;
if (type == SpectrumType::Reflectance) {
xyz[0] *= 0.95047f;
xyz[2] *= 1.08883f;
}
return FromXYZ(xyz);
}
Note that this requires an additional SpectrumType argument that differentiates between reflectance and illuminant spectrums.
I found a problem when trying to verify my own solution to this that (at least) affects materials that uses spectral data to describe reflectance/transmittance properties. The problem is the following function:
Which corresponds to the Riemann sum of:
The problem for the reflectance case is that the reflectance
S(λ)
has to attenuate some illuminantI(λ)
for these values to make sense, and the normalization factor should also take this into account:The code implicitly uses a constant equal energy illuminant
I(λ)=C
that is eliminated, which is fine except that the function used to then transform the resulting XYZ tristimulus values to sRGB assumes XYZD65:This shifts the RGB values to more reddish hues in places where SPD's are used to describe reflectance, for example when specifying
eta
andk
for conductive materials. The following scene uses spectral data to specifyeta
for usual soda-lime glass:(
FrConductor
generalizes to dielectrics fork=0
, so this is supposed to represent the specular reflectance part of glass)This can be fixed by either using the D65 illuminant as
I(λ)
when applying the CIE CMF's in the reflectance case, or by simply multiplying the XYZ values with the D65 white point before applying the transformation to sRGB:Note that this requires an additional
SpectrumType
argument that differentiates between reflectance and illuminant spectrums.