翻頁貼圖 · 完整指南

翻頁貼圖:如何在 Unity、Godot 與 VRChat 裡做精靈動畫

做一張自己的翻頁貼圖

翻頁貼圖是一張精靈表,由著色器在單一表面上逐幀播放。這裡教你怎麼在 Unity、Godot 和 VRChat 裡做出一張,並在 3D 裡即時翻動真正的精靈表。

✦精靈
做一張翻頁貼圖,素材來自 一個魔法傳送門循環

翻頁貼圖是一張精靈表,由著色器在單一表面上逐幀播放。沒有骨架,也沒有時間軸:整張表把每一幀放進固定的格子排列,材質再以固定速率讓一個小視窗在格子上移動。8 x 8 的表有 64 幀,以每秒 12 幀播放,約 5.3 秒循環一次。模型網格完全不用改,貼圖也不需要重新上傳,整段動畫只花一次貼圖取樣。

這一頁會講到翻頁動畫著色器需要的四個數字、影格索引怎麼對應到 UV 座標、在 Unity 裡驅動翻頁貼圖的兩種做法、Poiyomi 的翻頁動畫在 VRChat 角色上怎麼設定、Godot 和 Phaser 裡的對應做法,以及怎麼從 GIF 或一段文字描述做出自己的精靈表。

快速解答

翻頁貼圖就是一張當作著色器動畫用的精靈表,隨時間用列與欄去索引。整張表放在一個貼圖插槽裡;著色器每次求值時,用經過的時間和影格率算出影格索引,把索引拆成欄與列,再把表面的 UV 重新映射到那一個儲存格。影格 = floor(time * fps) mod frameCount,欄 = frame mod columns,列 = floor(frame / columns)。一張 6 x 6 的表在 10 FPS 下,36 個影格跑完一輪是 3.6 秒,全部在 GPU 上完成,不需要逐影格上傳,也不需要改 draw call。

翻頁貼圖的運作方式

四個數字就定義了整段動畫。欄與列描述表的排版。影格數通常等於欄乘列,索引就是在這個值上繞回。影格率決定索引前進的速度。其餘的東西——濾波、環繞模式、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,否則列的播放順序會上下顛倒,動畫的第一個影格會變成整張表的最後一列。

濾波決定成品乾不乾淨。把表的環繞模式設成 Clamp。用 Repeat 的話,貼圖右緣的取樣會讀到左緣,於是每個列的最後一個影格裡都會混進第 0 個影格的一條像素。硬邊像素美術要把 mipmap 關掉,因為 mip 鏈會把相鄰儲存格平均掉,表面只要離鏡頭幾公尺,動畫就糊成一團。如果目標引擎或著色器強制開啟 mipmap,就在每個儲存格四周留一兩像素的透明邊,或者把 UV 視窗四邊各內縮半個 texel。

值控制什麼
Columns橫向並排的影格數。決定單一儲存格的水平 tiling:1 / columns。
Rows表裡的列數。決定垂直 tiling:1 / rows。做成一條橫帶時填 1。
Frame count會被索引的影格總數,通常等於 columns x rows。索引在此繞回。
FPS索引往前推進的速度。floor(time * fps) 就是目前的影格編號。
Wrap modeClamp。設成 Repeat 的話,取樣器會讀到精靈表邊界之外。
Filter modePoint,並把 mipmaps 關掉;或是把每一格的 UV 往內縮半個 texel。

Unity 裡的 Flipbook 貼圖

有兩條路可走,而且兩條路出錯的方式不一樣。路線一是讓著色器自己持有列與行的索引,自行播放動畫。路線二是用 Animation 視窗對材質的 tiling 與 offset 打關鍵影格,取樣交給一般的 Lit 或 Unlit 著色器去做。必須跟遊戲時間同步的效果,走路線一;要讓 Animator state 驅動、而且得在時間軸上跟場景裡其他東西一起看得到的動畫,走路線二。

不管走哪條,都先把精靈表的匯入設定弄對:Wrap Mode 設 Clamp、Mip Maps 關掉、Filter Mode 設 Point,像素圖不要開壓縮。這三個設定搞出來的 flipbook 問題,比著色器裡的數學還多。

路線一:自帶列與行索引的著色器

在 Shader Graph 裡,Flipbook 節點吃 Columns、Rows 和一個由時間驅動的影格索引,輸出重映射後的 UV。手寫著色器的話,把 _Columns、_Rows、_Fps 開成屬性,在 vertex 或 fragment 階段算出儲存格。如果動畫必須跨物件同步,影格索引要用腳本裡的一個 float 來驅動,而不是每個材質各自去取樣 _Time.y,這樣同一個物件上的兩組 flipbook 才不會愈跑愈歪。

路線二:用 Animation 視窗驅動 tiling 與 offset

這條路是對貼圖的 _ST 向量做動畫。Built-in 的 Standard 與 Unlit 用 _MainTex_ST;URP 的 Lit 與 Unlit 用 _BaseMap_ST。Tiling 固定不動,維持在 (1, 1 / rows),只有 offset.y 會變。下面幾個步驟講的是這條路。

1

01

選取材質,打開 Animation 視窗。建立一個 clip,把貼圖的 _ST 向量屬性加成一條 track——它在材質上會以 Vector4 的形式出現。

2

02

把 clip 的 sample rate 設成跟目標 FPS 一樣的數字。sample rate 比你要的 FPS 低的話,關鍵影格會疊到同一個取樣點上,動畫播起來就比預期的慢。

3

03

每一幀只對 offset.y 打一次關鍵影格:若精靈表是第一列在最上面,值為 1 - (row + 1) / rows,tiling 設成 (1, 1 / rows)。

4

04

在曲線編輯器裡,把每個關鍵影格的插值設成 Constant。Linear 會把兩格混在一起,兩個影格同時出現,看起來就是殘影。

5

05

確認最後一個關鍵影格不是重複第一格的值,否則動畫在循環接縫處會多停一格。

6

06

如果有多個 renderer 共用同一份材質,請改成對每個 renderer 各自動畫 MaterialPropertyBlock,不要讀 renderer.material——第一次碰到它就會複製一份材質,而且那份複本會跟著物件活到物件消失為止。

VRChat 上的 Poiyomi flipbook

Poiyomi 著色器的 Flipbook 區塊,是在材質上讓一個 UV 視窗在整張圖上捲動,所以只要一個材質欄位,就能在角色網格上播出動畫精靈。它開放了 Rows、Columns 和影格率,另外還有一個模式讓你自行驅動影格索引,而不是交給著色器時間去跑。

用單列橫條,不要用網格。橫條只給著色器一個軸向可以推進,垂直接縫的問題就完全消失。用網格的話,UV 視窗每走完一列就得往下跳一列,而在那條邊界上,任何濾波、mip 層級或半個 texel 的捨入誤差都會取樣到隔壁那一列,播放時就看得見一條屬於別的影格的細帶。橫條還讓影格索引變成單純的乘法:UV 裡的影格寬度是 1 / frameCount,偏移量就是 frame / frameCount。以 8 個影格、每格 256 x 256 的動畫來說,橫條是 2048 x 256,而且維持在 2 的次方。

在角色上,透明通道比平面場景要緊得多。精靈邊緣是硬的話就用 Cutout 或 Opaque。Transparent 佇列的 flipbook 不寫深度,掛在角色身上時,它跟身體、跟自己之間的排序會隨視角改變。可以的話,把會動的表面放在角色本來就有的材質上——每多一個材質欄位都會扣角色效能等級的分數。影格索引若由帶同步參數的 Animator 狀態驅動,每個 client 看到的影格都一樣;由著色器時間算出來的影格索引是在本機計算,不同觀看者可能停在迴圈裡不同的位置。

1

01

把影格匯出成一條水平橫條。橫條高度等於單格高度,寬度等於影格數乘上單格寬度。尾巴不要多留一格。

2

02

匯入貼圖時 Wrap Mode 設 Clamp、Mip Maps 關掉、sRGB 打開。只有橫條真的帶透明通道時,才勾 Alpha Is Transparency。

3

03

建立一個 Poiyomi 材質,指定給要播動畫的網格,然後打開 Flipbook 區塊。

4

04

Rows 設 1,Columns 設影格數。影格率設成你要的長度——8 個影格搭 8 FPS 就是一秒的循環。

5

05

選影格來源。Manual 模式會露出一個影格索引,讓你用 Animator 狀態去驅動;時間驅動模式則自己循環。

6

06

在 Rendering 底下,硬邊精靈設 Cutout 或 Opaque。Transparent 只留給柔邊效果,而且要預期深度排序會出現一些怪毛病。

7

07

檢查循環:最後一格不能跟第一格重複,否則精靈每循環一次就會凍住一格。

8

08

在已上傳的角色上實測,用鏡子看,近距離看一次,再從 instance 的另一端看一次。mip 與濾波的問題,都是距離拉開之後才浮現。

Godot 與 Phaser 的 flipbook

在 Godot 4 裡,2D 走的是 AnimatedSprite2D 搭配 SpriteFrames 資源。匯入精靈表,打開 SpriteFrames,把 Hframes 與 Vframes 設成欄數與列數,加入這個動畫要用的影格,再把動畫速度設成每秒影格數,Loop 保持開啟。SpriteFrames 把動畫存成一串影格索引,所以網格狀精靈表和單條長條都能用,你這邊完全不必做 UV 運算。

如果只需要一個動畫,單純的 Sprite2D 更輕:Hframes 與 Vframes 設一次,接著用 AnimationPlayer 以 step 插值推進整數的 frame 屬性。不要用預設緩動曲線去 tween frame——那個值是整數索引,插值下去不是被捨成同一個值,就是在兩格之間閃爍。

要在 Godot 裡把 flipbook 貼上 3D 表面,最快的方法是 ShaderMaterial,行列運算跟 Unity 版一樣:uv = UV * vec2(1.0 / cols, 1.0 / rows) + vec2(col / cols, row / rows),row 由影格索引算出來。Godot 的 UV 原點在左下角,跟 Unity 相同,所以第一列排在最上面的精靈表一樣要做垂直翻轉。

Phaser 3 是在載入時就把精靈表切好,只載入一次。frameWidth 與 frameHeight 定義格子大小,影格由左到右、由上到下編號,動畫用編號辨識而不是座標——不必寫著色器,也沒有行列運算可以算錯。

引擎物件要設定什麼
Godot 4AnimatedSprite2D + SpriteFrames依精靈表排版設定 Hframes 與 Vframes,再設定動畫的 FPS 速度並開啟 Loop。
Godot 4Sprite2DHframes 與 Vframes 只設一次,之後由 AnimationPlayer 以 step 插值推進整數的 frame 屬性。
Godot 43D 網格上的 ShaderMaterial在著色器裡傳入欄數、列數與影格索引,UV 重映射與垂直翻轉都跟 Unity 一致。
Phaser 3this.load.spritesheetframeWidth 與 frameHeight;Phaser 由左到右、由上到下切分精靈表。
Phaser 3this.anims.create影格來自 anims.generateFrameNumbers,再加上 frameRate 與 repeat: -1 做循環播放。

在 3D 裡即時翻動精靈表

這一節可以實際遊玩的場景是 The Cyclops' Island,跑在 Neta Studio 的 3D game builder 裡。畫面上的翻頁動畫就是前面講的索引轉 UV,由 GPU 上的 Three.js 著色器算出來——差別在於它貼在真實場景的真實網格上,相機由你操作,燈光會移動。

這件事比聽起來重要。平放的 Unity 場景裡,翻頁動畫通常是正面觀看,接縫滲色和 mip 混色不容易看出來。把相機繞到掠射角去看一個動態表面,問題立刻現形:影格邊緣出現一條鄰格的色帶、最後一層 mip 把兩張影格平均成一團模糊,或是透明通道邊緣從正面看沒問題、從側面看就消失。瀏覽器版本把「做出一張表」到「在表面上看到它」之間的迴圈縮得夠短,不必等建置就能反覆調整。

Mini World 是比較小的場景,適合先拿來測單張精靈表——網格數較少、載入也較快。你手上還沒有精靈表的話,Agent Sprite Forge 可以生出影格;兩個場景跑的是同一條翻頁路徑,所以在 Mini World 看起來正確的表,在 The Cyclops' Island 也會一樣。

Mage cast——4 格的條狀表,正是翻頁著色器要的輸入。

自己做一張精靈表

要做出一張能用的精靈表有三條路:把現成的 GIF 轉過來、把網格圖裁成一條、或是用文字描述生成影格。三條路最後都收斂到同一個檔案——一張 PNG,每個儲存格大小一致,由左到右、由上到下排好,表寬是格寬的整數倍。

轉 GIF 是隱藏工作最多的一條路,因為 GIF 並不是一串完整影格。影格經常只是相對前一張的局部差分,而延遲是逐格以百分之一秒為單位存的。翻頁著色器只吃一種影格率,所以變動的時間必須在匯出前攤平成重複影格。

把網格裁成一條是機械性的工作:留下你要的那一列,把高度裁成單一儲存格,寬度就維持影格數乘上格寬。當一張網格表只當單一動畫使用時,這樣做是對的,因為它拿掉了垂直軸,也拿掉跟著來的接縫。第三條路是從描述生成——Agent Sprite Forge 吃一個動作和影格數,回傳固定格寬的精靈表,省掉一張一張匯出之後再手動對齊的工。

1

01

匯出前先把每個 GIF 影格壓平。把每一格解到畫布上,並讓前一格的內容延續過來;不然你匯出的會是差異矩形,不是畫面本身。

2

02

把時間軸統一。取 GIF 延遲值裡最短的或中位數當基準,來源停留比較久的地方就補上重複影格,這樣精靈表播放起來才是固定 FPS。

3

03

影格由左到右、由上到下排好,每個儲存格尺寸一致。整張精靈表的尺寸要取成儲存格尺寸的整數倍——寬度是 9.5 格的那種表,取樣到的永遠是錯的 texel。

4

04

要裁成單列長條,就把高度裁成一個儲存格,寬度維持在影格數乘上儲存格寬度。來源網格尾巴如果有一格空的,就把它刪掉。

5

05

如果目標引擎不讓你關掉 mipmap,就在每個儲存格周圍留 1 到 2 像素的透明間隙。否則就把 UV 視窗四邊各內縮半個 texel。

6

06

要生成而不是轉檔的話,寫下動作、影格數與朝向,讓 Agent Sprite Forge 回傳一張格線固定的精靈表,而不是一格一格匯出再手動對齊。

7

07

驗證的方法是在引擎裡一次推進一格影格索引。如果某個儲存格的邊緣露出隔壁格的一絲殘影,那是精靈表排版或 UV 內縮錯了,不是著色器的問題。

The Cyclops' Island —— 一段在即時 3D 場景裡貼在表面上播放的翻頁書動畫。

走進它所在的那個世界

精靈是一個無處可站的角色。最後這一步是別人沒做的:把你剛做好的精靈表放進一個有人能走動的世界——一座小島、一顆行星、一個下雨的街角。這個網站上的每個世界都是從一段文字描述生成,發布成一個可以直接玩的頁面。

常見問題

什麼是 Poiyomi 翻頁書?+

那是 Poiyomi 著色器裡的 Flipbook 區塊。它在材質上把 UV 視窗掃過一張精靈表,讓單一材質插槽就能在 avatar 網格上顯示動態精靈。你設定 Rows、Columns 和影格率,影格索引可以跟著著色器時間跑,也可以由 Animator 狀態驅動。建議用單列長條,因為這樣就沒有列與列之間的垂直接縫。

GIF 可以直接做成翻頁書嗎?+

不行。GIF 存的是每一格的延遲,而且常常只存有變動的像素;翻頁書著色器要的是一張貼圖和一個固定的影格率。所以先轉檔:把每一格壓平到前一格上、統一時間軸,再寫進固定的儲存格網格。著色器從頭到尾看不到 GIF。

為什麼我的翻頁精靈表在各幀之間會有顏色滲出?+

取樣器讀到了目前格子範圍之外。把 texture wrap mode 設成 Clamp,硬邊美術把 mipmaps 關掉,UV 視窗每一邊內縮半個 texel。距離越遠滲色越嚴重,因為那正是 mip 鏈開始把相鄰格子平均在一起的地方。

翻頁精靈表該用多少幀率?+

8 到 12 FPS 讀起來像手繪動畫,適合角色精靈;24 到 30 適合火焰、水這類持續性效果。著色器只有一個速率,所以如果原始素材的節奏不固定,就複製影格來對齊,不要想在播放途中改速度。

我需要用網格,還是單列就夠了?+

只要是一段連續播放的動畫,單列就夠,而且少了一個軸向的接縫誤差。網格是給把好幾段動畫塞進同一張貼圖的精靈表用的,著色器或引擎再從中挑一列給待機、跑步或攻擊。如果一張網格精靈表只輸出一段動畫,就把它裁成一條 strip。

翻頁精靈表能用在 Unity URP 的 Lit 著色器上嗎?+

不能直接用——URP Lit 沒有幀索引。用 Shader Graph 的 Flipbook 節點,它吃 Columns、Rows 和一個由時間驅動的索引;或是用固定關鍵影格插值去動 material 的 _BaseMap_ST tiling 與 offset。Built-in 的 Standard 和 Unlit 則是在同樣規則下用 _MainTex_ST。

回到 3D 遊戲建構器

31 個完成的 3D 世界、遊戲與場景全都在同一頁——每一個都能直接玩。

平台: Neta Studio頁面: 翻頁貼圖:如何在 Unity、Godot 與 VRChat 裡做精靈動畫接下來看: /explore/tool/gif-to-flipbook · /explore/tool/gif-to-sprite-sheet · /explore/guide/sprite-sheet

開啟精靈素材庫

這個素材庫裡的每一份素材,都是帶透明通道、可直接進遊戲的 PNG,並附上當初使用的提示詞——精靈表資源與免費精靈表,依動作、風格、主題加上標籤。瀏覽與下載都免費。

×