← 部落格
團隊手記閱讀約 12 分鐘

設計水母:形狀、像素與光。

AirJelly 水母不是一張精靈圖。它的鐘形本體由曲線計算而來,像素外觀來自低解析度渲染,而它的光暈則來自一張小型光圖。以下是我們如何打造這些部分,以及它們讓這個角色能在 AI 產品中做些什麼。

Translated by AI

打開水母實驗室,你就會遇見我們的吉祥物在辦公室裡游動,身後拖著串珠狀的觸手,地板上還有一片彩色光斑。它看起來像一張精靈圖,但其實是在頁面執行時從頭繪製出來的。

鐘形本體的形狀、臉、觸手、像素顆粒感與光暈全都來自一小組參數。每一個參數都以滑桿呈現,因此本文所談的內容,都對應到你能在實驗室裡親自試玩的控制項。

  • 鐘形本體是逐列計算出來的。五種鐘形使用五條不同的寬度曲線;一張臉每隻眼睛只需要一到三個像素。
  • 整個世界會先渲染到一個尺寸為視窗四分之一的畫布上,再在不做平滑處理的情況下放大回來。
  • 光暈來自一張十六分之一比例的光圖,搭配平方衰減、分階的光照等級,以及一個 4×4 抖動矩陣。

這裡的每一張圖都是實驗室的截圖。我們從頭到尾都使用同一隻蜜桃色水母,並套用作者手繪的臉部預設。沒有任何一張是為了這篇文章而假造的。

1. 角色是一個函式

像素藝術角色通常是從影像編輯器開始的。我們的角色則始於一個問題:對於「一隻水母」,最小的描述是什麼,而且還能讓人做出與我不同的版本?

答案原來是一條寬度曲線。鐘形本體是從上到下逐列繪製的,而鐘形、圓頂、團塊、燈籠與水滴之間唯一不同的地方,就是那個說明每一列有多寬的函式。圓頂張開得快,肩部柔和;團塊在中間最寬,兩端收窄;燈籠在最初六分之一處張開,並在最後五分之一處收攏;水滴大約在往下四分之三處達到全寬,然後變窄;而單純的鐘形則是一道筆直的喇叭口。

AirJelly 像素水母,鐘形身體,在保持相同蜜桃色調與臉部的情況下比較鐘形寬度曲線。鐘形
AirJelly 像素水母,圓頂形身體,在保持相同蜜桃色調與臉部的情況下比較鐘形寬度曲線。圓頂
AirJelly 像素水母,圓潤團塊形身體,在保持相同蜜桃色調與臉部的情況下比較鐘形寬度曲線。團塊
AirJelly 像素水母,燈籠形身體,在保持相同蜜桃色調與臉部的情況下比較鐘形寬度曲線。燈籠
AirJelly 像素水母,水滴形身體,在保持相同蜜桃色調與臉部的情況下比較鐘形寬度曲線。水滴
五種身體類型,共用一組滑桿。只有寬度曲線不同。(此處把鐘形高度調高,好讓曲線顯現;作者自己的鐘形則是又矮又扁。)

曲線底下有兩條小規則。「圓潤度」滑桿(1–4)決定頂冠要花幾列才張開。每一列的寬度也都是奇數。那一列中央欄位比聽起來更重要:眼睛和嘴巴可以正好落在正中央,而不是偏半個像素。

從這裡開始,身體其餘部分就順勢而生。寬度、頂部寬度、高度、圓潤度與壓扁程度是五個滑桿。因為形狀是計算出來的而非儲存的,鐘形本體還能變形:當你拖曳它時,你抓住的那一列會比離游標較遠的列傾斜得更多。就是這條規則,讓它感覺像柔軟的橡膠,而不是一張僵硬的卡片。

AirJelly 像素水母在拖曳前的靜止狀態。靜止時
AirJelly 像素水母被從靠近頂部處拖曳,身體傾斜、觸手拖曳在後。抓住靠近頂部處
AirJelly 像素水母被從靠近下緣處拖曳,顯示抓取點周圍的變形。抓住靠近下緣處
向右拖曳,分別抓住靠近頂部與靠近下緣處。鐘形本體傾斜、觸手拖曳在後,水流還留下尾跡——全都是拖曳過程中的真實畫面。

旋轉的處理方式相同,但方向相反:與其把像素往前旋轉(那會留下空洞),渲染器會走過旋轉後的邊界框,把每個螢幕格子映射回鐘形空間,然後問「這在r列裡面嗎?」。採最近鄰取樣、不做反鋸齒,所以傾斜的水母和直立的一樣銳利。

臉

眼睛位於鐘形本體高度的 54% 處,間距為鐘形本體寬度的 18%。這兩個比例就是整個版面系統。表情接著就只是哪一到三個像素被點亮:一個點、一條短橫、一個^,或是一個往上或往內挪一格的點。好奇是一隻眼睛比另一隻略高。卡住是兩隻眼睛都往鼻子擠。想睡是兩條短橫。暈眩——在 850 毫秒內連續三次急轉時觸發——是兩條寬短橫加上一陣搖晃。

AirJelly 像素水母,中性點狀眼睛,展示其內建的像素表情系統。中性
AirJelly 像素水母,好奇表情、雙眼高低不一,展示其內建的像素表情系統。好奇
AirJelly 像素水母,眉毛傾斜、表情專注,展示其內建的像素表情系統。專注
AirJelly 像素水母,咧嘴大笑表達開心,展示其內建的像素表情系統。開心
AirJelly 像素水母,嘴巴平直表達卡住,展示其內建的像素表情系統。卡住
AirJelly 像素水母,嘴角上揚的得意笑容,展示其內建的像素表情系統。得意
AirJelly 像素水母,閉眼、一臉想睡,展示其內建的像素表情系統。想睡
AirJelly 像素水母,放鬆的水平眼睛、表情平靜,展示其內建的像素表情系統。平靜
AirJelly 像素水母,眼睛睜大、嘴巴張開表達驚訝,展示其內建的像素表情系統。驚訝
AirJelly 像素水母,動作時使用的暈眩狀態臉,展示其內建的像素表情系統。暈眩
九種表情加上暈眩臉(關閉手繪臉部,好讓內建眼睛顯現)。每一種都是每隻眼睛一到三個像素,外加一個可選的嘴巴,位於下方兩列處。

讓臉生動的不是像素,而是它們看往哪裡。眼睛是身體上唯一擁有自己動作規則的部位:它們會朝你拉扯鐘形本體的反方向偏移一個像素,而當你用鍵盤游動時,它們會先於身體轉入轉向。嘴巴只在水母說話或表達情緒時出現;說話時,它會隨著快速的 sine 波開合。這些全都不需要繪製任何一格動畫。

觸手

沒有口腕,也沒有觸手精靈圖。每條觸手都是一串方形珠子,以 Verlet 積分計算,錨定在鐘形本體往下 28% 處,每幀進行三次距離約束迭代,讓珠子維持在它們的「間隙」距離。讓它們感覺身在水下的是三條不同頻率的 sine 波——緩慢的漂移、較快的擺動,以及偏向尾端的捲曲——所以根部保持平靜,而末端則擺盪。把游標移過它們,它們會分開並打旋;停下來,它們會以自己的節奏安定下來。

觸手滑桿(數量 3–12、節段 4–18、重力、摩擦力、張力、水流、捲曲、避讓)直接開放,而四個一鍵「動作預設」——靜止、活潑、狂野、漂移——不過就是那六個數字的四組已儲存組合。

顏色

預設隨附七組三色調色盤——鐘形本體、觸手、眼睛——外加每項的自由選色器:

調色盤鐘形本體觸手眼睛
海洋#8ae2ff#8ae2ff#303030
日落#ffb38a#ff8fc7#3a2a2a
青檸#b6f5a0#8ce6c0#26331f
葡萄#c7a2ff#a98cff#2c2640
玫瑰#ff9ec4#ffc0dd#3a2630
金色#ffe08a#ffd06a#3a3320
單色#cfd8e3#aab4c2#20242b

鐘罩顏色不只是替鐘罩上色。引擎會把它寫進頁面的強調色變數(並把觸手顏色寫進次要強調色),因此整個實驗室介面都會跟著你正在製作的水母重新換上主題色。黑、白、灰與語意紅永遠不變;其他一切則跟著你走。而且,正如我們會在第三部分看到的,它也會成為你所攜帶之光的顏色。

如果程序化鐘罩還不夠,你可以把它關掉,在 6×6 到 28×28 的格子上自己畫,並在上面畫上最多八張自訂臉孔。我們有十三位隊友正是這麼做的;他們的水母現在以 NPC 的身分住在辦公室場景裡,每一隻都有自己的身體、觸手數量、表情和對白。

2. 像素外觀是一種解析度

在網路上做出「像素外觀」最常見的方法,是以完整解析度渲染再後製——把顏色吸附到固定值、疊上一層網格,也許再加上模糊並重新量化。我們做的正好相反:這個世界實際上非常小。它被畫進一個只有視窗四分之一大小的畫布——在預設縮放下,一個世界格等於四個螢幕像素——然後告訴瀏覽器在不平滑的情況下把它放大。

引擎裡的每一次繪製呼叫,都會把打包好的 32 位元顏色值直接寫進那個小緩衝區——鐘罩、觸手珠、地形磚,甚至文字。世界圖層裡完全沒有任何向量路徑,所以沒有任何東西會意外被反鋸齒。位置在每個錨點都會對齊格線:鐘罩頂端、眼睛那一列、每一塊地形磚、光照格線所用的相機偏移。

這對「像素大小」滑桿有個很好的結果。因為它改變的是一個世界格涵蓋多少螢幕像素(4 × 0.7 到 4 × 1.6),它會一次同時改變畫面的顆粒以及水母的視覺大小。沒有另外的縮放。

像素大小縮放 0.7 的 AirJelly 水母,顯示更細的像素與辦公室場景中更小的角色。縮放 0.7
像素大小縮放 1.0 的 AirJelly 水母,顯示參考的像素顆粒與辦公室場景中的角色大小。縮放 1.0
像素大小縮放 1.6 的 AirJelly 水母,顯示更粗的像素與辦公室場景中更大的角色。縮放 1.6
像素大小滑桿在 0.7、1.0 與 1.6 時的樣子。顆粒與角色大小會一起改變——後面的房間文字也會變得更粗。

兩塊畫布與一層真正的文字

我們特別喜歡的一個結構決定:這個世界是兩塊畫布,中間夾著一層 HTML。房間地板與標題畫在底層畫布上。住在辦公室裡的部落格風格文案,則是疊在其上的普通、可選取、無障礙的 HTML。頂層畫布——水波紋、物品、傳送門、水母本身——讓每個未觸及的格子保持透明,所以文字能穿過空無一物的水被讀到,而水母則游過其上。

AirJelly 水母與水像素疊在真正的 HTML 下載標題、段落與連結上,展示雙畫布構圖。首頁 · 水母疊在真正的 HTML 文案上
首頁房間。標題、段落與連結是夾在兩塊畫布之間的普通 HTML;水母與水像素則畫在它們之上。沒有任何字形被點陣化。

結果是文字感覺沉進場景裡,而不是貼在上面。背景畫布也會被快取:只有當相機移動整整一格或場景改變時,它才會重繪。

用抖動,不用漸層

當畫面需要漸層時——主要是在光照上——我們使用有序抖動,而不是平滑插值。一個 4×4 的 Bayer 矩陣,以世界座標為索引,讓圖案在你捲動時保持不動,把光拆成平坦的色帶,然後把接縫打散。「光照階數」以滑桿呈現(2–8,預設 3)。在 2 時,地板看起來像雙色木刻版畫;在 8 時,它接近平滑。3 則是它看起來仍像手工製作的樣子。

光照階數為 2 的 AirJelly 辦公室場景,展示發光水母周圍強烈的木刻般對比。2 階
光照階數為 3 的 AirJelly 辦公室場景,展示發光水母周圍預設的分階光照層次。3 階(預設)
光照階數為 8 的 AirJelly 辦公室場景,展示發光水母周圍更平滑的光線過渡。8 階
光照階數為 2、3 與 8 的辦公室(水母光暈調強,讓地板看得清楚)。2 是木刻版畫;8 幾乎平滑;3 是預設值。

色彩分級——全域色調與飽和度調整——會經過一張 32×32×32 的查找表,這會順帶把調色盤悄悄量化成每通道五位元。介面裡的每個預覽畫布都關閉平滑;引擎唯一使用 CSS 模糊的地方是介面轉場,從不用在世界本身。

3. 光暈是一個光場

不會發光的水母就不是水母。顯而易見的實作方式——在場景上混合一層柔和的放射狀光暈,或做一次模糊處理——第一天就被排除了,因為它會產生平滑、次像素的漸層,在硬像素格線上顯得很不對。平滑的光暈會浮在世界之上;它不屬於這個世界。

所以我們不畫光暈,而是照亮房間。每一盞光——水母、檯燈、發光物品——都會被蓋進一張小光照圖,解析度是畫布的四分之一(螢幕的十六分之一),並帶有(1 − d/r)²衰減:不是物理上正確,而是柔和、便宜且容易調整。地板與家具接著會依照有多少光到達它們而被提亮,用的是同樣三個平坦階數與同樣的抖動。我們保留兩張圖——一張給地板,一張給物件,半徑寬 1.6 倍,所以道具會落在比其下方地面稍大的光暈裡。

光照合成關閉的 AirJelly 辦公室場景,提供水母光照比較的基準。光照合成關閉
光照合成開啟的 AirJelly 辦公室場景,展示水母周圍地板上的蜜桃色光線與抖動色帶。光照合成開啟
同一地點在光照處理關閉與開啟時的樣子。開啟時,地板呈現色帶與抖動,水母的蜜桃色光落在周圍一切上——水母本身並沒有畫上光暈。(光暈半徑與強度調強,讓它在頁面上看得清楚。)

著色處理會把每個像素乘以環境光加上光場,並限制在一個上限。那個上限——1.25,意思是光可以把顏色推到自己本身的 125%,不能再多——就是全部的「泛光」預算。光暈核心稍微過曝就是從這裡來的,也是燈籠與鏡頭光斑之間的差別。

有三個實作細節很容易被忽略:

  • 鐘罩顏色也是光的顏色。挑一隻玫瑰色水母,附近地板就會轉為玫瑰色;同一個十六進位值也會更新實驗室的強調色。
  • 顏色會以其峰值通道正規化,所以深藏青色的鐘罩仍然會投出全強度的藏青色調,而不是幾乎照不亮房間。
  • 閃爍每 8 幀更新一次。輕微的頓挫比完美平滑的微光更適合這個像素世界。

還有一條規則讓檯燈不會看起來不對:任何會發光的東西都被排除在受光之外。帶有發光元件的物品會以自發光遮罩繪製,著色處理會原封不動地複製那些像素——所以檯燈永遠不會被自己的環境光變暗。

旋鈕

所有這些都暴露在「場景 → 光照」面板中。這些是我們最後出貨的數字:

控制項範圍預設值
環境亮度0.2 – 10.55
環境顏色—#6d8fae
光照階數2 – 83
高光上限1 – 1.51.25
水母光暈 · 半徑8 – 8032
水母光暈 · 強度0 – 1.50.6
水母光暈 · 閃爍0 – 0.60.05

地板只有在關卡、著色器參數或光源位置改變時才會重新著色;視埠外的光源在蓋印前會被剔除;分級表會被快取。實務上,整個世界——背景、水體模擬、物品、觸手、光照、水母——在筆記型電腦上能輕鬆塞進單一動畫幀,而且在一個 1440px 寬的視窗只有 360 格寬的解析度下。

4. 關於它如何移動的幾件事

嚴格說來不是關於外觀,但它們塑造了角色的讀感,所以簡短說明:

  • 鍵盤推力會累積並衰減,上限略低於每幀一格。速度也會影響鐘罩變形,所以快速游動時會明顯看到它被「水流」壓扁。
  • 滾輪一格會把衝量送進與鍵盤相同的速度中,換算下來等於一格的穩態移動距離。
  • 相機會在你游動時緩慢跟隨,穩定下來時跟得更快,而在拖曳後會非常緩慢地跟隨 1.6 秒,以免它猛然彈回。
  • 水平速度會設定一個目標傾斜角。一條僵硬、有阻尼的彈簧會跟隨它,並讓鐘罩在停止時稍微過衝。
  • 游標會在 40 px 內把觸手珠推開。在 58 px 內,它還會加上漩渦與放射狀脈動,並偏向尾部。
  • 每移動兩格,就會在 2D 波浪模擬中加入一個柔和的脈衝。這些波浪可以觸發道具,而道具又能把脈衝繼續傳遞下去。

5. 為什麼要用資料來製作角色

我們並不是從一套關於 AI 角色的理論開始的。我們從一個水母編輯器開始,然後注意到一個實際的優勢:讓角色可自訂的那些參數,同時也讓它對 agent 而言易於理解。

參數對 agent 來說更容易使用

精靈圖對模型來說是不透明的。我們的水母大約是三十個數字和幾個列舉值,小到足以放進一個提示詞、一個 JSON blob 或一次工具呼叫。

  • agent 可以讀取參數,知道它的使用者那隻水母長什麼樣子。
  • 它可以把「幫我做一隻有長觸手的愛睏紫色水母」變成一個預設組合,而不必生成一張新圖像。每位隊友的 NPC 都是一列資料,而不是一項美術資產。
  • 表情、發光和動作都是執行時期的狀態,所以吉祥物可以在 agent 工作時看起來專注,或在它搞混時看起來頭暈。
  • 這些參數可以有很寬的變化範圍,而結果仍然看起來像是同一種水母。

這就是對 AirJelly 有用的部分。吉祥物可以扮演 agent 的身體,因為它的外觀和行為都是 agent 可以讀取和更改的資料。我們永遠不必要求模型去編輯一張不透明的圖像。

為什麼我們仍然喜歡像素(和 ASCII)

有一個實際的理由。像素精靈是一小格一小格的調色盤索引;ASCII 藝術本身就已經是文字。兩者都可以被精簡地描述,並逐格編輯。對於我們需要的精確修改,16×16 的精靈圖留給模型即興發揮的空間,就是比一幅畫作來得少。

還有一個更主觀的理由:我們喜歡硬性的網格。它們坦率地承認自己是由機器製作的,而一個像素的錯誤往往看起來很討喜,而不是詭異。這種調性很適合一個關於 AI 住在你電腦上的產品。這或許也是為什麼終端機在 AI 工具裡讓人感到自在。

所以這就是我們最後定案的地方:一個由數字描述、畫在網格上的角色。它剛好看起來很復古,但真正有用的部分,是它對人和 agent 來說都很容易精確地修改。

最後一件事。如果你在實驗室裡調出一隻你喜歡的水母,把它的參數匯出並寄到xiaohua@airjelly.ai——我們會把它放上網站。辦公室裡已經住著十三隻了;永遠還有空間再多一隻。