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.

Question BilinearSample on Atlas texture

Discussion in 'Shaders' started by Danil0v3s, Nov 18, 2022.

  1. Danil0v3s

    Danil0v3s

    Joined:
    Sep 22, 2014
    Posts:
    30
    I've been trying to convert a function that works for a single sprite to use a sprite atlas instead. I've searched for a bit and it seems that I must convert the UV from the atlas perspective to the sprite perspective. I tried a few solutions but I cannot for the life of me get this working.

    This is how I'm sending data to the shader

    Code (CSharp):
    1.  
    2. var layer = frame.layers[0];
    3. var sprite = Sprites[layer.index];
    4. var rect = Rects[layer.index];
    5.  
    6. MeshRenderer.material.SetVector("_uTextSize", new Vector2(sprite.rect.width, sprite.rect.height));
    7. MeshRenderer.material.SetVector("_Rect", new Vector4(rect.x, rect.y, rect.width, rect.height));
    8.  
    And this is the shader function where it uses the UV
    Code (Boo):
    1. fixed4 bilinearSample(sampler2D indexT, sampler2D LUT, float2 uv) {
    2.     float2 TextInterval = 1.0 / _uTextSize;
    3.  
    4.     float tlLUT = tex2D(indexT, uv).x;
    5.     float trLUT = tex2D(indexT, uv + float2(TextInterval.x, 0.0)).x;
    6.     float blLUT = tex2D(indexT, uv + float2(0.0, TextInterval.y)).x;
    7.     float brLUT = tex2D(indexT, uv + TextInterval).x;
    8.  
    9.     float4 transparent = float4(0.5, 0.5, 0.5, 0.0);
    10.  
    11.     float4 tl = tlLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(tlLUT, 1.0)).rgb, 1.0);
    12.     float4 tr = trLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(trLUT, 1.0)).rgb, 1.0);
    13.     float4 bl = blLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(blLUT, 1.0)).rgb, 1.0);
    14.     float4 br = brLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(brLUT, 1.0)).rgb, 1.0);
    15.  
    16.     float2 f = frac(uv.xy * _uTextSize);
    17.     float4 tA = lerp(tl, tr, f.x);
    18.     float4 tB = lerp(bl, br, f.x);
    19.  
    20.     return lerp(tA, tB, f.y);
    21. }
    This is the end result I'm trying to achieve (single sprite) vs what I'm currently getting (atlas)
    upload_2022-11-18_20-17-8.png

    And here is what I've tried so far:
    Code (Boo):
    1.                 half2 uvs = i.uv;
    2.                 uvs.x = (uvs.x * _MainTex_TexelSize.z - _Rect.x) / _Rect.z;
    3.                 uvs.y = (uvs.y * _MainTex_TexelSize.w - _Rect.y) / _Rect.w;
    4.                 fixed4 tex = bilinearSample(_MainTex, _PaletteTex, uvs);
    Code (CSharp):
    1.                 float2 localuv = (i.uv - _Rect.xy) / (_Rect.zw - _Rect.xy);
    2.                 fixed4 tex = bilinearSample(_MainTex, _PaletteTex, localuv);
     
  2. Danil0v3s

    Danil0v3s

    Joined:
    Sep 22, 2014
    Posts:
    30
    Got it working by sending the size of the atlas texture instead of the sprite to the shader.
    Instead of
    Code (CSharp):
    1. var layer = frame.layers[0];
    2. var sprite = Sprites[layer.index];
    3. var rect = Rects[layer.index];
    4. MeshRenderer.material.SetVector("_uTextSize", new Vector2(sprite.rect.width, sprite.rect.height));
    5. MeshRenderer.material.SetVector("_Rect", new Vector4(rect.x, rect.y, rect.width, rect.height));
    I now do
    Code (CSharp):
    1. var materialTexture = MeshRenderer.material.mainTexture;
    2. if (materialTexture != null) {
    3.     MeshRenderer.material.SetVector("_uTextSize", new Vector2(materialTexture.width, materialTexture.height));
    4. }
    5.  
    And it works
    upload_2022-11-19_12-46-0.png
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    11,926
    Unity does that already for you btw.

    float4 _TextureName_TexelSize.zw
    is the texture resolution.
     
  4. Danil0v3s

    Danil0v3s

    Joined:
    Sep 22, 2014
    Posts:
    30
    Hey! I tried using that but for some reason it didn't work, maybe I wans't passing the correct UV, not in front of the code now to test but I remember I tried debugging the texel size by setting it to a property and it was always 0
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    11,926
    If you set it as a property, it’ll prevent Unity from assigning it automatically.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    11,926
    You'll probably want to change your sample function to something like this:
    Code (CSharp):
    1. fixed4 (sampler2D indexT, float4 indexT_TexelSize, sampler2D LUT, float2 uv) {
    2.     float2 TextInterval = indexT_TexelSize.xy;
    3.     float tlLUT = tex2D(indexT, uv).x;
    4.     float trLUT = tex2D(indexT, uv + float2(TextInterval.x, 0.0)).x;
    5.     float blLUT = tex2D(indexT, uv + float2(0.0, TextInterval.y)).x;
    6.     float brLUT = tex2D(indexT, uv + TextInterval).x;
    7.     float4 transparent = float4(0.5, 0.5, 0.5, 0.0);
    8.     float4 tl = tlLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(tlLUT, 1.0)).rgb, 1.0);
    9.     float4 tr = trLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(trLUT, 1.0)).rgb, 1.0);
    10.     float4 bl = blLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(blLUT, 1.0)).rgb, 1.0);
    11.     float4 br = brLUT == 0.0 ? transparent : float4(tex2D(LUT, float2(brLUT, 1.0)).rgb, 1.0);
    12.     float2 f = frac(uv.xy * indexT_TexelSize.zw);
    13.     float4 tA = lerp(tl, tr, f.x);
    14.     float4 tB = lerp(bl, br, f.x);
    15.     return lerp(tA, tB, f.y);
    16. }
    And then call it like this:
    Code (CSharp):
    1. bilinearSample(_MainTex, _MainTex_TexelSize, _PaletteTex, i.uv);
    You want
    float4 _MainTex_TexelSize;
    in your shader code just like it would be if it was a material property, but you do not want it as a material property as then whatever is set on the material is what it'll use, not what Unity would automatically set that value to. Unity will not automatically override material properties.
     
  7. Danil0v3s

    Danil0v3s

    Joined:
    Sep 22, 2014
    Posts:
    30
    Your suggestion worked as usual. Had to change
    float2 TextInterval = indexT_TexelSize.xy;
    to
    float2 TextInterval = indexT_TexelSize.zw;


    I was trying to create a property with a different name like
    _Test("aaa", Vector) = (0,0,0,0)
    then I was setting its value to the one from _MainTex_TexelSize but it was always (0,0,0,0).
     
    bgolus likes this.
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    11,926
    The value you’re setting in the shader is several levels removed from the value you see in the material. Nothing you do in the shader can change the material’s properties you see in the editor, because it’s all intentionally a one direction data flow and temporary values.

    The material property exists as data in an asset on the CPU side.
    Those properties, along with others set by the engine, are gathered together for each mesh that is drawn. At this point any connection to the material & material property is lost (outside of some kept around for debugging).
    That data is packed up and sent from the CPU to the GPU when the mesh is drawn.
    When the GPU draws the mesh, each vertex and then each fragment, gets a unique copy of that data to work from. Any modifications to that data is kept as a local variable that is tossed out when the shader function finishes for each vertex or fragment. The only information retained is that which is output by each shader stage; usually only the vertex’s clip space position, UVs, normal, etc. is retained and passed along to the fragment, and the fragment outputs the color you see on screen, and that’s it.

    No data comes back to the CPU from the GPU, so the material property won’t … can’t … be modified by the shader.
     
    Danil0v3s likes this.
  9. Danil0v3s

    Danil0v3s

    Joined:
    Sep 22, 2014
    Posts:
    30
    Thank you for the explanation, that was enlightening!