Search Unity

Animated UV with rect array data?

Discussion in 'Shaders' started by IEdge, Jan 25, 2020.

  1. IEdge

    IEdge

    Joined:
    Mar 25, 2017
    Posts:
    51
    Hey guys.

    I'm trying to make a flipbook shader, I'm starting from here, that shader works with rows and cols to change the UV positions, but is usseless for me because my spritesheets has diferent frame sizes.

    My idea is to implement a float4[] variable to set the frame rect data from C# script, and read each index over time.

    Any idea how to do this?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Sure. What part are you stuck on?

    I will say usually if you're using a sprite atlas with variable sized sprites the usual solution is to just use a sprite, or maybe a particle system (which can use a sprite atlas).

    Otherwise you'd need to take your sprite atlas, call GetSprites() on it, and iterate over the textureRect and calculate the appropriate UV scale and offset to use and set those values for the array. You'll also need to pass in the offset and scale for the mesh vertices themselves, since presumably every frame isn't perfectly centered. That can be in a separate array, or packed into the same array and have the uv & mesh scale and offset be every other index.
     
    IEdge likes this.
  3. IEdge

    IEdge

    Joined:
    Mar 25, 2017
    Posts:
    51
    Hi @bgolus, thanks for your answer.

    Here a sample of my spritesheets: upload_2020-1-27_18-10-52.png

    As you can see, each image has multiple animations with different offset/scale, I store these data into a scriptable object, then in runtime I load it into a Vector4[] and set to material using this code:
    upload_2020-1-27_18-18-39.png

    Here my shader "code" (I'm totally noob with shaders sorry):
    Code (CSharp):
    1.             float4 _Frames[512];
    2.             int _TotalFrames = 0;
    3.             uint _Index;
    4.          
    5.             v2f vert(appdata v)
    6.             {
    7.                 v2f o;
    8.                 o.vertex = UnityObjectToClipPos(v.vertex);
    9.  
    10.  
    11.                 // How I can run the bellow code over time? (20/30/60 fps...)
    12.              
    13.                 o.uv = v.uv * _Frames[_Index].xy + _Frames[_Index].zw;
    14.                
    15.                 _Index++;
    16.  
    17.                 if (_Index > _TotalFrames)
    18.                     _Index = 0;
    19.  
    20.                 return o;
    21.             }
     
    Last edited: Jan 28, 2020
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Mkay. So here's the thing about shaders. They don't have any state beyond the current evocation. What I mean by that is each vertex, and each pixel, for every frame, gets fresh values from the mesh & material data and outputs a color value at the end. Any values set during the running of the code that aren't either being passed onto the next shader stage (ie: vertex -> fragment) or outputting a color to a render texture / frame buffer are immediately destroyed. It's also running that vert function on each vertex of your mesh individually, so even if some state was retained between frames you wouldn't want it to in this case since every single vertex would increment the
    _Index
    value.

    So instead to figure out the current frame you need to recalculate it from some value being passed to the material. That could be as simple as actually setting the current index from c#, or it could be using the
    _Time
    vector value.

    For example:
    Code (csharp):
    1. uint index = (int)(_Time.y / 30.0) % 512; // _Time.y is equivalent to Time.timeSinceLevelLoad in c#
    That'll run the animation at 30fps, looping through all 512 frames.
     
    IEdge likes this.
  5. IEdge

    IEdge

    Joined:
    Mar 25, 2017
    Posts:
    51
    Great? works as expected, thanks you!


    Code (CSharp):
    1. // Credits:
    2. // https://unitycoder.com/blog/2018/11/30/sprite-sheet-flip-book-shader/
    3. // https://forum.unity.com/threads/animated-uv-with-rect-array-data.815685/#post-5415345
    4.  
    5. Shader "Pokémon LG/Spritesheet"
    6. {
    7.     Properties
    8.     {
    9.         [Header(Texture Sheet)]
    10.         _MainTex("Texture", 2D) = "white" { }
    11.  
    12.         [Header(Settings)]
    13.         _Fps("Frames Per Seconds", Range(1, 60)) = 30
    14.     }
    15.  
    16.     SubShader
    17.     {
    18.         Tags {
    19.             "Queue" = "AlphaTest"
    20.             "IgnoreProjector" = "True"
    21.             "PreviewType" = "Plane"
    22.             "RenderType" = "TransparentCutout"
    23.             "DisableBatching" = "True"
    24.         }
    25.  
    26.         LOD 100
    27.  
    28.         Pass
    29.         {
    30.             CGPROGRAM
    31.             #pragma vertex vert
    32.             #pragma fragment frag
    33.             #include "UnityCG.cginc"
    34.  
    35.             struct appdata
    36.             {
    37.                 float4 vertex : POSITION;
    38.                 float2 uv : TEXCOORD0;
    39.             };
    40.  
    41.             struct v2f
    42.             {
    43.                 float2 uv : TEXCOORD0;
    44.                 float4 vertex : SV_POSITION;
    45.             };
    46.  
    47.             sampler2D _MainTex;
    48.             float _Fps;
    49.             float4 _Frames[512];
    50.             uint _FrameCount = 0;
    51.  
    52.             v2f vert(appdata v)
    53.             {
    54.                 v2f o;
    55.                 o.vertex = UnityObjectToClipPos(v.vertex);
    56.  
    57.                 uint index = 0 == _FrameCount ? 0 : ((int)(_Time.y / (1 / _Fps)) % _FrameCount);
    58.  
    59.                 o.uv = 0 == _FrameCount ? v.uv : (v.uv * _Frames[index].xy + _Frames[index].zw);
    60.  
    61.                 return o;
    62.             }
    63.  
    64.             fixed4 frag(v2f i) : SV_Target
    65.             {
    66.                 fixed4 col = tex2D(_MainTex, i.uv);
    67.  
    68.                 // cutout
    69.                 clip(col.a - 0.05);
    70.  
    71.                 return col;
    72.             }
    73.             ENDCG
    74.         }
    75.     }
    76. }
    77.  
    I have a final question, do you know how to prevent lost the values of _Frames and _FrameCount when editor lost focus?
     
    Last edited: Jan 29, 2020
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Make them material properties, or reset them every frame.
     
  7. IEdge

    IEdge

    Joined:
    Mar 25, 2017
    Posts:
    51
    The problem is that _Frames is an array.

    Can you show me a example?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Oh, right.


    Code (csharp):
    1. void Update() {
    2.   mat.SetVectorArray(“_Frames”, frameData);
    3. }
    If you don’t want to do it every update, you can check if the editor / application has focus in an Update function and only reset it when it changes back to having focus. There are easier ways to detect when the standalone player regains focus, but not the editor.

    You could also encode the frame data into a texture and sample that rather than use an array. That you can set as a material property without loosing it between focus changes.
     
    IEdge likes this.