I'm trying to write my own spectral path tracer and a bit stuck with converting computed SPDs to LDR RGB values. What I'm doing right now is convert sampled SPDs (in W/m^-1
) to XYZ using CIE 1931 color matching functions:
Vec3 xyz(0, 0, 0);
float norm = 0;
for (int i = 0; i < SPD_SAMPLE_COUNT; ++i) {
const float wavelength =
lerp(MIN_WAVELENGTH, MAX_WAVELENGTH, i / SPD_SAMPLE_COUNT);
const Vec3 cieMatchingFunctionValue = cie1931(wavelength);
xyz += cieMatchingFunctionValue * spd[i];
norm += cieMatchingFunctionValue.y;
}
xyz /= norm;
Here implementation of cie1931
is an anylitic approximation from Wyman et al.
Then XYZ to xyY (i.e. chromaticity and luminance), normalise luminance to be in [0, 1] range across the whole image (for now by dividing my max luminance just to keep it simple):
float maxLuminance = 0;
for (auto &xyz : image) {
maxLuminance = std::max(maxLuminance, xyz.y);
}
for (auto &xyz : image) {
Vec2 chromaticity = Vec2(xyz.x, xyz.y) / (xyz.x + xyz.y + xyz.z);
float luminance = xyz.y / maxLuminance;
// then to "scaled" XYZ and RGB, see below
}
Also I've tried Reinhard tone mapping operator:
float logSum = 0;
for (auto &xyz : image) {
logSum += std::log(xyz.y);
}
float logAvgLuminance = std::exp(logSum / image.size());
for (auto &xyz : image) {
Vec2 chromaticity = Vec2(xyz.x, xyz.y) / (xyz.x + xyz.y + xyz.z);
const float a = 0.18; // constant from Reinhard's paper.
float luminance = a * xyz.y / logAvgLuminance;
luminance /= luminance + 1;
// then to "scaled" XYZ and RGB, see below
}
Then back to XYZ, than to linear RGB (wia this transform):
for (auto &xyz : image) {
// XYZ to chromaticity and "scaled luminance", see above
Vec3 scaledXyz(
chromaticity.x * luminance / chromaticity.y,
luminance,
(1 - chromaticity.x - chromaticity.y) * luminance / chromaticity.y
);
float r = dot(Vec3( 3.2406, -1.5372, -0.4986), scaledXyz);
float g = dot(Vec3(-0.9689, 1.8758, 0.0415), scaledXyz);
float b = dot(Vec3( 0.0557, -0.2040, 1.0570), scaledXyz);
// Output RGB, i.e. std::cout << Vec3(r, g, b);
}
After that I still get RGB values outside of [0..1]. What am I doing wrong? It seems that "max" luminance should depend on chromaticities of the color. Is there an algorithm to account for that?