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

Question Getting exact r value from Tex2D call on Sampler2D

Discussion in 'Shaders' started by gtaogle, Aug 27, 2023.

  1. gtaogle

    gtaogle

    Joined:
    Jan 6, 2022
    Posts:
    18
    Question: how do I get the exact 'red' value on a sampler2d at a given uv? I've been successful so far in picking exact pixels from an index texture, but none of the values in the texture that I'm trying to replace colors in (in the shader) seem to have reasonable r values.

    My expectation: 0,0,0 and 255,255,255 : at least somewhere in the fragment should have the color 0,0,0,1 and 1,1,1,1 respectively. The call I use is this:

    Code (CSharp):
    1. fixed4 orig = tex2D (_MainTex, IN.uv_MainTex);
    however, both black and white end up with the value of 0. To be specific, my index is 256 colors, they end up with a value, presumably, between -0.5/256 and 0.5/256 (testing shows it's closer to -0.9, 0.9. And in general in the source image any pixels whose colors are within a few bits end up with the same value. This might be because of interpolation (but both the source (sprite) texture and the index texture are point filtered, and so have no anti-aliasing/blending/bilinear filtering that might cause unexpected intermediary values.)

    What is the *actual* expected range of r,g,b,a coming out of tex2D? I have written color-index-swapping shaders before in an earlier version of GLSL, and had no problem figuring out the difference between 1,0,0,255 and 0,0,0,255, much less between 255,0,0,255 and 0,0,0,255 in the shader code - and in the unity shader I can manually override this call

    Code (CSharp):
    1. fixed4 swapCol = tex2D (_SwapTex, float2(orig.r,0));
    replacing orig.r with an arbitrary value such as 160.0/256.0 and all non-transparent pixels get replaced with the color at 160,0 in the index texture. This indicates that very certainly the r-value of 160 doesn't come out of tex2D as 160.0/256.0 - or even 160.0/255.0 - minor adjustments to the resulting r-value don't get a pixel with an r-value of 160 to be replaced with the color at 160 regardless of finagling, which indicates that the output values of r,g,b are not at all in the range 0,1 corresponding to input ranges of 0,255.

    So what are they? Or a better question, is there a good way to find out what these values really are, if for some reason some directives are altering the result of the tex2D call in a non-uniform way (i.e, if lighting is somehow being applied *before* I sample the texture, as could be the case in my attempts to make this work in a surface shader.)

    If this is the case, I would wonder if I can't simply grab the unaltered color from _MainTex? I had thought about adding an "index" texture as a third texture, but I haven't figured out how to get that settable through the Sprite Renderer, so it would just be a workflow burden and a source of bugs.

    EDIT: just as an update and to confirm I'm not nuts, I did some work in the shader to output color based on input values, and this is the insane pattern you get


    Code (CSharp):
    1.             float realIndex = orig.r*(255.0) + 0.5;
    2.             // value 64 = 14-15
    3.             // value 128 = 55-56
    4.             // value 160 = 85-86
    5.             // value 192 = 128-129
    6.             // value 216 = 171-172
    7.             // value 255 = 255-256
    So if I put an r-value of 64 in the input image, that actually corresponds to a new r-value of 14-15. Thus there's no way to map colors even if you take unity's default sprite shader and modify its frag function to sample the color and replace based on the r-value as suggested by this tutorial:

    https://gamedevelopment.tutsplus.co...dynamically-swap-a-sprites-colors--cms-25129t

    naturally, the sprite shader he based his work on is no longer available, so I have no idea if his project even works now.

    Many such cases...

    Second Edit:

    I attached a file showing real results following the tutorials, the source image I generated to repaint the texture, the original image is on the left. The right hand image shows what happens if you iterate through all of the pixels in the texture and repaint them based on r-value manually (this method doesn't scale acceptably) - the middle image is showing Unity's shaders being broken, it seems, in general.

    Final Edit:

    It seems that a possible solution is to convert 'back' to gamma space. I do not know how precise it is, but there were two things tutorials did not mention, perhaps because defaults have changed since they did their work
    1. Linear space being active versus gamma space changes things
    2. If you are loading pixel textures, you must manually disable compression for them as it will ruin subtle color variations. This is probably intended for larger, photograph like textures.

    It seems likely that although I am already in linear space, the shader code is still trying to convert my color space into Linear space, creating a distorted color. By converting 'back' to gamma space, I actually get into (what seems to me, not an expert in computer graphics terminology) linear space.


    Code (CSharp):
    1. fixed4 orig = SampleSpriteTexture (IN.texcoord);
    2. float3 l_col = LinearToGammaSpace(orig);
    3. fixed4 swapCol = tex2D (_SwapTex, float2((l_col.r)+(0.5*_SwapTex_TexelSize.x),0));
    4. fixed4 c = lerp(orig, swapCol, swapCol.a) * IN.color;          
    5. c.a = orig.a;
    6. c.rgb *= c.a;
    7. return c;
    This is the (at least mostly) working fragment code. l_col.r does actually contain the properly bounded value, at least approximately. I don't have the time to submit a bug, but I think when you are in Linear space it is additionally converting you into linear space as if you were already in gamma space. My version is 2022.1.23f1.

    If you read this far, leave a comment on whether I'm correct or not, since it's hard to find feedback on this topic.

    Cheers!
     

    Attached Files:

    Last edited: Aug 28, 2023
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Here's the problem you're having.
    upload_2023-8-28_11-29-13.png

    Disable sRGB on the texture's import settings.
     
  3. gtaogle

    gtaogle

    Joined:
    Jan 6, 2022
    Posts:
    18
    That was one thing I tried, it made the result much worse...
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    What application are you using to make the original sprites, and what format are you saving them as?

    Unity's shaders are not broken. They're not even really "Unity's" shaders as the code is running on the GPU separate from Unity, and does only what the code says to do. If the color values are incorrect when sRGB is disabled on the import settings, it means the tool you're using to author that texture is applying a color correction or color profile on the texture when it's saved. Unity is fairly dumb when it comes to loading textures, though recently it did add some support for applying color profiles to PNG images.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Code (csharp):
    1. Shader "SwatchTest"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    6.         [NoScaleOffset] _SwatchTex ("Swatch", 2D) = "white" {}
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    11.         LOD 100
    12.         Pass
    13.         {
    14.             Blend SrcAlpha OneMinusSrcAlpha
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #include "UnityCG.cginc"
    19.             struct appdata
    20.             {
    21.                 float4 vertex : POSITION;
    22.                 float2 uv : TEXCOORD0;
    23.             };
    24.             struct v2f
    25.             {
    26.                 float2 uv : TEXCOORD0;
    27.                 float4 vertex : SV_POSITION;
    28.             };
    29.             sampler2D _MainTex;
    30.             sampler2D _SwatchTex;
    31.             v2f vert (appdata v)
    32.             {
    33.                 v2f o;
    34.                 o.vertex = UnityObjectToClipPos(v.vertex);
    35.                 o.uv = v.uv;
    36.                 return o;
    37.             }
    38.             fixed4 frag (v2f i) : SV_Target
    39.             {
    40.                 fixed4 orig = tex2D(_MainTex, i.uv);
    41.                 fixed4 col = tex2D(_SwatchTex, float2(orig.r, 0.0));
    42.                 col.a = orig.a;
    43.                 return col;
    44.             }
    45.             ENDCG
    46.         }
    47.     }
    48. }
    Created the red channel texture in photoshop, using your screenshot as the swatch for the values (which do match your description). Saved with photoshop as a PNG using SuperPNG, because Photoshop's built in PNG saving kinda sucks.
    upload_2023-8-28_14-31-34.png

    Red texture is set to no compression, sRGB off.

    I also probably have my Photoshop setup to use different color profiles though, which can affect how the default PNG plugin will save it.
     

    Attached Files:

  6. gtaogle

    gtaogle

    Joined:
    Jan 6, 2022
    Posts:
    18
    Dunno, couldn't get that to work. Had to do LinearToGammaSpace on the result to get it correct.

    Cheers!

    (Also, there's no need to respond to the post, I answered my question.)