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

Bug In shader, 2.0 * (127, 127, 127) color does not give white color

Discussion in 'Shaders' started by DankalApps, May 30, 2023.

  1. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    (inb4: I know 254, 254, 254 is not exactly white)

    Created simple shader and wanted add bump map, but calculations on it gave strange results.

    Recreated those issues using simpler version. For some reason simple multiplying in the shader is not working as expected.
    In the shader code below there are 5 tests (a,b,c,d,e) I performed (as an input I provide bump map texture with r/x component equal to 127).

    Code (CSharp):
    1. Shader "Custom/SimpleUnlitBump"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("_MainTex", 2D) = "white" {}
    6.         _BumpMap ("_BumpMap", 2D) = "bump" {}
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Transparent"
    11.                "Queue" = "Transparent"}
    12.         Blend SrcAlpha OneMinusSrcAlpha
    13.         Zwrite Off
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma alpha
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2f
    30.             {
    31.                 float2 uv : TEXCOORD0;
    32.                 float4 vertex : SV_POSITION;
    33.             };
    34.          
    35.             sampler2D _MainTex;
    36.             sampler2D _BumpMap;
    37.             float4 _MainTex_ST;
    38.  
    39.             v2f vert (appdata v)
    40.             {
    41.                 v2f o;
    42.                 o.vertex = UnityObjectToClipPos(v.vertex);
    43.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    44.                 return o;
    45.             }
    46.  
    47.             fixed4 frag (v2f i) : SV_Target
    48.             {              
    49.                  float4 col = tex2D(_MainTex, i.uv);
    50.                  // a)  results in texture with  (127, 127, 127) grey color, as expected --> OK
    51.                  col.xyz = 1.0 *  (tex2D(_BumpMap, i.uv).r);
    52.                  // b) darker, but not even close to (64, 64, 64) --> BUG
    53.                  col.xyz = 0.5 *  (tex2D(_BumpMap, i.uv).r);
    54.                  // c) brighter, but far far from white --> BUG
    55.                  col.xyz = 2.0 *  (tex2D(_BumpMap, i.uv).r);
    56.                  // d) still not close to white --> BUG
    57.                  col.xyz = 3.0 *  (tex2D(_BumpMap, i.uv).r);
    58.                  // e) multiplying by value around 4.65 finally gives white --> WHY??
    59.                  col.xyz = 4.65 *  (tex2D(_BumpMap, i.uv).r);
    60.                  return col;
    61.              }
    62.              ENDCG
    63.         }
    64.     }
    65. }
    66.  
    I believe it is bug, but still new to shaders so I hope you help me verify its my bug somewhere, not Unity's.

    (in case it is important: working on 2021.3.26f1)
     
    Last edited: May 30, 2023
  2. samy99

    samy99

    Joined:
    Mar 4, 2020
    Posts:
    7
    1.Do you enable any post-processing effect ? it may change the final color out on screen.
    2.Is the _BumpMap a sRGB texture or a Normal texture on linear space ? and your project setting with a Linear space or Gamma spcae?
     
  3. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    @samy99 Ad1. No. I plan to use anti-aliasing, but it was off during tests.
    Ad2. Bump map in inspector is (NPOT) RGBA8 sRGB.
    Ad2b. If you mean Color space setting in Player->OtherSettings->Rendering, it is set to Linear (and it is greyed out so can't even change it)
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    No bug. Linear space rendering can be very confusing.

    If you take a middle grey color texture and render on an unlit object, it’ll appear to be the same grey.

    If you take that middle grey texture and multiply it by 2 in the shader … it’ll still appear grey. And that’s correct. Because middle grey in linear color space isn’t 0.5, it’s about 0.214. And because 127/255 isn’t 0.5, it’s 0.498, so the middle grey texture will be around 0.212 in the shader. 1.0/4.65 = 0.215 so pretty close, which is why multiplying by that values gets white.

    But why? Because color values get transformed from sRGB space to linear color space. And then once rendering is done, the linear values are converted back into sRGB for display.

    https://en.m.wikipedia.org/wiki/SRGB

    Here’s a Google sheets template for converting between sRGB and linear color values if you want to play with it.
    https://docs.google.com/spreadsheets/d/1zpvPyV1kQDjgH0_59p4b5QlPZxVuyh1rKSGRZqOsaY8/template/preview

    The 0-255 color values we’re used to dealing with are in sRGB color space. These are values that are roughly perceptually linear, so a value of 127/255 looks half as bright as 255/255, but human vision isn’t actually linear in terms of the amount of light entering the eyes. Linear rendering is trying to replicate the way light actually works.
     
    DankalApps likes this.
  5. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    Many thanks!

    Yes, it is confusing..

    So, to sum up, if I get this right:
    1) In C# I create middle grey Texture 2D (127, 127, 127), with RGBA32 format
    2) I add this to the material (
    Code (CSharp):
    1. Renderer.material.SetTexture("_BumpMap", normalMapTexture)
    )
    3) Unity converts the texture non-linearly to linear space, where middle grey is no longer "middle" grey, but it gets shifted (to about 0.21).
    4) This converted texture is given as an input to shader.
    5) In shader, I do multiplication in that new space, 0.21 * 2 = 0.42, thus not getting even close to 1.0 (white color)
    6) (probably) somewhere later, result of the shader frag, gets transformed back to the "standard" space and gets displayed on the monitor

    Great thanks for the spreadsheet. It will help me understand what exact values are inside the shader and if my normal calculations (rotations) are correct.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,255
    First, Unity isn’t doing this. The GPU does that work. The data in the texture stays “127, 127, 127” at all times. But textures can be specified as being either linear or sRGB data, and the GPU does the conversion in hardware when sampling the texture if it’s set to sRGB.

    The
    Texture2D()
    constructor has an important option that’s shown in the documentation, but not commented on.
    Code (csharp):
    1. public Texture2D(int width, int height, TextureFormat textureFormat = TextureFormat.RGBA32, int mipCount= -1, bool linear = false);
    https://docs.unity3d.com/ScriptReference/Texture2D-ctor.html

    That last option
    bool linear = false
    means the texture is created as an sRGB texture. If you pass in a true when creating the texture the GPU will not do anything to the texture data when sampling it.

    It happens after every object in the scene has been rendered, as an invisible post process of sorts.
     
    DankalApps likes this.
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    You should not be using sRGB for normal/bump maps!

    If you have a bump map (grayscale height) which you want to use as a normal map, you can set that up for Unity to import it by checking the "create from grayscale" option:



    Normal maps are compressed in a certain way depending on platform (typically, BC5) that will retain detail better than standard compression techniques. The data is stored in a "linear" manner (not sRGB), but instead of between 0 and 1, you usually want to extract 2 values between -1 and 1, then compute the third value. This is all done internally (the function to use in BIRP is
    UnpackNormal(tex2D(_BumpMap, i.uv))
    ).
     
    Last edited: Jun 2, 2023
  8. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    @bgolus thank you for clarifications! I used this linear=true option and now my normal calculations work as charm.

    @burningmime I generate both texture and normal map in script.
    Code (CSharp):
    1.  
    2. normalMapTexture = new Texture2D(width, height, TextureFormat.RGBA32, false, true);
    3. normalMapTexture.SetPixels32(normalMapPixels);
    4.  
    I pack normals to 0-255 range, then in shader I use simple
    float3 normal = tex2D(_BumpMap, i.uv).xyz * 2 - 1;
    formula. I guess I could do it more efficiently, using something different than RGBA32.