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?
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.
Hi @bgolus, thanks for your answer. Here a sample of my spritesheets: 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: Here my shader "code" (I'm totally noob with shaders sorry): Code (CSharp): float4 _Frames[512]; int _TotalFrames = 0; uint _Index; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); // How I can run the bellow code over time? (20/30/60 fps...) o.uv = v.uv * _Frames[_Index].xy + _Frames[_Index].zw; _Index++; if (_Index > _TotalFrames) _Index = 0; return o; }
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): 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.
Great? works as expected, thanks you! Code (CSharp): // Credits: // https://unitycoder.com/blog/2018/11/30/sprite-sheet-flip-book-shader/ // https://forum.unity.com/threads/animated-uv-with-rect-array-data.815685/#post-5415345 Shader "Pokémon LG/Spritesheet" { Properties { [Header(Texture Sheet)] _MainTex("Texture", 2D) = "white" { } [Header(Settings)] _Fps("Frames Per Seconds", Range(1, 60)) = 30 } SubShader { Tags { "Queue" = "AlphaTest" "IgnoreProjector" = "True" "PreviewType" = "Plane" "RenderType" = "TransparentCutout" "DisableBatching" = "True" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float _Fps; float4 _Frames[512]; uint _FrameCount = 0; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); uint index = 0 == _FrameCount ? 0 : ((int)(_Time.y / (1 / _Fps)) % _FrameCount); o.uv = 0 == _FrameCount ? v.uv : (v.uv * _Frames[index].xy + _Frames[index].zw); return o; } fixed4 frag(v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); // cutout clip(col.a - 0.05); return col; } ENDCG } } } I have a final question, do you know how to prevent lost the values of _Frames and _FrameCount when editor lost focus?
Oh, right. Code (csharp): void Update() { mat.SetVectorArray(“_Frames”, frameData); } 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.