Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

50% grey differences in shader

Discussion in 'Shaders' started by ArchaeopteryxRoboParty, Jan 27, 2016.

  1. ArchaeopteryxRoboParty

    ArchaeopteryxRoboParty

    Joined:
    Sep 15, 2014
    Posts:
    10
    Hi there,

    I'm struggling to figure out what is hopefully a simple a problem and would appreciate any pointers.

    Using the default Unlit/Color shader with an RGB color of (127, 127, 127) gives 50% grey. No problems there.

    In a custom fragment shader, I'm expecting fixed4(0.5, 0.5, 0.5, 1) to produce 50% grey.

    Instead, I'm getting an RGB value of (188, 188, 188) (using the color picker in Photoshop to check).
    I feel like there's some fundamental thing I'm missing?

    Unlit/Color on the left, custom fragment shader returning 0.5 on the right:



    Shader code:
    Code (CSharp):
    1. Shader "Custom/Grey" {
    2.     Properties {}
    3.     SubShader
    4.     {
    5.         Tags { "RenderType" = "Opaque" }
    6.         Pass
    7.         {
    8.             CGPROGRAM
    9.             #pragma vertex vert
    10.             #pragma fragment frag
    11.             #pragma target 3.0
    12.  
    13.             struct appdata
    14.             {
    15.                 float4 vertex : POSITION;
    16.                 float2 uv : TEXCOORD0;
    17.             };
    18.  
    19.             struct v2f
    20.             {
    21.                 float2 uv : TEXCOORD0;
    22.                 float4 vertex : SV_POSITION;
    23.             };
    24.  
    25.             v2f vert(appdata v)
    26.             {
    27.                 v2f o;
    28.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    29.                 o.uv = v.uv;
    30.                 return o;
    31.             }
    32.  
    33.             fixed4 frag(v2f i) : SV_Target
    34.             {
    35.                 return fixed4(0.5, 0.5, 0.5, 1);
    36.             }
    37.  
    38.             ENDCG
    39.         }
    40.     }
    41. }

    Thanks!
     
  2. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    Gamma is your problem here. You can try to change color space to gamma and it will work as expected (Player settings->Rendering->Color Space).
    If you have color space set to linear, then all color inputs are expected to be in gamma space. Unity will automatically remove gamma from them before shader starts. All computations are done in linear space. And finally, gamma is applied to the result when it is written to the render target. So in your example, you set Unlit Color shader to (127, 127, 127), Unity will remove gamma before shader starts (56, 56, 56), gamma is applied again when the shader is done (127, 127, 127).
    Your shader simply returns (127, 127, 127). Gamma is applied to that and you'll get (187, 187, 187). You can either set the color through material just like the Unlit Color shader (Unity will handle gamma for you), or you can handle gamma in the shader yourself:
    Code (CSharp):
    1. return pow(fixed4(0.5, 0.5, 0.5, 1), 2.2);
    Edit: You should apply gamma correction to rgb channels only (unlike I did in the code snippet). Alpha should be always linear.
     
    Last edited: Jan 27, 2016
  3. ArchaeopteryxRoboParty

    ArchaeopteryxRoboParty

    Joined:
    Sep 15, 2014
    Posts:
    10
    Ah thank you very much! Definitely a fundamental thing I missed.
    I was porting procedural textures from CPU to GPU and noticed the GPU versions were all brighter. It'd be great to make the shaders project agnostic if possible - is there any way you could recommend of dealing with potential different color spaces per project?
    The first thing that springs to mind is checking PlayingSettings.colorSpace and toggling accordingly, though that seems a bit cludgy?
    Many thanks :)
     
  4. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    Gamma correction can be a real pain but Unity gives you tools to avoid most of the problems. It is possible to have one shader for both spaces. It really affects only 2 things. Color constants and color textures. Everything else is always linear.

    1. Color constants
    Avoid declaring color constants directly in hlsl:
    Code (CSharp):
    1. float4 color = float4(1, 1, 1, 1);
    Declare them as properties instead and Unity will handle gamma for you. That's why Unlit Color shader works in both spaces
    Code (CSharp):
    1. Properties
    2. {
    3.     _Color ("Color", Color) = (1,1,1,1)
    4. }
    2. Color textures
    You have to tell the run time if given texture is encoded in gamma space. Color textures are in gamma and non-color textures are in linear space. Texture importer has a checkbox "Bypass sRGB Sampling". Check it for non-color textures and uncheck for color textures.
    If you're creating textures at run-time, then Texture2D constructor accepts "bool linear" parameter.

    If you do this right then your shader will work in both spaces. Just like builtin shaders do. Of course, you're not going to get identical results depending on the calculations you're doing in the shader. But that's kind of the whole point of linear vs gamma. Linear is correct and gamma is "good enough".

    If you truly need identical results in both spaces, then you probably want to use "shader_feature", handle gamma on your own and switch between shader variants based on PlayerSettings.colorSpace. But that doesn't really make sense to me, just use the same space if you want the same result.
     
  5. ArchaeopteryxRoboParty

    ArchaeopteryxRoboParty

    Joined:
    Sep 15, 2014
    Posts:
    10
    That's great info, much appreciated!

    The texture generation is done entirely in the fragment shader based on uvs (noise), so there's no Color or Texture2D ShaderLab properties - now makes sense why Unity isn't handling it and why the Unlit shader works as it does!
    "shader_feature" also did the trick - I'll muse on whether I'm over-complicating things but it's handy to have the code and I've definitely learnt a few things in the process :)

    Thanks again!

    EDIT:
    After doing a little more digging, it turns out there are handful of useful shader functions in UnityCG.inc:
    IsGammaSpace()
    GammaToLinearSpaceExact (float value)
    GammaToLinearSpace (half3 sRGB)
    LinearToGammaSpaceExact (float value)
    LinearToGammaSpace (half3 linRGB)

    IsGammaSpace() works great and the shader works in both color spaces, though I guess having a conditional might not perform as well as toggling keywords with shader_feature
     
    Last edited: Jan 28, 2016
    Michal_ likes this.
  6. konglan8810

    konglan8810

    Joined:
    Apr 29, 2015
    Posts:
    2
    Informative post. But another thing I'm very confused. I feed in a (188,188,188) sRGB texture to diffuse material and give 0.5 intensity light. The result should be (136,136,136) sRGB (188sRGB->127Linear 127*0.5=64Linear=136sRGB). But Unity gives me (94,94,94) sRGB. That is not correct, anyone knows why?
     
  7. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    Your math is correct. It looks to me like you have the rendering color space set to gamma. 188 sRGB * 0.5 = 94 sRGB. In fact, I tried it in linear space and I got the correct result (136 sRGB). Or perhaps you're doing something different in the shader then simply "return tex2D(...)*0.5"?

    Btw. here and here is more in-depth explanation of gamma correction. In case somebody wants to understand why we have to deal with this problematic gamma in the first place: