Rendering in XYZ color space and why this is wrong

I've been asked lately several times if it is possible to make V-Ray render in XYZ color space. The answer is 'no' because rendering in XYZ color space is completely wrong. Here I describe why and give some examples.

The rendering equation describes the light reaching a point in space from a given direction. However it also has another parameter that for convenience is most often ignored. This parameter is the wavelength of the light. One direct consequence of the wavelength dependence is that, when ignoring effects of fluorescence (materials that absorb light at one wavelength and re-emit it at another) what happens at one wavelength is completely independent of what happens at another. The RGB color space in that regard is a well-defined physical space where we solve the rendering equation for three particular wavelengths. Each of the red, green and blue components has a specific physical unit of measurement. Most render engines solve the equation simultaneously for the three or more wavelengths to speed things up, but the fundamental independence of the three color components is honored - the RGB components are 'orthogonal' and independent from one another.

The XYZ color space on the other hand is not a physical, but a perceptual color space specifically designed to help represent color values. Except for the Y component, the other two components have no physical meaning whatsoever and no physical unit can be attached to them. It is very useful concept for transforming colors from one color reproduction device to another, but it is not designed for physically based rendering.

Below are some comparison examples between several renderers. Some operate in RGB color space, there is one spectral renderer and one renderer that operates in XYZ color space. The goal here is to compare how these renderers handle color interactions between surfaces and whether the results are physically plausible.

Example 1: Color independence

In this example, we have a plane and a sphere lit only by a uniform white skylight. The plane has a diffuse checker texture alternating between red and magenta, and green reflection color, while the sphere has red diffuse color, green reflection color and blue refraction color. According to the rendering equation, because the red/green/blue wavelengths are independent, the red channel of the result would show a scene with only diffuse materials; the green channel would show a scene with only reflections, and the blue channel would show a glass sphere on a checker diffuse plane. Note that we don't expect to get the same result between all the renderers, because of the different ways in which they interpret material components. What we do expect, is to see a perfectly diffuse scene in the red channel with no trace of the checker map; a perfectly reflective scene in the green channel with no diffuse or checker components; and a refractive sphere with a checker map on the plane in the blue component.

Renderer type Rendered image Red channel Green channel Blue channel
RGB renderer A
RGB renderer B
Spectral renderer
XYZ renderer

Here you see that the two RGB renderers and the spectral renderer are in agreement and produce the expected result. The XYZ renderer however is wrong - reflections bleed between the diffuse and refraction channels in an unexpected way. This result is not simply "different"; it is plainly wrong. As a side consequence, if you attempt to do any compositing with the resulting image in a post-processing program, you might not be able to reproduce the beauty element from the individual components.

Example 2: Color bleeding

The next example is a scene that measures the correctness of the color bleeding. It is a simple scene for which the rendering equation can be computed exactly and so it is very easy to verify the correctness of a renderer. The scene consists of a small sphere light in the center of a large sphere with a diffuse material. For this scene, 100 GI bounces were specified for the renderers that have such control. First we give example renders with a grey material applied to the sphere, and then we exchange that for a color diffuse material. When rendering with a grey material with reflectivity 0.25, 0.5 and 0.75 respectively, all four renderers are mostly in agreement. Now we apply a diffuse material with 0.25, 0.5 and 0.75 for the red/green/blue components respectively. What we expect to get, is that each component in the final image will be exactly the same as the respective result when we had a greyscale material.

         
RGB renderer A grayscale images  
Diffuse 0.25, 0.25, 0.25

Diffuse 0.5, 0.5, 0.5

Diffuse 0.75, 0.75, 0.75
RGB renderer A color image and components
Diffuse 0.25, 0.5, 0.75
 

Diffuse 0.25, 0.5, 0.75
Red component

Diffuse 0.25, 0.5, 0.75
Green component

Diffuse 0.25, 0.5, 0.75
Blue component
RGB renderer B grayscale images  
Diffuse 0.25, 0.25, 0.25

Diffuse 0.5, 0.5, 0.5

Diffuse 0.75, 0.75, 0.75
RGB renderer B color image and components
Diffuse 0.25, 0.5, 0.75
 

Diffuse 0.25, 0.5, 0.75
Red component

Diffuse 0.25, 0.5, 0.75
Green component

Diffuse 0.25, 0.5, 0.75
Blue component
Spectral renderer grayscale images  
Diffuse 0.25, 0.25, 0.25

Diffuse 0.5, 0.5, 0.5

Diffuse 0.75, 0.75, 0.75
Spectral renderer color image and components
Diffuse 0.25, 0.5, 0.75
 

Diffuse 0.25, 0.5, 0.75
Red component

Diffuse 0.25, 0.5, 0.75
Green component

Diffuse 0.25, 0.5, 0.75
Blue component
XYZ renderer grayscale images  
Diffuse 0.25, 0.25, 0.25

Diffuse 0.5, 0.5, 0.5

Diffuse 0.75, 0.75, 0.75
XYZ renderer color image and components
Diffuse 0.25, 0.5, 0.75
 

Diffuse 0.25, 0.5, 0.75
Red component - actually negative!

Diffuse 0.25, 0.5, 0.75
Green component

Diffuse 0.25, 0.5, 0.75
Blue component

The two RGB renderers and the spectral renderer correctly produce the expected result. However, the XYZ renderer produces something completely unexpected - the red color component in the resulting image is negative! Again, this is not simply a "different" result; it means that color bleeding is completely wrong.

Lets look at a real-world example, colored Christmas ornaments:

Photograph
RGB renderer
XYZ renderer

The spheres are a pale shade of pink, and as the reflection depth increases, the color shifts more and more towards red, as it is the wavelength that is least absorbed by the material. As can be seen, the RGB renderer more or less faithfully represents the real-world effect. The XYZ renderer on the other hand, incorrectly darkens the result. The darkening is not a result of limited ray depth - for both renderers we set the reflection/GI bounces to 100, and identical materials are applied to the spheres.

Example 3: Color transprency

The next example is somewhat similar to the previous one, but deals with color transparency. We have a series of semi-transparent thin boxes where each box has been set up to let through only a certain percentage of the light behind it. For each renderer, we rendered out two sets of images. The first set consisted of the same scene but with different grayscale absorption values. In the second set of renders, the aborption values are colors, but the color components have values like in the grayscale images. We would expect, as would happen in the real world, that the red/green/blue channel of the color images would match the respective greyscale one.

         
RGB renderer A grayscale images  
Absorption 1.0, 1.0, 1.0

Absorption 0.5, 0.5, 0.5

Absorption 0.25, 0.25, 0.25
RGB renderer A color image and components
Absorption 1.0, 0.5, 0.25
 

Absorption 1.0, 0.5, 0.25
Red component

Absorption 1.0, 0.5, 0.25
Green component

Absorption 1.0, 0.5, 0.25
Blue component
RGB renderer B grayscale images  
Absorption 1.0, 1.0, 1.0

Absorption 0.5, 0.5, 0.5

Absorption 0.25, 0.25, 0.25
RGB renderer B color image and components
Absorption 1.0, 0.5, 0.25
 

Absorption 1.0, 0.5, 0.25
Red component

Absorption 1.0, 0.5, 0.25
Green component

Absorption 1.0, 0.5, 0.25
Blue component
Spectral renderer grayscale images  
Absorption 1.0, 1.0, 1.0

Absorption 0.5, 0.5, 0.5

Absorption 0.25, 0.25, 0.25
Spectral renderer color image and components
Absorption 1.0, 0.5, 0.25
 

Absorption 1.0, 0.5, 0.25
Red component

Absorption 1.0, 0.5, 0.25
Green component

Absorption 1.0, 0.5, 0.25
Blue component
XYZ renderer grayscale images  
Absorption 1.0, 1.0, 1.0

Absorption 0.5, 0.5, 0.5

Absorption 0.25, 0.25, 0.25
XYZ renderer color image and components
Absorption 1.0, 0.5, 0.25
 

Absorption 1.0, 0.5, 0.25
Red component

Absorption 1.0, 0.5, 0.25
Green component

Absorption 1.0, 0.5, 0.25
Blue component

Here the two RGB renderers give results in line with the expectations, but both the spectral renderer and the XYZ renderer produce inconsistent results. The XYZ renderer gives negative results for some color components.

The spectral renderer does not seem to attenuate the green light component properly - we would expect that since we set the green absoprtion to 0.5, it will go down to zero eventually, like all other renderers. This suggests that perhaps there is some issue with the implementation of color transparency. This is confirmed by the fact that the spectral renderer also produces strange results for aborption value equal to grayscale 0.75, 0.75, 0.75, where instead of gradually decreasing, the intensity abruprtly cuts off to black:

 

Example 4: Color interaction

This scene is again very simple - a blue diffuse sphere inside a yellow refractive sphere shell lit by a uniform white environment. In the real world, the inside sphere appears black and this is what we expect from our render engine.

RGB renderer A RGB Renderer B Spectral renderer XYZ renderer

Here the XYZ renderer is plainly wrong - we will never get this in the real world.

Again, this might seem a far-fetched situation, but here is something similar that happens in the real world - a blue object is put inside an orange bowl. When inside the bowl, the blue object becomes very dark, almost black - it does not become a cheerful shade of red.