Flipbook 貼圖:如何在 Unity、Godot 與 VRChat 中製作 Sprite 動畫


製作你自己的 Flipbook 貼圖
Flipbook 貼圖能在單一表面上逐格播放 Sprite 圖集。了解如何在 Unity、Godot 與 VRChat 中製作,並在 3D 中即時翻動真實的 Sprite 圖集。
Flipbook 貼圖是一種 Sprite 圖集,由 Shader 在單一表面上逐格播放。它不需要骨架,也不需要時間軸:圖集以固定網格保存每一格,材質則以固定速率讓一個小視窗在網格上移動。8 x 8 的圖集可容納 64 格;以每秒 12 格播放時,約 5.3 秒完成一次循環。網格本身不會改變,不會重新上傳貼圖,整段動畫只需一次貼圖取樣。
本頁說明 Flipbook Shader 所需的四個數值,以及影格索引如何對應到 UV 座標;在 Unity 中驅動 Flipbook 貼圖的兩種方式;如何在 VRChat 虛擬人物上設定 Poiyomi Flipbook;Godot 與 Phaser 中的對應做法;以及如何從 GIF 或文字描述製作自己的圖集。
快速解答
Flipbook 貼圖是一種當作 Shader 動畫使用的 Sprite 圖集,會隨時間依列與欄建立索引。圖集佔用一個貼圖插槽;每次 Shader 求值都會根據經過時間與影格率計算出影格索引,將該索引縮減為欄與列,並把表面 UV 重新對應到該單一儲存格。Frame = floor(time * fps) mod frameCount,column = frame mod columns,row = floor(frame / columns)。6 x 6 的圖集在 10 FPS 下會於 3.6 秒內循環 36 格,完全在 GPU 上執行,不需要逐格上傳,也不會改變 Draw Call。
Flipbook 貼圖的運作方式
四個數值定義了這段動畫。欄與列描述圖集版面。影格數通常是欄數乘以列數,也是索引繞回的數值。影格率是索引前進的速度。其他一切 — 濾波、Wrap Mode、UV 內縮 — 都是為了讓取樣器不會讀取到目前儲存格之外。
索引的數學式很短,值得寫出來。給定經過時間 t:frame = floor(t * fps) mod frameCount,column = frame mod columns,row = floor(frame / columns)。接著 UV 視窗為 tiling = (1 / columns, 1 / rows),offset = (column / columns, row / rows)。垂直方向是最容易讓人卡住的部分。大多數圖集製作工具會把第 0 列寫在影像頂端,而 Unity 與 Godot 的 UV 原點在左下角。在 Unity 中,若圖集是上列優先,offset.y 會變成 1 - (row + 1) / rows,否則列的播放會上下顛倒,動畫的第一格會變成圖集的最後一列。
濾波決定結果是否乾淨。將圖集的 Wrap Mode 設為 Clamp。若使用 Repeat,取樣在貼圖右緣時會讀到左緣,因此第 0 格的像素列會出現在每一列的最後一格內。硬邊像素藝術請關閉 mipmaps,因為 mip 鏈會平均相鄰儲存格,當表面距離數公尺遠時,動畫就會糊掉。如果目標引擎或 Shader 強制開啟 mipmaps,請在每個儲存格周圍保留一到兩像素的透明間隙,或將 UV 視窗四邊各內縮半個 texel。
| 數值 | 控制內容 |
|---|---|
| 欄 | 影格並排。設定單一儲存格的水平平鋪:1 / columns。 |
| 列 | 圖集中的列數。設定垂直平鋪:1 / rows。條狀圖請設為 1。 |
| 影格數 | 建立索引的影格總數,通常是 columns x rows。索引會在此繞回。 |
| FPS | 索引前進的速度。floor(time * fps) 會給出目前影格。 |
| Wrap Mode | Clamp。Repeat 會讓取樣器讀取到圖集邊緣之外。 |
| Filter Mode | Point,並關閉 mipmaps;或將每個儲存格的 UV 內縮半個 texel。 |
Unity 中的 Flipbook 貼圖
有兩種做法,而且它們會以不同方式出錯。做法一是由 Shader 自行持有列與欄索引並播放動畫。做法二是用 Animation 視窗為材質的 tiling 與 offset 設定關鍵影格,讓單純的 Lit 或 Unlit Shader 負責取樣。做法一較適合必須與遊戲時間保持同步的效果;做法二則適合需要由 Animator 狀態驅動動畫,並讓它在時間軸中與場景其他內容並列顯示的情況。
無論哪種做法,請先設定 Sprite 圖集的匯入選項:Wrap Mode 設為 Clamp、Mip Maps 關閉、Filter Mode 設為 Point,且像素藝術不要壓縮。這三項設定造成的 Flipbook 錯誤比 Shader 數學還多。
做法 1:帶有列與欄索引的 Shader
在 Shader Graph 中,Flipbook 節點會接受 Columns、Rows 與由時間驅動的影格索引,並輸出重新對應後的 UV。在手寫 Shader 中,將 _Columns、_Rows 與 _Fps 公開為屬性,並在頂點或片段階段計算儲存格。如果動畫必須在多個物件之間保持同步,請用腳本中的 float 來驅動影格索引,而不是在每個材質中各自取樣 _Time.y,這樣同一個物件上的兩個 Flipbook 才不會彼此漂移。
做法 2:由 Animation 視窗驅動 tiling 與 offset
這個做法會播放貼圖的 _ST 向量。內建 Standard 與 Unlit 使用 _MainTex_ST;URP Lit 與 Unlit 使用 _BaseMap_ST。Tiling 固定為 (1, 1 / rows),只有 offset.y 會改變。以下步驟適用於這個做法。
01
選取材質並開啟 Animation 視窗。建立一個剪輯,然後將貼圖的 _ST 向量屬性新增為軌道 — 它會以 Vector4 的形式顯示在材質上。
02
將剪輯的取樣率設為與目標 FPS 相同的數值。如果取樣率低於你想要的 FPS,關鍵影格會疊到同一個取樣點上,動畫播放速度會比預期慢。
03
每一格為 offset.y 設定一次關鍵影格:若為上列優先的圖集,value = 1 - (row + 1) / rows,tiling = (1, 1 / rows)。
04
在曲線編輯器中將每個關鍵影格的插值設為 Constant。Linear 會將兩個儲存格混合在一起,你會同時看到兩格而產生殘影。
05
確認最後一個關鍵影格沒有重複第一格的值,否則動畫會在循環接縫處多停留一格。
06
如果有多個 Renderer 共用同一個材質,請為每個 Renderer 播放 MaterialPropertyBlock,而不要讀取 renderer.material;後者會在你第一次取用時複製材質,並讓該複本在物件生命週期內一直存在。
VRChat 用的 Poiyomi Flipbook
Poiyomi Shader 的 Flipbook 區段會在材質本身讓 UV 視窗在圖集上捲動,因此單一材質插槽就能在虛擬人物網格上顯示動畫 Sprite。它提供 Rows、Columns 與影格率,還有一種模式可讓你自行驅動影格索引,而不是讓它跟著 Shader 時間跑。
請使用單列條狀圖,而不是網格。條狀圖只給 Shader 一個軸向可逐步前進,能完全消除垂直接縫。若使用網格,UV 視窗在每一列結尾都必須往下移動一列,而該邊界上任何濾波、mip 層級或半 texel 捨入誤差都會取樣到相鄰列 — 播放時會看到一條不同影格的細帶。條狀圖也讓影格索引變成單純的乘法:UV 中的影格寬度是 1 / frameCount,而 offset 就是 frame / frameCount。對於 256 x 256 儲存格的 8 格動畫,條狀圖為 2048 x 256,且維持在 2 的次方。
在虛擬人物上,Alpha 比在平面場景中更重要。如果 Sprite 是硬邊,請使用 Cutout 或 Opaque。Transparent 佇列的 Flipbook 不會寫入深度,而在虛擬人物上,它們與身體及其他物件的排序方式會隨相機角度改變。盡可能把動畫表面放在虛擬人物上已有的材質 — 每個額外材質插槽都會影響虛擬人物的效能等級。如果影格索引是由帶有同步參數的 Animator 狀態驅動,每個用戶端看到的影格都相同;由 Shader 時間計算出的影格索引則是在本機求值,不同檢視者可能停在循環中的不同位置。
01
將影格匯出為一條水平條狀圖。條狀圖高度等於儲存格高度;條狀圖寬度等於影格數乘以儲存格寬度。不要在結尾留下多餘的儲存格。
02
匯入貼圖時,Wrap Mode 設為 Clamp、Mip Maps 關閉、sRGB 開啟。只有當條狀圖確實帶有 alpha 時,才啟用 Alpha Is Transparency。
03
建立 Poiyomi 材質,將其指定給動畫網格,然後開啟 Flipbook 區段。
04
將 Rows 設為 1,Columns 設為影格數。設定影格率以符合你想要的長度 — 8 格、8 FPS 就是一秒的循環。
05
選擇影格來源。Manual 模式會提供一個由 Animator 狀態驅動的影格索引;時間驅動模式則會自行循環。
06
在 Rendering 底下,硬邊 Sprite 請設為 Cutout 或 Opaque。Transparent 只保留給柔邊效果,並預期會遇到深度排序的怪問題。
07
檢查循環:最後一格不能與第一格重複,否則 Sprite 每次循環都會凍結一格。
08
在已上傳的虛擬人物中搭配鏡子測試,從近距離和執行個體另一頭觀看。距離正是 mip 與濾波問題浮現的地方。
Godot 與 Phaser 中的 Flipbook
在 Godot 4 中,2D 做法是使用 AnimatedSprite2D 搭配 SpriteFrames 資源。匯入圖集、開啟 SpriteFrames、將 Hframes 與 Vframes 設為欄與列的數量、加入動畫所需的影格,然後以每秒影格數設定動畫速度並保持 Loop 開啟。SpriteFrames 會將動畫儲存為影格索引清單,因此網格圖集與單一條狀圖都能運作,你這邊不需要做任何 UV 數學。
如果你只需要一個動畫,單純的 Sprite2D 更輕量:設定一次 Hframes 與 Vframes,然後用 AnimationPlayer 以 step 插值推進整數 frame 屬性。不要用預設緩動曲線補間 frame — 該值是整數索引,對它插值不是向下取整成單一值,就是在兩個儲存格之間閃爍。
在 Godot 中若要在 3D 表面使用 Flipbook,最快的方式是使用與 Unity 版本相同的列欄數學的 ShaderMaterial:uv = UV * vec2(1.0 / cols, 1.0 / rows) + vec2(col / cols, row / rows),其中 row 由影格索引計算得出。Godot 的 UV 原點與 Unity 一樣在左下角,因此上列優先的圖集也需要相同的垂直翻轉。
在 Phaser 3 中,圖集只會載入一次,並在載入時切片。frameWidth 與 frameHeight 定義儲存格,影格由左至右、由上至下編號,動畫是透過影格編號而非座標來識別 — 不需要寫 Shader,也沒有列與欄的算術能出錯。
| 引擎 | 物件 | 設定項目 |
|---|---|---|
| Godot 4 | AnimatedSprite2D + SpriteFrames | 將 Hframes 與 Vframes 設為圖集版面,然後設定動畫速度(FPS)並啟用 Loop。 |
| Godot 4 | Sprite2D | 設定一次 Hframes 與 Vframes,然後由 AnimationPlayer 以 step 插值推進整數 frame 屬性。 |
| Godot 4 | 3D 網格上的 ShaderMaterial | 在 Shader 中設定 columns、rows 與影格索引,並使用與 Unity 相同的 UV 重對應與垂直翻轉。 |
| Phaser 3 | this.load.spritesheet | frameWidth 與 frameHeight;Phaser 會由左至右、由上至下切片圖集。 |
| Phaser 3 | this.anims.create | 影格來自 anims.generateFrameNumbers,另加上 frameRate 與 repeat: -1 以循環。 |
在 3D 中即時翻動 Sprite 圖集
本節的可遊玩場景是 The Cyclops' Island,運行於 Neta Studio 3D 遊戲建構工具中。畫面上的 Flipbook 是與上述相同的索引轉 UV 操作,在 GPU 上的 Three.js Shader 中求值 — 差別在於它是在真實場景中的真實網格上運行,並有你可控制的相機與會移動的燈光。
這件事比聽起來更重要。平面 Unity 場景中的 Flipbook 通常是以正面觀看,接縫滲色與 mip 混合很難看出來。將相機以掠射角繞著動畫表面走一圈,問題立刻就會浮現:影格邊緣出現相鄰儲存格的條帶、最後一層 mip 將兩格平均成模糊影像,或 alpha 邊緣從正面看正確、從側面卻消失。瀏覽器版本讓「製作圖集」與「在表面上看到結果」之間的循環短到不必經過建置步驟就能反覆調整。
Mini World 是用來測試單一圖集的較小場景 — 網格數量較少,載入時間較短。如果你還沒有圖集,Agent Sprite Forge 可以產生影格;兩個場景都執行相同的 Flipbook 路徑,因此在 Mini World 中看起來正確的圖集,在 The Cyclops' Island 中也會有一樣的表現。
製作自己的圖集
有三種方式可以做出可用的圖集:轉換現有的 GIF、將網格裁切成單一條狀圖,或從文字描述產生影格。三種方式最終都匯出同一個檔案 — 一個 PNG,每個儲存格大小相同,由左至右、由上至下排列,且圖集寬度必須是儲存格寬度的整數倍。
轉換 GIF 是隱藏工作最多的一條路,因為 GIF 並不是一連串完整影格。影格經常只是與前一格的局部差異,而延遲是以百分之一秒為單位逐格儲存。Flipbook Shader 只接受一種影格率,因此可變的時間必須在匯出前攤平成重複影格。
將網格裁切成條狀圖是機械性的工作:保留你需要的那一列,將高度裁成一個儲存格,寬度則保持為影格數乘以儲存格寬度。當網格圖集只當作單一動畫使用時,這是正確的做法,因為它移除了垂直軸以及隨之而來的接縫。從描述產生是第三條路 — Agent Sprite Forge 接受動作與影格數,並回傳固定儲存格大小的圖集,省去逐格匯出再手動對齊的工作。
01
匯出前先攤平每個 GIF 影格。將每一格解碼到會保留前一格內容的畫布上,否則你匯出的會是差異矩形,而不是完整畫面。
02
正規化時間。選用最短或中位數的 GIF 延遲,然後在來源停留較久的地方複製影格,讓圖集以固定的 FPS 播放。
03
將影格由左至右、由上至下排列,每個儲存格大小一致。讓圖集尺寸為儲存格大小的精確倍數 — 寬度為 9.5 個儲存格的圖集會永遠取樣到錯誤的 texel。
04
若要裁切成條狀圖,將高度裁成一個儲存格,寬度保持為影格數乘以儲存格寬度。如果來源網格有多餘的尾端儲存格,請將其刪除。
05
如果目標引擎不允許你停用 mipmaps,請在每個儲存格周圍加入一到兩像素的透明間隙。否則將 UV 視窗四邊各內縮半個 texel。
06
若要產生而不是轉換,請寫下動作、影格數與面向,讓 Agent Sprite Forge 回傳固定儲存格的圖集,而不是逐格匯出再手動對齊。
07
在引擎中一次一步地前進影格索引來驗證。如果任何儲存格邊緣出現鄰格的細條,問題出在圖集版面或 UV 內縮,而不是 Shader。
走進它所屬的世界
Sprite 是一個無處可站的角色。最後一步是其他人沒有提供的:把你剛做好的圖集放進一個有人能走動的世界 — 一座小島、一顆行星、一個下雨的街角。這個網站上的每個世界都是從一段文字描述製作而成,並發布為可遊玩的頁面。
常見問題
什麼是 Poiyomi Flipbook? +
它是 Poiyomi Shader 的 Flipbook 區段,會在材質上讓 UV 視窗在 Sprite 圖集上捲動,因此單一材質插槽就能在虛擬人物網格上顯示動畫 Sprite。你可以設定 Rows、Columns 與影格率,並選擇讓影格索引跟著 Shader 時間跑,或由 Animator 狀態驅動。建議使用單列條狀圖,因為它能消除列與列之間的垂直接縫。
可以直接將 GIF 做成 Flipbook 嗎? +
不行。GIF 會逐格儲存延遲,而且通常只儲存有變化的像素,而 Flipbook Shader 預期的是單一貼圖與單一固定影格率。請先轉換:將每一格疊到前一格上、正規化時間,並將結果寫入固定儲存格網格。Shader 永遠不會看到 GIF。
為什麼我的 Flipbook 貼圖會在影格之間滲色? +
取樣器讀取到目前儲存格視窗之外。將貼圖的 wrap mode 設為 Clamp、硬邊美術關閉 mipmaps,並將 UV 視窗每一側內縮半個 texel。滲色會隨距離惡化,因為那正是 mip 鏈開始平均相鄰儲存格的地方。
Flipbook 應該使用多少影格率? +
8 到 12 FPS 看起來像手繪動畫,適合角色 Sprite;24 到 30 則適合火焰、水與其他持續性效果。Shader 只有一種速率,因此如果來源時間是可變的,請複製影格來配合,而不是試圖在循環中途改變速度。
我需要網格,還是單一列就夠了? +
對於任何以序列播放的內容,單一列就夠了,而且能消除一個軸向的接縫錯誤。網格適用於將多個動畫打包到單一貼圖的圖集,由 Shader 或引擎為待機、奔跑或攻擊選擇某一列。如果網格圖集只輸出一段動畫,請將它裁成條狀圖。
Flipbook 能搭配 Unity URP 的 Lit Shader 使用嗎? +
無法直接使用 — URP Lit 沒有影格索引。請使用 Shader Graph 的 Flipbook 節點,它會接受 Columns、Rows 與由時間驅動的索引,或者以 Constant 關鍵影格插值來播放材質的 _BaseMap_ST tiling 與 offset。內建 Standard 與 Unlit 則在相同規則下使用 _MainTex_ST。
返回 3D 遊戲建構工具
全部 31 個完成的 3D 世界、遊戲與場景都在同一頁 — 每一個都可以遊玩。
開啟 Sprite 素材庫
這個資料庫中的每個素材都是透明、可直接用於遊戲的 PNG,並附有錄製的提示詞 — Sprite 圖集資源與免費 Sprite 圖集,依動作、風格與主題加上標籤。可免費瀏覽與下載。