Search Unity

Visualizations of Color Solids Look Off

Discussion in 'Shaders' started by TommyPoulin, Feb 27, 2017.

  1. TommyPoulin

    TommyPoulin

    Joined:
    Feb 5, 2017
    Posts:
    14
    At the moment, I'm working on some color space transition functions. Specifically I'm working on functions for transitioning to and from HSV and HSL. They seem to look alright when I'm actually using them for their intended purpose (turning an RGB color to HSV or HSL, manipulating it in some way and then turning back to RGB).

    But when I try to actually visualize the color solid by passing in cylindrical coordinates to the functions for turning HSL or HSV back to RGB the results look a bit off and I don't really understand why.

    Here are the functions I'm using:
    Code (csharp):
    1.  
    2. inline float3 HSVtosRGB(float3 HSV)
    3. {
    4.    float chroma = HSV.z * HSV.y;
    5.    float hue = fmod(HSV.x, 360.0f);
    6.    hue = lerp(hue, 360.0f + hue, hue < 0.0f) / 60.0f;
    7.    float component = chroma * (1.0f - abs(fmod(hue, 2.0f) - 1.0f));
    8.  
    9.    float3 sRGB =
    10.        (float3(chroma, component, 0.0f) * ((0.0f <= hue) * (hue <= 1.0f))) +
    11.        (float3(component, chroma, 0.0f) * ((1.0f < hue) * (hue <= 2.0f))) +
    12.        (float3(0.0f, chroma, component) * ((2.0f < hue) * (hue <= 3.0f))) +
    13.        (float3(0.0f, component, chroma) * ((3.0f < hue) * (hue <= 4.0f))) +
    14.        (float3(component, 0.0f, chroma) * ((4.0f < hue) * (hue <= 5.0f))) +
    15.        (float3(chroma, 0.0f, component) * ((5.0f < hue) * (hue <= 6.0f)));
    16.  
    17.    float minColor = HSV.z - chroma;
    18.  
    19.    return sRGB + minColor;
    20. }
    21.  
    22. inline float3 HSLtosRGB(float3 HSL)
    23. {
    24.    float chroma = (1.0f - abs(2.0f * HSL.z - 1.0f)) * HSL.y;
    25.    float hue = fmod(HSL.x, 360.0f);
    26.    hue = lerp(hue, 360.0f + hue, hue < 0.0f) / 60.0f;
    27.    float component = chroma * (1.0f - abs(fmod(hue, 2.0f) - 1.0f));
    28.  
    29.    float3 sRGB =
    30.        (float3(chroma, component, 0.0f) * ((0.0f <= hue) * (hue <= 1.0f))) +
    31.        (float3(component, chroma, 0.0f) * ((1.0f < hue) * (hue <= 2.0f))) +
    32.        (float3(0.0f, chroma, component) * ((2.0f < hue) * (hue <= 3.0f))) +
    33.        (float3(0.0f, component, chroma) * ((3.0f < hue) * (hue <= 4.0f))) +
    34.        (float3(component, 0.0f, chroma) * ((4.0f < hue) * (hue <= 5.0f))) +
    35.        (float3(chroma, 0.0f, component) * ((5.0f < hue) * (hue <= 6.0f)));
    36.  
    37.    float minColor = HSL.z - (0.5f * chroma);
    38.  
    39.    return sRGB + minColor;
    40. }
    41.  
    Here's the code for the fragment shader I'm using to test them out on:
    Code (csharp):
    1.  
    2. float4 frag (v2f i) : SV_Target
    3. {
    4.    float2 dir = normalize(i.worldPos.xz);
    5.    float3 sRGB = HSVtosRGB(float3(degrees(atan2(dir.y, dir.x)), length(i.worldPos.xz), i.worldPos.y));
    6.  
    7.    return float4(sRGB, 1.0f);
    8. }
    9.  
    Here are pictures of what I'm getting for HSV and HSL respectively:

    HSV_normal.png HSL_normal.png

    The hue for red, green, and blue seem to take up a very small area while cyan, magenta, and yellow seem to take up an unusually large area. The white and black areas also seem to transition to suddenly. Again, the results do seem to look okay when actually using the functions for their intended purpose, so I'm wondering if perhaps my math is just wrong in some way for actually trying to visualize the color solids.

    Weirdly, the color solids look much more like what I'd imagine they should look like if I gamma correct the resulting RGB value.

    pow(sRGB, 2.2f);

    Here's what they look like when I gamma correct them:
    HSV_pow.png
    HSL_pow.png

    I have no idea what the apparent problem is or why Gamma correction even seems to be involved (the only reason I tried it was because that looked like it was the problem).
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Because an sRGB to linear conversion is involved. If you're rendering using the linear color space (which I believe is now the default?) all of the math being done in the shader is in "linear space", which is fine, but the output from those is expected to be in sRGB space, not in linear.

    The trick you're using of pow(col, 2.2) is a common approximation of sRGB, though not quite right. You should use GammaToLinearSpace() instead of the pow.
     
  3. TommyPoulin

    TommyPoulin

    Joined:
    Feb 5, 2017
    Posts:
    14
    Oh, that makes much more sense. Also, thanks for pointing out the GammaToLinearSpace() function.

    Now just to make sure I've got this straight, what I want to do when I'm actually using these functions (for best results) is:

    1. Convert from linear to gamma space
    2. Convert from sRGB to HSV/HSL
    3. Manipulate the values however I want
    4. Convert from HSV/HSL to sRGB
    5. Convert from gamma to linear space
     
    bgolus likes this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    That's correct.
     
  5. TommyPoulin

    TommyPoulin

    Joined:
    Feb 5, 2017
    Posts:
    14
    Thank you! :D