Having spectral reflectance values is all well-and-good, but this still isn’t a color we can use in a look-up table.
Representing Spectral Reflection Values
How do we go about converting this data into something usable? Fortunately much of this work has already been done by the Commission internationale de l’éclairage (‘CIE’, pronounced ‘see’).
Human sight is broadly defined as being receptive to 3 colors: red, green and blue. This isn’t strictly true, as discussed previously, these categorizations are human constructs and don’t accurately reflect what is actually happening.
The human eye has 3 cones receptive to 3 different spectrums of visible light. Here they are illustrated across the visible spectrum:
We could multiply our reflectance values by these stimulus curves to get three color values. However, due to the distribution of cones in the human eye, the values to multiply by are actually dependent on the observer’s field of view.
Captain Dissilusion has a great little primer on color here, and is worth a watch:
The CIE Standard Observer and XYZ Color Space
To solve this problem, CIE has defined a color-mapping function we can use that standardizes on the 2º FOV. It looks like this:
If we multiply our reflectance values by each of these color-matching functions, we will given three values, X, Y and Z.
These are not RGB values. RGB is a color space – a way of numerically storing colours. XYZ is another type of color space. The CIE XYZ is a device-invariant way of storing color and we use it because not all colors are equal – is is possible to have two colors that are the same, but are composed of different wavelengths of light.
CIE XYZ is also a space from which you can convert to any other color space, such as RGB, sRGB, HSV, etc.
We can generate these curves using Gaussian functions. Gaussian functions are used to create parabola-like curves. We can generate them the following way:
By multiplying our reflectance values by this, we can obtain CIE XYZ values.
The sRGB Color Space
Now to convert them to usable color information for textures. We’ll convert them to sRGB as this is what we’ll need to textures in UE4.
To do this, we multiply our XYZ values by the RGB matrix for the 2º observer, with the D65 illuminant.
r = x * m[0][0] + y * m[0][1] + z * m[0][2]
g = x * m[1][0] + y * m[1][1] + z * m[1][2]
b = x * m[2][0] + y * m[2][1] + z * m[2][2]
In order to now get this into sRGB, we simply do a gamma correction:
if abs(c) > 0.0031308
c = 1.055 * c1/2.2 -0.055
else
c = 12.92 * c
where c is one of our channels, r, g or b
After all of that work, we now have sRGB colours from our reflectance values. Now, we’ll implement it