Flipbook Texture: How to Animate Sprites in Unity, Godot & VRChat


Make your own flipbook texture
A flipbook texture plays a sprite sheet frame by frame on one surface. Learn how to build one in Unity, Godot and VRChat, and flip a real sprite sheet live in 3D.
A flipbook texture is a sprite sheet that a shader plays frame by frame on a single surface. There is no rig and no timeline: the sheet holds every frame in a fixed grid, and the material moves a small window across that grid at a constant rate. An 8 x 8 sheet holds 64 frames; at 12 frames per second it loops in about 5.3 seconds. The mesh never changes, no texture is re-uploaded, and the whole animation costs one texture lookup.
This page covers the four numbers a flipbook shader needs and how the frame index maps onto UV coordinates, the two ways to drive a flipbook texture in Unity, how a Poiyomi flipbook is set up on a VRChat avatar, the equivalent in Godot and Phaser, and how to build your own sheet from a GIF or from a written description.
Quick Answer
A flipbook texture is a sprite sheet used as a shader animation, indexed by row and column over time. The sheet sits in one texture slot; each shader evaluation computes a frame index from elapsed time and a frame rate, reduces that index to a column and a row, and remaps the surface UVs into that single cell. Frame = floor(time * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). A 6 x 6 sheet at 10 FPS cycles 36 frames in 3.6 seconds, entirely on the GPU, with no per-frame upload and no draw call change.
How a flipbook texture works
Four numbers define the animation. Columns and rows describe the sheet layout. Frame count is normally columns times rows, and it is the value the index wraps at. Frame rate is how fast the index advances. Everything else — filtering, wrap mode, UV insets — exists to keep the sampler from reading outside the current cell.
The index math is short and worth writing out. Given elapsed time t: frame = floor(t * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). The UV window is then tiling = (1 / columns, 1 / rows) and offset = (column / columns, row / rows). Vertical direction is the part that trips people up. Most sheet-authoring tools write row 0 at the top of the image, while Unity and Godot UV origins are at the bottom left. On a top-row-first sheet in Unity, offset.y becomes 1 - (row + 1) / rows, otherwise the rows play inverted and the first frame of the animation is the last row of the sheet.
Filtering decides whether the result is clean. Set the sheet's wrap mode to Clamp. With Repeat, a sample at the right edge of the texture reads the left edge, so a column of pixels from frame 0 shows up inside the last frame of every row. Turn mipmaps off for hard-edged pixel art, because the mip chain averages neighbouring cells and the animation turns to mush once the surface is a few metres away. If the target engine or shader forces mipmaps on, leave a one or two pixel transparent gutter around every cell, or inset the UV window by half a texel on all four sides.
| Value | What it controls |
|---|---|
| Columns | Frames side by side. Sets horizontal tiling of one cell: 1 / columns. |
| Rows | Rows in the sheet. Sets vertical tiling: 1 / rows. Use 1 for a strip. |
| Frame count | Total frames indexed, normally columns x rows. The index wraps here. |
| FPS | How fast the index advances. floor(time * fps) gives the current frame. |
| Wrap mode | Clamp. Repeat lets the sampler read across the sheet edge. |
| Filter mode | Point, with mipmaps off, or inset UVs by half a texel per cell. |
Flipbook texture in Unity
There are two routes, and they fail in different ways. Route one is a shader that owns the row and column index and animates itself. Route two is the Animation window keyframing the material's tiling and offset while a plain Lit or Unlit shader does the sampling. Route one is better for effects that must stay in sync with game time; route two is better when you need the animation driven by an Animator state and visible in the timeline alongside everything else in the scene.
Either way, set the sprite sheet import first: Wrap Mode Clamp, Mip Maps off, Filter Mode Point, and no compression on pixel art. Those three settings cause more flipbook bugs than the shader math does.
Route 1: a shader with a row and column index
In Shader Graph, the Flipbook node takes Columns, Rows and a time-driven frame index and outputs the remapped UVs. In a hand-written shader, expose _Columns, _Rows and _Fps as properties and compute the cell in the vertex or fragment stage. If the animation has to be in sync across objects, drive the frame index from a script float rather than sampling _Time.y independently in every material, so two flipbooks on the same object cannot drift apart.
Route 2: the Animation window driving tiling and offset
This route animates the texture's _ST vector. Built-in Standard and Unlit use _MainTex_ST; URP Lit and Unlit use _BaseMap_ST. Tiling stays fixed at (1, 1 / rows) and only offset.y changes. The steps below apply to this route.
01
Select the material and open the Animation window. Create a clip, then add the texture's _ST vector property as a track — it appears as a Vector4 on the material.
02
Set the clip's sample rate to the same number as your target FPS. If the sample rate is lower than the FPS you want, keyframes collapse onto the same sample and the animation plays slower than intended.
03
Key offset.y once per frame: value = 1 - (row + 1) / rows for a top-row-first sheet, tiling = (1, 1 / rows).
04
Set every keyframe's interpolation to Constant in the curve editor. Linear blends two cells together and you see both frames at once as ghosting.
05
Make sure the last key does not repeat the first frame's value, or the animation holds for one frame at the loop seam.
06
If several renderers share the material, animate a MaterialPropertyBlock per renderer instead of reading renderer.material, which clones the material the first time you touch it and leaves the clone alive for the lifetime of the object.
Poiyomi flipbook for VRChat
The Flipbook section of the Poiyomi shader scrolls a UV window across a sheet on the material itself, so one material slot can show an animated sprite on an avatar mesh. It exposes Rows, Columns and a frame rate, plus a mode where you drive the frame index yourself instead of letting it run off shader time.
Use a single-row strip rather than a grid. A strip gives the shader one axis to step along, which removes the vertical seam entirely. With a grid, the UV window has to move down a row at the end of each row, and any filtering, mip level or half-texel rounding error at that boundary samples the neighbouring row — visible as a thin band of a different frame during playback. A strip also makes the frame index a straight multiply: frame width in UV is 1 / frameCount, and the offset is just frame / frameCount. For an 8-frame animation of 256 x 256 cells, the strip is 2048 x 256 and stays a power of two.
Alpha matters more on an avatar than in a flat scene. If the sprite has hard edges, use Cutout or Opaque. Transparent queue flipbooks do not write depth, and on an avatar they sort against the body and against each other in ways that change with camera angle. Keep the animated surface on a material that is already on the avatar where you can — every extra material slot counts against your avatar's performance rank. If the frame index is driven from an Animator state with a synced parameter, every client sees the same frame; a frame index computed from shader time is evaluated locally and can sit at a different point in the loop for different viewers.
01
Export the frames as one horizontal strip. Strip height equals cell height; strip width equals frame count times cell width. Do not leave a spare cell at the end.
02
Import the texture with Wrap Mode Clamp, Mip Maps off, and sRGB on. Enable Alpha Is Transparency only if the strip actually carries alpha.
03
Create a Poiyomi material, assign it to the animated mesh, and open the Flipbook section.
04
Set Rows to 1 and Columns to the frame count. Set the frame rate to match the duration you want — 8 frames at 8 FPS is a one-second loop.
05
Choose the frame source. Manual mode exposes a frame index you drive from an Animator state; the time-driven mode loops on its own.
06
Under Rendering, set Cutout or Opaque for hard-edged sprites. Leave Transparent for soft-edged effects only, and expect depth-sorting quirks.
07
Check the loop: the last frame must not be a duplicate of the first, or the sprite freezes for one frame every cycle.
08
Test in an uploaded avatar with a mirror, from close up and from across the instance. Distance is where mip and filtering problems appear.
Flipbook in Godot & Phaser
In Godot 4 the 2D path is AnimatedSprite2D with a SpriteFrames resource. Import the sheet, open SpriteFrames, set Hframes and Vframes to the column and row count, add the frames for the animation, then set the animation's speed in frames per second and leave Loop on. SpriteFrames stores the animation as a list of frame indices, so a grid sheet and a single strip both work without any UV math on your side.
If you only need one animation, a plain Sprite2D is lighter: set Hframes and Vframes once, then advance the integer frame property from an AnimationPlayer with step interpolation. Do not tween frame with a default easing curve — the value is an integer index and interpolating it either floors to a single value or flickers between two cells.
For a flipbook on a 3D surface in Godot, a ShaderMaterial with the same row and column math as the Unity version is the shortest path: uv = UV * vec2(1.0 / cols, 1.0 / rows) + vec2(col / cols, row / rows), with the row computed from the frame index. Godot's UV origin is bottom-left, same as Unity, so a top-row-first sheet needs the same vertical flip.
In Phaser 3 the sheet is loaded once and sliced at load time. frameWidth and frameHeight define the cell, frames number left to right and top to bottom, and the animation is identified by frame numbers rather than coordinates — there is no shader to write and no row and column arithmetic to get wrong.
| Engine | Object | What you set |
|---|---|---|
| Godot 4 | AnimatedSprite2D + SpriteFrames | Hframes and Vframes to the sheet layout, then the animation speed in FPS with Loop enabled. |
| Godot 4 | Sprite2D | Hframes and Vframes once, then the integer frame property advanced by an AnimationPlayer with step interpolation. |
| Godot 4 | ShaderMaterial on a 3D mesh | Columns, rows and a frame index in the shader, with the same UV remap and vertical flip as Unity. |
| Phaser 3 | this.load.spritesheet | frameWidth and frameHeight; Phaser slices the sheet left to right, top to bottom. |
| Phaser 3 | this.anims.create | Frames from anims.generateFrameNumbers, plus frameRate and repeat: -1 to loop. |
Flip a sprite sheet live in 3D
The playable scene in this section is The Cyclops' Island, running in the Neta Studio 3D game builder. The flipbook on screen is the same index-to-UV operation described above, evaluated in a Three.js shader on the GPU — the difference is that it is running on a real mesh in a real scene, with a camera you control and lights that move.
That matters more than it sounds. A flipbook in a flat Unity scene is usually viewed head-on, where seam bleed and mip blending are hard to see. Walk the camera around an animated surface at a grazing angle and the problems show up immediately: a band of the neighbouring cell at the frame edge, the last mip level averaging two frames into a blur, or an alpha edge that reads correctly from the front and disappears from the side. The browser build makes the loop between authoring a sheet and seeing it on a surface short enough to iterate without a build step.
Mini World is the smaller scene to test a single sheet in — a smaller mesh count and a shorter load. Agent Sprite Forge generates the frames if you do not have a sheet yet, and both scenes run the same flipbook path, so a sheet that looks right in Mini World behaves the same in The Cyclops' Island.
Make your own sheet
There are three routes into a usable sheet: convert an existing GIF, cut a grid down to a single strip, or generate frames from a written description. All three end at the same file — one PNG with every cell the same size, laid out left to right and top to bottom, with sheet width a clean multiple of cell width.
Converting a GIF is the route with the most hidden work, because a GIF is not a sequence of full frames. Frames are frequently partial diffs against the previous frame, and delays are stored per frame in hundredths of a second. A flipbook shader accepts one frame rate, so variable timing has to be flattened into duplicated frames before export.
Cutting a grid to a strip is mechanical: keep the row you need, crop the height to one cell, and leave the width as frame count times cell width. It is the right move when a grid sheet is used as a single animation, because it removes the vertical axis and the seam that comes with it. Generating from a description is the third route — Agent Sprite Forge takes an action and a frame count and returns a sheet at a fixed cell size, which removes the hand-matching work of lining up frames exported one at a time.
01
Flatten every GIF frame before exporting. Decode each frame onto a canvas that carries the previous frame forward, or you will export the diff rectangles instead of the picture.
02
Normalise the timing. Pick the shortest or the median GIF delay, then duplicate frames where the source held longer, so the sheet plays back at one constant FPS.
03
Lay the frames out left to right, top to bottom, every cell identical in size. Make the sheet dimensions an exact multiple of the cell size — a sheet that is 9.5 cells wide will sample the wrong texels forever.
04
To cut to a strip, crop the height to one cell and keep the width at frame count times cell width. Delete the empty tail cell if the source grid had one.
05
Add a one or two pixel transparent gutter around every cell if the target engine will not let you disable mipmaps. Otherwise inset the UV window by half a texel on all four sides.
06
To generate instead of convert, write the action, the frame count and the facing direction, and let Agent Sprite Forge return a fixed-cell sheet rather than exporting frames one at a time and aligning them by hand.
07
Verify by stepping the frame index one at a time in the engine. If any cell shows a sliver of its neighbour at the edge, the sheet layout or the UV inset is wrong, not the shader.
Walk into the world it belongs to
A sprite is a character with nowhere to stand. The last step is the one nobody else offers: take the sheet you just built and put it in a world someone can walk around in — a small island, a planet, a rainy street corner. Every world on this site was made from one written description and published as a page you can play.
FAQ
What is a Poiyomi flipbook? +
It is the Flipbook section of the Poiyomi shader, which scrolls a UV window across a sprite sheet on a material so one material slot can show an animated sprite on an avatar mesh. You set Rows, Columns and a frame rate, and either let the frame index run off shader time or drive it from an Animator state. A single-row strip is preferred because it removes the vertical seam between rows.
Can you flipbook a GIF directly? +
No. A GIF stores per-frame delays and often only the pixels that changed, while a flipbook shader expects one texture and one constant frame rate. Convert it first: flatten every frame onto the previous one, normalise the timing, and write the result into a fixed cell grid. The shader never sees the GIF.
Why does my flipbook texture bleed between frames? +
The sampler is reading outside the current cell window. Set the texture wrap mode to Clamp, turn mipmaps off for hard-edged art, and inset the UV window by half a texel on each side. Bleeding gets worse with distance, because that is where the mip chain starts averaging neighbouring cells together.
What frame rate should a flipbook use? +
8 to 12 FPS reads as hand-animated and suits character sprites; 24 to 30 suits fire, water and other continuous effects. The shader has exactly one rate, so if the source had variable timing, duplicate frames to match instead of trying to vary the speed mid-loop.
Do I need a grid, or is one row enough? +
One row is enough for anything that plays as a sequence, and it removes one axis of seam error. Grids are for sheets that pack several animations into one texture, where the shader or engine selects a row for idle, run or attack. If only one animation comes out of a grid sheet, cut it down to a strip.
Does a flipbook work with Unity URP's Lit shader? +
Not out of the box — URP Lit has no frame index. Use Shader Graph with the Flipbook node, which takes Columns, Rows and a time-driven index, or animate the material's _BaseMap_ST tiling and offset with constant keyframe interpolation. Built-in Standard and Unlit use _MainTex_ST under the same rules.
Back to the 3D Game Builder
All 31 finished 3D worlds, games and scenes live on one page — every one of them playable.
Open the sprite asset library
Every asset in this library is a transparent, game-ready PNG with a recorded prompt — Sprite sheet resources and Free sprite sheets, tagged by action, style and subject. Free to browse and download.