24 條測試全過,她的腿還是歪的

幫格莉奇做會動的身體那晚,AI 沒有重畫腿,寫了一段程式把畫好的腿拉寬壓窄。測試全綠,量出來的寬度跟參考差不到幾像素,畫面上的小腿是壞的。同一晚,它又把一張本來畫對的手改壞一次。

三欄對照:左邊三視圖參考,中間 0.4.3 用寬度帶拉出來的腿,右邊 0.4.4 重新產圖的腿

9 月 10 日晚上十一點多,格莉奇的骨架推到 0.4.3。AI 的回報寫著:單元測試 24/24,兩腿取樣八個點,除了故意收細的那一格,每一格跟參考圖都在 4 像素以內。

我回的第一句是:

手不對 腿也不對 你應該要用codex images 生成 不是你自行去編輯圖

接著又補了四句:「手掌跟腿都是有問題的」「小腿整個都壞掉了」「手掌要參考原圖手掌」「大腿被綁太緊了 你這樣很怪」。

兩分多鐘後,那段拉腿的程式被撤掉。上面那張圖的中間,就是當時的腿。

先說她是誰、那晚在做什麼

格莉奇是我的角色,一個只有 4KB 記憶體的 AI 機器人女孩,她有一台開在瀏覽器裡的桌面,寫在讓一個 4KB 記憶體的 AI 開口講電話。

那陣子在幫她做一副會動的身體:把她拆成 21 個部件,在瀏覽器裡組起來,用 WebGL 畫,講話時嘴型跟著聲音動。程式全是 AI 寫的。我出範圍,在對齊工具裡把每個部件的位置、角度、大小調到我覺得對,然後看成品。

那一晚的目標是比例:組出來的她,要對上一張正面三視圖。

它沒有重畫,它寫了一段程式去拉

前一版收掉了袖子、手、大腿幾個問題,腿還剩三個形狀不對:大腿太細;腿套上緣跟底下的布一樣寬,看不出有束住;右腿的吊襪帶平貼在皮膚上。

前兩版遇到形狀問題,都是回去重新產圖。0.4.3 沒有。它在算網格怎麼變形的地方加了七行,叫「寬度帶」:在腿上指定一段高度,把那一段左右拉寬或壓窄。它當時給的理由是:之後要「再粗一點」,只要改一個數字,不用再等八分鐘產圖。

for (const band of part.bands || []) {
  // Local width edits along one part: a fuller thigh, a cuff that grips, a strap mark.
  const { top, bottom, scale, feather = 24, center = cx } = band;
  let weight = clamp(Math.min(y - top + feather, bottom + feather - y) / feather, 0, 1);
  weight *= weight * (3 - 2 * weight);
  x = center + (x - center) * (1 + (scale - 1) * weight);
}

大腿拉到 1.09 倍,腿套束口壓到 0.85 倍。吊襪帶那一圈壓到 0.93,上下兩邊各鼓一點,想用「一縮兩鼓」做出勒進肉裡的樣子。

有點像修圖軟體的液化:不重畫,直接把畫好的腿一段推寬、一段擠窄。

測試跟量測都說對了

那一筆同時加了一條測試,四句斷言:大腿比原圖寬、束口比原圖窄、勒痕那一列比上面窄、鞋子完全不動。

四句都成立。帶子確實照指令把那幾段拉寬、壓窄了,所以全綠。取樣的寬度也是真的接近參考,因為拉的就是寬度。

畫面上壞掉的是別的東西。一張畫好的腿,輪廓線跟陰影都是照原本那個寬度畫的,橫向一拉,線條跟著被拉長拉細,陰影的漸層也歪了。測試問的是帶子有沒有照指令拉,取樣量的是寬度,這兩樣都量不到線條跟陰影。我回的那幾句,講的都是畫面。

撤掉的時候,寬度帶整條路一起退:那七行刪掉、那條測試拿掉、為了拉而加密的網格也退回去。0.4.3 其他的修正,脖子、袖紋那些,都留著。bands 這個欄位之後再也沒回來過。撤回的 commit 訊息留了一句:

寬度帶是把畫好的圖拉寬拉窄,不是把圖畫對。

回去重畫,但把尺寸寫死

0.4.4 回到產圖那一步。這次把要的形狀直接寫成數字放進提示詞:腿從上到下取六個高度,每個高度寫死左右腿各要多寬;畫布大小、鞋底那條線在哪,一格都不准動。

產圖沒有照數字畫得剛好。第一輪出來,有幾列寬度最多差 24 像素,羅紋上緣也掉了大約 8 像素。第二輪把每一列改寫成左右端點的座標再試一次,沒有比較準,最後用的還是第一輪那張。

這一版量出來的八個點,除了故意收細的那一格,也都在 5 像素以內。光看數字,兩版都過。差別在畫面:0.4.4 的腿,我看了說的是「這個腿應該可以」。

產出來的圖沒有透明通道,每張素材都站在一片純綠底上,載入時才算出透明度。底色怎麼選會決定後面有多難,那件事另外寫過一篇:整件深紅披風被我自己的去背腳本吃掉。

同一晚,它又改壞一張手

手的事是我起的頭。更早一點我說過兩隻手掌大小不一,要參考原圖的手。0.4.4 量出左手比三視圖大,就叫產圖把它縮成小孩的手,畫出來是一隻短胖的手。

那張手的圖,從 0.4.1 到 0.4.3 其實都畫對了:修長、微張、指尖收尖。0.4.3 的手不對,是 rig 把它縮得太小,不是圖畫錯。

我看到 0.4.4 的手,問的是:「原圖手修長 你現在為什麼會那麼短呢」。過一分多鐘又補了一句「……我還不如0.4.2版呢」。它回:

你是對的,我把本來對的東西改壞了。

0.4.5 把那張手換回 0.4.1 的原圖,只補一條被咬掉的橫紋跟一個方形扣環,手的區域只有 4363 個像素有變。

AI 那晚記下的是:動手改之前,先確認那個東西真的錯了。它信了自己的量測,沒有信原圖。

那條線劃在哪

rig(把部件組起來、決定怎麼擺怎麼動的那份設定)也不是完全不能碰形狀。0.4.2 加的兩個參數到今天還在:一個把脖子連同頸環縮回頭的比例,一個把下巴加寬一點、不要那麼尖。數學跟寬度帶幾乎一樣。

差別在改的是什麼。頸環變粗、脖子露太少,是因為上衣整片被放大到 1.14 倍,那是擺放造成的失衡,參數只是把擺歪的東西擺回去。腿上要的是厚度、收緊的布、一圈勒痕,這些圖上本來就沒畫,拉也拉不出來。

所以規則寫成這樣:

這條當晚就收進了 AI 的工作記憶,後面幾版動到素材形狀都照這條走。

0.4.6 又撞到一個變體。0.4.4 給吊襪帶壓的勒痕做過頭,我看了說「右大腿的金屬壓痕不舒服」。要拿掉它的時候,自架的那台產圖服務只吐 1024×1536 的圖,整張腿送過去會被拉成別的比例,實測 24% 的像素會位移,等於又拉了一次,所以那張沒用。最後改成只把右大腿那一塊照原始像素裁出來重畫,再一比一貼回去,全程不重新取樣,左腿零變化。

順帶一提:為什麼不用 Live2D

九月初其實走過 Live2D:原圖切成 34 層,在 Wine 上跑官方的 Cubism Editor,匯出成模型。最後沒有留在那條線上,撞到的是這三件:

換成這副骨架之後,素材重新產、比切原圖大得多,rig 就是一份 JSON,改完重新整理就看得到。repo 也從 glitch-l2d 改名成 glitch-2d,因為它已經不是 Live2D 了。

自己去看

全身展示頁:https://yazelin.github.io/glitch-2d/。比例對齊小工具:https://yazelin.github.io/glitch-2d/align/,部件全部拖得動。舊的 Live2D 版本還在 https://yazelin.github.io/glitch-2d/legacy/,兩個開起來對著看就知道差在哪。

隔天凌晨,這副骨架被掛上格莉奇的站當桌寵,那一段寫在桌寵換成會動的骨架,開心那一格反而最看不出來。