Search Unity

2D sprite tiling shader works only "once"

Discussion in 'Shaders' started by Cjreek, Apr 24, 2014.

  1. Cjreek

    Cjreek

    Joined:
    Apr 23, 2013
    Posts:
    33
    Hi,

    I'm new to shaders and I'm not always sure if I know what I'm doing :mrgreen:
    I hope someone can help me with this.

    I'm trying to make a shader which tiles the texture instead of stretching it.
    I needed the x/y scaling of the object on which the shader is currently executed.

    I found something on the internet which I used in my fragment shader to get the x/y scaling of the object.

    This shader works if I have 1 object (sprite) in my scene with this shader (1)
    As soon as I copy this object or manually add another object which uses this shader it seems like both default to
    the default sprite shader (2). As soon as I change the shader of one of those sprites, the last remaining object using my shader works again (3).

    $Tileshader.png

    This is my Code:

    Code (csharp):
    1. Shader "Sprites/Default Tiled" {
    2.     Properties {
    3.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    4.     }
    5.     SubShader {
    6.         Tags {
    7.             "RenderType"="Opaque"
    8.             "IgnoreProjector"="False"
    9.             "PreviewType"="Plane"
    10.             "CanUseSpriteAtlas"="True"
    11.         }
    12.        
    13.         Pass
    14.         {      
    15.             Cull off
    16.            
    17.             CGPROGRAM
    18.                 #pragma vertex vert
    19.                 #pragma fragment frag
    20.                
    21.                 #include "UnityCG.cginc"
    22.        
    23.                 struct appdata {
    24.                     float4 vertex : POSITION;
    25.                     float2 texcoord : TEXCOORD;
    26.                     float4 color: COLOR;
    27.                 };
    28.                
    29.                 struct v2f {
    30.                     float4 pos : SV_POSITION;
    31.                     float2 uv : TEXCOORD;
    32.                     float4 color: COLOR;
    33.                 };
    34.                
    35.                 uniform sampler2D _MainTex;
    36.                
    37.                 v2f vert (appdata v)  
    38.                 {
    39.                     v2f o;
    40.                     o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    41.                     o.uv = v.texcoord;
    42.                     o.color = v.color;
    43.                    
    44.                     return o;
    45.                 }
    46.                
    47.                 half4 frag(v2f i) : COLOR
    48.                 {          
    49.                     float xScale = length(float3(_Object2World[0][0], _Object2World[1][0], _Object2World[2][0]));
    50.                     float yScale = length(float3(_Object2World[0][1], _Object2World[1][1], _Object2World[2][1]));
    51.    
    52.                     half4 c = tex2D(_MainTex, fmod(i.uv * float2(xScale ,yScale),1)) * i.color;
    53.                    
    54.                     return c;
    55.                 }
    56.    
    57.             ENDCG
    58.         }
    59.     }
    60.     FallBack "Sprites/Default"
    61. }
    62.  
     
    Last edited: Apr 24, 2014
  2. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Is there any particular reason why you can't use the tiling settings on the material?
    Most standard shaders automatically apply those settings to the uv coordinates using TRANSFORM_TEX in the vertex shader. (background info: TRANSFORM_TEX requires a float4 with the same name as the texture sampler + "_ST", which automagically receives the scale and offset information)
     
  3. Cjreek

    Cjreek

    Joined:
    Apr 23, 2013
    Posts:
    33
    I'm using 2D sprites (Unity 4.3) The sprite renderer does not support material offset/tiling settings.
    Also those tiling settings would affect every object which this material is assigned to. Every object is supposed to be tiled according to its x- and y-scale.

    I found out that this problem only occurs if more than one object is rendered with this shader at the same time.
    If every object with this shader has a different "SortingLayer"/"Order in Layer" combination everything is fine.
    What's the reason for this?

    I found a (bad?) solution for this. I made a tiny script which sets uniform variables for scaling in the shader on Awake.

    Code (csharp):
    1.  
    2. void Awake ()
    3. {
    4.     renderer.material.SetFloat("_ScaleX", transform.localScale.x);
    5.     renderer.material.SetFloat("_ScaleY", transform.localScale.y);
    6. }
    7.  
    Code (csharp):
    1.  
    2. // ..
    3.  
    4. uniform float _ScaleX;
    5. uniform float _ScaleY;
    6.  
    7.  
    8. // ...
    9.  
    10. half4 frag(v2f i) : COLOR
    11. {          
    12.     half4 c = tex2D(_MainTex, fmod(i.uv*float2(_ScaleX,_ScaleY),1)) * i.color;                 
    13.     return c;
    14. }
    It works, but I feel this is not a good solution :/
     
    Last edited: Apr 25, 2014
  4. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Ahhh, of course. Tiling and offset is used for sprite atlas stuff?

    Yeah, I guess that solution might prevent proper batching. [edit]the fact that sprites use [PerRendererData] might already prevent batching.
    For uniformly scaled objects, I guess you could use unity_Scale.w.

    ----- At this point into writing the post I decided to run a little experiment with _Object2Dworld ------
    I guess you could modify the uv coordinates using the matrix that moves objects to worldspace. I did a quick naive experiment with it, and it seems to work.
    (The following code was used to modify the built-in DefaultResourcesExtra/Sprites-Diffuse shader for Unity 4.3.4)
    Code (csharp):
    1. float2 tiledUv = mul(_Object2World, float4(IN.uv_MainTex, 0,0)).xy;
    2. fixed4 c = tex2D(_MainTex, tiledUv) * IN.color;
    $Experimental Tiling sprite w720.jpg
    (Please excuse the ugly test graphic)​
    Of course, this means that you lose control over the size of the texture, but you should be able to add that back in with a regular shader property, without losing batching.
    There might be some other problems, but I haven't worked with sprite shaders a lot yet, so I don't know. One obvious thing is that the generated mesh still uses the shape of the original sprite.

    Would that work for you?
     
    Last edited: Apr 26, 2014
  5. Cjreek

    Cjreek

    Joined:
    Apr 23, 2013
    Posts:
    33
    Thanks, but this doesn't work either.

    This is how it should work (and how it works with setting the uniform variables in a script):

    $soll.png

    And this is what your code does:

    $ist.png

    The 3 crates are not important. Only the brick floors.
     
  6. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Ahhh, it only works for sprites set to 'single' mode, because they can use repeat sampling, not clamp.
    Does it help to do the repeat stuff manually?
    Code (csharp):
    1. tiledUv = fmod(tiledUv, float2(1,1));
     
    Last edited: Apr 25, 2014
  7. Cjreek

    Cjreek

    Joined:
    Apr 23, 2013
    Posts:
    33
    Wow, thanks it works now! :smile:
    Although I really could have tried using fmod on tiledUv myself. It's kinda obvious :mrgreen:
     
  8. RC-1290

    RC-1290

    Joined:
    Jul 2, 2012
    Posts:
    639
    Yes, you could, but I'd like to imagine that my post was at least somewhat useful ^_^.

    Anyway, I was wondering why this solution worked, and did another experiment. At first I thought Unity was doing something fancy with the textures, but it appears to work quite straight-forward, using modified texture coordinates. The workaround I described above will ignore custom sprite texture coordinates set in the sprite editor, because it assumes that the texture coordinates lie in the 0-1 range.

    To support multiple sprites from one image, you need to do some extra work. And if you don't need that, you might as well set the Sprite Mode to single, and remove the uv wrapping code.
     
    Last edited: Apr 26, 2014
  9. Cjreek

    Cjreek

    Joined:
    Apr 23, 2013
    Posts:
    33