Search Unity

Strange bug/behaviour in color channels value access in fragment shader - nonlinear channel value

Discussion in 'Shaders' started by ogavr, May 15, 2019.

  1. ogavr

    ogavr

    Joined:
    Mar 26, 2018
    Posts:
    5
    Hi, all
    I have strange/broken behaviour in simple color channel acces in shader in project.
    For some reason color channel access is not linear.
    I had complex shader but pinpointed the problem to this.
    I use worldPos.y for a debug and I see that accessing _MyColor.r gives somehow interpolated values. For example channel.r (originally 0.1) results in 0.01 Y position. 0.2 in 0.034 position etc.
    At the same time 0 gives 0 and 1 gives 1. So it is somehow scaled.
    Shader is just default unlit shader with additional property _MyColor{.....}=....
    Access is performed in "frag" part by simple calling of worldPos.y = _MyColor.r. All the to world and from world vertex calls are in place (see code below).
    At the same time shader performs as usually (0.5 channel value gives 0.5 meters y) in new blank project.
    I use small set of additional assets: MapMagic, Microsplat, SteamVR.
    I can assume it is because of some of these assets (because there is no such issues in blank project) but I don't get how it can happen at all. It's shaders and it must not be dependent.
    Maybe there is some settings problems but I had never heard of any such settings.
    I would be really grateful for any suggestions.
    At the same time I would try to reimport assets step by step and verify it is not it's fault. At the ssame time I don't get mechanism behind such behaviour.
    Shader code
    Code (CSharp):
    1. Shader "Unlit/Test"
    2. {
    3.     Properties
    4.     {
    5.         _MyColor("Color", Color) = (0.5,1,1,1)
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             #include "UnityCG.cginc"
    18.  
    19.             struct appdata
    20.             {
    21.                 float4 vertex : POSITION;
    22.             };
    23.  
    24.             struct v2f
    25.             {
    26.                 float4 vertex : SV_POSITION;
    27.                 float4 color : COLOR;
    28.             };
    29.  
    30.             float4 _MyColor;
    31.  
    32.             v2f vert (appdata v)
    33.             {
    34.                 v2f o;
    35.                 float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
    36.                 worldPos.y = _MyColor.r;
    37.                 v.vertex = mul(unity_WorldToObject, worldPos);
    38.                 o.vertex = UnityObjectToClipPos(v.vertex);
    39.                 o.color = _MyColor;
    40.                 return o;
    41.             }
    42.  
    43.             fixed4 frag (v2f i) : SV_Target
    44.             {
    45.              
    46.                 return i.color;
    47.             }
    48.             ENDCG
    49.         }
    50.     }
    51. }
    52.  
     
  2. ogavr

    ogavr

    Joined:
    Mar 26, 2018
    Posts:
    5
    It's SteamVR plugin.
    Shader becomes broken after import of SteamVR from asset store.
    I have no even slightest idea how it can happen.
    Even theoretically.
    Any suggestions?
    I would issue a bug in SteamVR Github but still mechanism is beyond my understanding.
     
    Last edited: May 15, 2019
  3. ogavr

    ogavr

    Joined:
    Mar 26, 2018
    Posts:
    5
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    How are you validating the shader data? Are you going off of the on screen color value? If so, then there's no bug here with the interpolation. SteamVR is likely changing the project to use linear color space. When you're using linear color space, then a value of "0.5" will show up as 188/255, or 0.75 because the linear value is being displayed as an sRGB color, so there's a conversion from linear to sRGB.

    If you want the color value to match your expectations, you'll need to apply a conversion in the shader to counter the conversion the GPU will eventually do when displaying the image.
    Code (csharp):
    1. #if !defined(UNITY_COLORSPACE_GAMMA)
    2. color.rgb = GammaToLinearSpace(color.rgb);
    3. #endif
    Note: you should only do this to linear values like world position or UVs. This might seem backwards that you're applying a "gamma to linear" to a linear value, but you want the on screen color value to be representative of the color value; if you want the linear value "0.5" to be "0.5" in sRGB space you have to pretend it is a "gamma space" color value. You shouldn't do this to color values you've set on the material.
    Though you may want to do it to vertex colors from the mesh since Unity doesn't handle color coversion on those, but that's a different topic.

    Also, you don't need to do this. If you're not modifying the worldPos, just use the original v.vertex, you don't need to transform the unmodified worldPos value back to object space.

    If you are modifying the worldPos use:
    o.vertex = mul(UNITY_MATRIX_VP, float4(worldPos.xyz, 1));
     
    ogavr likes this.
  5. ogavr

    ogavr

    Joined:
    Mar 26, 2018
    Posts:
    5
    Awesome answer.
    I verified and it is colorspace indeed.
    I was not aware of such mechanism (I am not into shaders that deep).
    Also I found Your posts from 2012 where You described its mechanisms.
    So I assume that when I set any kind of Color (via Color Picker or sRGB texture) it is converted to Linear in shader (on shader input). So when I get channel in Shader and it returns 0.2 (while having 0.5 in color picker) it is because it was recalculated to linear on input to shader. And it is real linear value.
    So my problem is that I form my input as Gamma and await it to be the same in shader while it is recalculated to linear.
    So to get back value that was encoded in gamma channel I had to use LinearToGammaSpace?
    It is for the case if I want to retrieve channel data (to modify position or uvs for example).
    Am I right?
    So simple fast fix (for current situation) is (if I get it right) to check if colospace is not gamma and convert linear color value to gamma to get appropriate color channel value (that was encoded in gamma color or texture), I updated code in dummy shader to:
    Code (CSharp):
    1. worldPos.y = _MyColor.r * 10;
    2.  #if !defined(UNITY_COLORSPACE_GAMMA)
    3.  worldPos.y = LinearToGammaSpace(_MyColor.rgb).r * 10;
    4.  #endif
    And it now works well. Note that I use opposite method then You specified.
    Is it correct?

    As of worldPos modification - I do modification, I use textures as displacement maps in world space.

    As of UNITY_MATRIX_VP usage - right it would give the one line conversation.
    I just stick to it as it gives me more concise way of writing. (ConvertOToW -> ConvertWtoO -> ConvertToClipPos). I would consider using one line. Thank You.
     
    Last edited: May 16, 2019
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    If it looks right, then sure. I always get the LinearToGamma / GammaToLinear function use wrong when it comes to debugging stuff. ;)
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    "ConvertToClipPos" internally is "CovertOToW -> ConvertWToClip"
     
  8. ogavr

    ogavr

    Joined:
    Mar 26, 2018
    Posts:
    5
    *Sigh*, it seems that in shaders it's not that easy :)
    If it seems right - it still is not guaranteed to be it. (without colorspace understanding it seemed right too :) )
    So my understanding of situation is correct?

    Whoa, thanks, I was not looking into it. It makes sense then.
    I really appreciate Your help.