Search Unity

"Relative" texcoord when using sprite atlas

Discussion in 'Shaders' started by gigapunk, Apr 26, 2016.

  1. gigapunk

    gigapunk

    Joined:
    Mar 30, 2013
    Posts:
    7
    Hello,

    I'm in a situation where I need to know the "relative" position inside the sprite. Evertything works fine and easy when using single sprites, since I can simply refer to UV coordinates, but when I start to use sprite atlasing everything breaks up, because the texcoords contains the coordinates to the whole spritesheet.

    Another thing that is giving me the headache is this: I was able to resolve the problem above by passing the size of the sprite to the shader like this

    Code (CSharp):
    1. fixed2 relativePos = fixed2((IN.vertex.x + objectWidth * 0.5) / objectWidth,( IN.vertex.y + objectHeight* 0.5) / objectHeight);
    because it assumes that the geometry of the sprite is centered at the origin, but it works as long as I create new material instances for each object. If I draw evertything in a single draw call the vertices seems to be already offseted in some way, and the calculation doesn't work anymore. (In this scenario, the calculations are correct only if I leave the object at 0,0,0 position)

    Is there anyway to calculate the "relative" UV position? (providing the size of the sprite to the shader, or any other parameter needed)

    Thank you
     
    howong likes this.
  2. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    howong likes this.
  3. Aedous

    Aedous

    Joined:
    Jun 20, 2009
    Posts:
    244
    I'm actually looking into this one as well, hoping someone will come along and help us out.

    From what I've looked up, there's no way the shader directly knows about the sprite size. According to this post that I found: http://stackoverflow.com/questions/23165899/unity3d-shader-for-sprite-clipping
    He/She basically explains that they way they solved it was by providing parameters to the shader to allow him to pass in the information about the current sprite being used.

    I'm not sure which code he's using to grab the sprite's relative UV position, but at the moment in an Update method I'm trying:
    Code (CSharp):
    1.  
    2. float minX = _SpriteRenderer.sprite.bounds.min.x;
    3. float maxX = _SpriteRenderer.sprite.bounds.max.x;
    4. _Renderer.sharedMaterial.SetFloat("_MinX", minX);
    5. _Renderer.sharedMaterial.SetFloat("_MaxX", maxX);
    6.  
    So far can't really tell if it's working, that's mostly because I'm not quite sure what value I'm meant to be passing into it, or even how the shader in the provided link is even checking whether it's in the sprites bounds.

    Will post back if I find anything else.
     
    joaobsneto likes this.
  4. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    Thanks Aedous! You brought some good information. I looked at the Sprite documentation and bounds do no good in this case. It refers to the the bounds of the sprite in the world space. But...

    http://docs.unity3d.com/ScriptReference/Sprite-textureRect.html

    This property refers to the texture rect in the atlas. If we pass this rect information to the shader we can get the UV bounds using _MainTex_TexelSize, which contains the size of the Atlas.
    I will try it tomorrow and post the results.
     
    howong likes this.
  5. Aedous

    Aedous

    Joined:
    Jun 20, 2009
    Posts:
    244
    Thanks for the heads up, I actually managed to get something actually looking good and working!
    Current sprite UV in the shader ( which I then pass to tex2D(_MainTex2, relativePos) when drawing the second texture on top of the sprite).
    Code (CSharp):
    1.  
    2. fixed2 relativePos = fixed2((IN.vertex.x + _Width * 0.5) / _Width, (IN.vertex.y + _Height * 0.5) / _Height);
    3.  
    Monobehaviour script attached to sprite using shader
    Code (CSharp):
    1.  
    2. GetComponent<Renderer>().sharedMaterial.SetFloat("_Width", spriteRenderer.sprite.rect.width);
    3. GetComponent<Renderer>().sharedMaterial.SetFloat("_Height", spriteRenderer.sprite.rect.height);
    4.  
    By using that I passing in the Sprite.rect Width and Height, I was able to fit a texture into the shape of the current sprite I'm looking at, even with animations playing! YAY!
    I noticed that if I only pass in sprite Width and Height at the start and not in the update, it actually stops the shader texture from jumping about, therefore making it more smooth :).

    I think your _MainTex_TexelSize might help with the other problem I have now, which I think is similar to @gigapunk issue, which is not being able to get the relative position, therefore making the secondary texture not follow the sprite properly. How are you using this _MainTex_TexelSize?
     
  6. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    _MainTex_TexelSize is a parameter that Unity Shader sets by default. It's a vector4 and it contains:

    _MainTex_TexelSize.x => 1/sprite.texture.width;
    _MainTex_TexelSize.y => 1/sprite.texture.height;
    _MainTex_TexelSize.z => sprite.texture.width;
    _MainTex_TexelSize.w => sprite.texture.height;

    When the sprite is in the Sprite Packer, it refers to the atlas of the image, same as the texture field in the Sprite object.
    I was using as a provisory solution for my shader. Since I now the size of the atlas I can calculate safe offsets and pixel based offsets in the image, but now that I know the bounds, I can acheive better results.
     
    howong likes this.
  7. Aedous

    Aedous

    Joined:
    Jun 20, 2009
    Posts:
    244
    Nice! thanks for the info, does that mean we don't have to try and get the reference from the Sprite through script and can simply just get it through _MainTex_TexelSize ?

    I've actually got mine working quite well, I'm quite new to even shader code so there might be some things that are off, but I figured out how to get the relative position using this in my shader:
    Code (CSharp):
    1.  
    2. struct appdata_t
    3. {
    4. float4 vertex   : POSITION;
    5. float4 color    : COLOR;
    6. float2 texcoord : TEXCOORD0;
    7. };
    8.  
    9. struct v2f
    10. {
    11. half2 texcoord  : TEXCOORD0;
    12. float3 worldPos : TEXCOORD1;
    13. float3 localPos : TEXCOORD2;
    14. float4 vertex   : SV_POSITION;
    15. fixed4 color    : COLOR;
    16. };
    17.  
    18. v2f vert(appdata_t IN)
    19. {
    20. v2f OUT;
    21. OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
    22. float4 scaleVertex = float4(IN.vertex.xyz, 0); //By setting the last value to 0 it ignores the flipping ( loses relative position if sprite is flipped :( )
    23. float4 wP = mul(_Object2World, scaleVertex); //Get the object to world vertex and store it
    24. OUT.worldPos = wP.xyz; //For use in fragment shader
    25. float4 lP = mul(_World2Object, scaleVertex); //Get the world to object vertex and store it
    26. OUT.localPos = lP.xyz; //For use in fragment shader
    27.  
    28. OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex);
    29. OUT.color = IN.color;
    30.  
    31. return OUT;
    32. }
    I make sure that the vertex that I'm grabbing for the local and world position are not flipped, this is because the second texture then loses it's relative position to the sprite when it's flipped.

    Finally in the frag function I calculate the relative position for the sprite with this:
    Code (CSharp):
    1.  
    2. fixed4 frag(v2f IN) : COLOR
    3. {
    4.        fixed4 t =  tex2D(_MainTex, IN.texcoord)*IN.color;
    5.     //Calculate relative position
    6.     fixed2 relativeWorld = fixed2(IN.worldPos.x + IN.localPos.x, IN.worldPos.y + IN.localPos.y);
    7.    
    8.     //This becomes the UV for the texture I want to apply to the sprite ( using the sprites width and height )
    9.     fixed2 relativePos = fixed2((relativeWorld.x + _Width), (relativeWorld.y + _Height));
    10.    
    11.     return tex2D(_MainTex_2, relativePos);
    12.    //Other shader nonsense
    13.  
    14. }
    Seems to work quite well for me, but I'm sure there most be some noob things I'm doing. Will post back with any updates I find, and will try out your method with "Texel Size".
     
    xucian likes this.
  8. Khamei

    Khamei

    Joined:
    Feb 25, 2016
    Posts:
    1
    I'm facing a simmilar issue with my project. My I ask you Aedous what are the variables _Object2World and _World2Object and how did you get them?
     
  9. TruffelsAndOranges

    TruffelsAndOranges

    Joined:
    Nov 9, 2014
    Posts:
    92
    I cannot seem to use _MainTex_TexelSize. When I use it, it just says that it doesn't exist. How do I use it? In for example Unity's default Sprites/Default shader?
     
  10. joaobsneto

    joaobsneto

    Joined:
    Dec 10, 2009
    Posts:
    152
    It's been more than one year that I solved this problem... but I think you have to declare it. You don't have to set the value, Unity will set for you.
    float4 _MainTex_TexelSize;
    _MainTex = the name of your Sprite texture/sampler2D.
     
    howong likes this.