クラゲラボを開くと、オフィスの中を泳ぐ私たちのマスコットに出会えます。後ろにはビーズ状の触手が連なり、床には色とりどりの光の斑点が映っています。スプライトのように見えますが、ページが動いている間にゼロから描画されています。
傘の形、顔、触手、ピクセルの粒状感、そして輝きは、すべて少数のパラメータから生まれます。それぞれがスライダーとして公開されているので、この記事はラボで自分で試せるコントロールに沿って進みます。
- 傘は行ごとに計算されます。5つの傘の形は5つの異なる幅の曲線を使い、顔は片目あたりわずか1〜3ピクセルしか必要としません。
- 世界はビューポートの4分の1のサイズのキャンバスにレンダリングされ、その後スムージングなしで引き伸ばされます。
- 輝きは、2乗の減衰、段階的な光レベル、4×4のディザマトリックスを備えた1/16スケールのライトマップから生まれます。
ここにあるすべての画像はラボからのスクリーンショットです。記事全体を通して同じピーチ色のクラゲを使い、著者の手描きの顔プリセットを適用しました。記事のために何かをでっち上げたものはありません。
1. キャラクターは関数である
ピクセルアートのキャラクターは通常、画像エディタから始まります。私たちの場合は、ある問いから始まりました。「クラゲ」を最も小さく記述しつつ、誰もが自分のクラゲを私のものとは違う見た目にできるようにするには、どうすればよいか?
答えは幅の曲線であることが分かりました。傘は上から下へ1行ずつ描かれ、ベル、ドーム、ブロブ、ランタン、ドロップレットの間で変わるのは、各行の幅を決める関数だけです。ドームは柔らかな肩で素早く開き、ブロブは中央が最も広く両端がくびれ、ランタンは最初の6分の1で開いて最後の5分の1で引き締まり、ドロップレットは約4分の3まで下がったところで最大幅に達してから狭まり、普通のベルはまっすぐに広がります。
曲線の下には2つの小さなルールがあります。「丸み」スライダー(1〜4)は、冠が広がるまでに何行かかるかを決めます。また、すべての行の幅は奇数です。この中央の列は、聞こえる以上に重要です。目と口を半ピクセルずらすのではなく、ど真ん中に配置できるからです。
ここから体の残りの部分が決まります。幅、上部の幅、高さ、丸み、つぶれ具合の5つのスライダーです。形は保存されるのではなく計算されるため、傘は変形することもできます。ドラッグすると、つかんだ行がカーソルから遠い行よりも大きく傾きます。この単一のルールが、硬いカードではなく柔らかいゴムのように感じさせるのです。
回転も同じように扱われますが、逆方向です。ピクセルを前方に回転させる(穴が残ってしまう)代わりに、レンダラーは回転した境界ボックスを走査し、各スクリーンセルを傘の空間に逆写像して「これは行rの内側か?」と問い合わせます。最近傍法でアンチエイリアスなしなので、傾いたクラゲも直立したものと同じくらいくっきりしています。
顔
目は傘の高さの54%に位置し、間隔は傘の幅の18%です。この2つの比率がレイアウトシステムのすべてです。表情は、どの1〜3ピクセルを点灯させるかだけです。点、ダッシュ、^、または1ピクセル上や内側にずらした点です。好奇心は片目がもう片方よりわずかに高い状態。困惑は両目が鼻の方に寄った状態。眠そうは2本のダッシュ。めまいは——850ミリ秒以内に3回の急な方向転換でトリガーされる——2本の幅広いダッシュとぐらつきです。
顔を引き立てるのはピクセルではなく、どこを見るかです。目は体の中で唯一、独自の動きのルールを持つ部分です。傘を引っ張る方向とは逆に1ピクセル動き、キーボードで泳いでいる間は体より先に曲がる方向へ向きます。口はクラゲが話したり感情を表したりするときだけ現れ、話している間は速いサイン波で開閉します。これらはどれもアニメーションの1フレームも描く必要がありませんでした。
触手
口腕も触手のスプライトもありません。各触手はVerlet積分による四角いビーズの連鎖で、傘の28%下を基点とし、ビーズを「間隔」距離に保つためにフレームごとに3回の距離制約パスを実行します。水中にいるように感じさせるのは、3つの異なる周波数の3つのサイン波です——ゆっくりした漂流、より速い揺れ、尾に向かって重み付けされたカール——なので、根元は穏やかで先端は波打ちます。カーソルをそれらの中を動かすと分かれて渦を巻き、止めるとそれぞれのペースで落ち着きます。
触手のスライダー(本数3〜12、セグメント4〜18、重力、摩擦、張力、流れ、カール、回避)は直接公開されており、4つのワンクリック「モーションプリセット」——静止、活発、ワイルド、漂流——は、それら6つの数値の保存された組み合わせにすぎません。
色
デフォルトで7つの3色パレット(傘、触手、目)が用意され、さらにそれぞれに自由形式のカラーピッカーがあります:
| パレット | 傘 | 触手 | 目 |
|---|---|---|---|
| オーシャン | #8ae2ff | #8ae2ff | #303030 |
| サンセット | #ffb38a | #ff8fc7 | #3a2a2a |
| ライム | #b6f5a0 | #8ce6c0 | #26331f |
| グレープ | #c7a2ff | #a98cff | #2c2640 |
| ローズ | #ff9ec4 | #ffc0dd | #3a2630 |
| ゴールド | #ffe08a | #ffd06a | #3a3320 |
| モノ | #cfd8e3 | #aab4c2 | #20242b |
鐘の色は鐘を彩るだけにとどまらない。エンジンはその色をページのアクセント変数に(そして触手の色をセカンダリのアクセントに)書き込むので、ラボのUI全体が、あなたが作っているクラゲに合わせてテーマを変える。黒、白、グレー、そしてセマンティックな赤は決して変わらず、それ以外はすべてあなたに追従する。そしてパート3で見るように、その色はあなたが運ぶ光の色にもなる。
プロシージャルな鐘では物足りないなら、それをオフにして6×6から28×28のグリッドに自分で描き、さらに最大8つのカスタムフェイスを上に描くことができる。チームメイトの13人がまさにそうした。彼らのクラゲは今、オフィスシーンにNPCとして住んでおり、それぞれが独自の体、触手の数、表情、そしてセリフを持っている。
2. ピクセルルックは解像度である
ウェブで「ピクセルルック」を得る最も一般的な方法は、フル解像度でレンダリングしてポストプロセスをかけることだ。色をスナップし、グリッドオーバーレイを重ね、場合によってはぼかして再量子化する。私たちは逆をやった。世界は実際に小さいのだ。ウィンドウの4分の1のサイズのキャンバスに描画され(デフォルトのスケールでは、世界の1セルがスクリーンの4ピクセル)、ブラウザにはスムージングなしで引き伸ばすよう指示される。
エンジン内のすべての描画呼び出しは、パックされた32ビットの色値をその小さなバッファに直接書き込む。鐘も、触手のビーズも、地形タイルも、テキストさえも。ワールドレイヤーにはベクターパスが一切ないので、何かが誤ってアンチエイリアスされることはない。位置はすべてのアンカーでセルグリッドに丸められる。鐘の頂点、目の行、各地形タイル、ライティンググリッドが使うカメラオフセット。
これは「ピクセルサイズ」スライダーにとって嬉しい結果をもたらす。世界の1セルがスクリーンの何ピクセルを覆うか(4 × 0.7 から 4 × 1.6)を変えるので、画像の粒状感とクラゲの見かけのサイズの両方を一度に変えるのだ。別個のズームは存在しない。
2つのキャンバスと本物のテキストのレイヤー
私たちが特に気に入っている構造上の決定が1つある。世界は2つのキャンバスで、その間にHTMLレイヤーが挟まれている。部屋の床とタイトルは下のキャンバスに描かれる。オフィスの中に住むブログ風のコピーは、その上にある普通の、選択可能で、アクセシブルなHTMLだ。上のキャンバス(水面の波紋、アイテム、ポータル、クラゲ自体)は触れていないすべてのセルを透明のままにするので、テキストは空の水を通して読めるが、クラゲはその上を泳ぎ渡る。
その結果、テキストはシーンの上に貼り付けられたのではなく、シーンに沈み込んでいるように感じられる。背景キャンバスもキャッシュされる。再描画されるのは、カメラが1セル分動いたとき、またはシーンが変わったときだけだ。
グラデーションではなくディザ
画像にグラデーションが必要な場所(主にライティング)では、滑らかな補間ではなく順序ディザリングを使う。4×4のBayerマトリクスをワールド座標でインデックスするので、スクロールしてもパターンはその場にとどまる。これが光をフラットなバンドに分割し、その継ぎ目を散らす。「光のステップ」はスライダーとして公開されている(2〜8、デフォルト3)。2では床は2トーンの木版画のように見え、8ではほぼ滑らかになる。3はまだ手作りに見えるところだ。
カラーグレーディング(グローバルな色合いと彩度の調整)は32×32×32のルックアップテーブルを通る。これは副作用として、パレットをチャンネルあたり5ビットに静かに量子化する。UI内のすべてのプレビューキャンバスはスムージングがオフになっており、エンジンがCSSブラーを使う唯一の場所はUIのトランジションであり、ワールドには決して使わない。
3. 輝きは光のフィールドである
光らないクラゲはクラゲではない。明白な実装——シーンの上にブレンドされた柔らかな放射状のハロー、あるいはブラーパス——は初日から却下された。なぜならそれは滑らかなサブピクセルのグラデーションを生み出し、ハードなピクセルグリッドの上では間違って見えるからだ。滑らかなハローは世界の上に浮かぶのであって、世界に属してはいない。
だから輝きを描く代わりに、私たちは部屋を照らす。すべての光——クラゲ、ランプ、光るアイテム——は、キャンバス解像度の4分の1(スクリーンの16分の1)の小さなライトマップに、(1 − d/r)²の減衰でスタンプされる。物理的に正確ではないが、柔らかく、安価で、調整しやすい。床と家具は、どれだけ光が届くかによって明るくされ、同じ3つのフラットなステップと同じディザで処理される。2つのマップが保持される。1つは床用、もう1つは半径が1.6倍広いオブジェクト用で、小道具はその下の地面より少し大きなハローの内側に収まる。
シェーディングパスは各ピクセルをアンビエントとライトフィールドの積で乗算し、上限でクランプする。その上限——1.25、つまり光は色をそれ自体の125%まで押し上げられるが、それ以上は無理——が「ブルーム」の予算のすべてだ。輝きのやや露出オーバーなコアはここから来ており、ランタンとレンズフレアの違いでもある。
見落としやすい実装の詳細が3つある。
- 鐘の色は光の色でもある。バラ色のクラゲを選べば、近くの床はバラ色になる。同じ16進値がラボのアクセントカラーも更新する。
- 色はそのピークチャンネルで正規化されるので、濃紺の鐘でも部屋をほとんど照らさないのではなく、フル強度の紺の色合いを投げかける。
- ちらつきは8フレームごとに更新される。完璧に滑らかなきらめきよりも、わずかなカクつきの方がこのピクセルの世界に合っている。
もう1つのルールがランプを正しく見せる。光を放つものは照らされる対象から除外される。光のコンポーネントを持つアイテムは発光マスクで描かれ、シェーディングパスはそれらのピクセルをそのままコピーして通す。だからランプが自身のアンビエントで暗くなることは決してない。
ノブ
これらすべてはScene → Lightパネルで公開されている。これが最終的に出荷した数値だ。
| コントロール | 範囲 | デフォルト |
|---|---|---|
| アンビエントの明るさ | 0.2 – 1 | 0.55 |
| アンビエントの色 | — | #6d8fae |
| 光のステップ | 2 – 8 | 3 |
| ハイライトの上限 | 1 – 1.5 | 1.25 |
| クラゲの輝き · 半径 | 8 – 80 | 32 |
| クラゲの輝き · 強さ | 0 – 1.5 | 0.6 |
| クラゲの輝き · ちらつき | 0 – 0.6 | 0.05 |
床が再シェーディングされるのは、レベル、シェーダーパラメータ、または光の位置が変わったときだけだ。ビューポート外の光はスタンプされる前にカリングされ、グレーディングテーブルはキャッシュされる。実際には、ワールド全体——背景、水のシミュレーション、アイテム、触手、ライティング、クラゲ——は、1440px幅のウィンドウが横にわずか360セルにしかならない解像度で、ラップトップの1アニメーションフレームに余裕で収まる。
4. 動き方についてのいくつかのこと
厳密には見た目の話ではないが、キャラクターの読み取られ方を形作るので、手短に。
- キーボードの推力は蓄積して減衰し、1フレームあたり1セル弱で上限となる。速度は鐘の変形にも影響するので、速く泳ぐと「流れ」に対して目に見えて押しつぶされる。
- ホイールのノッチはキーボードと同じ速度にインパルスを加え、定常状態で1セルの移動になる。
- カメラは泳いでいる間はゆっくり追従し、落ち着いている間はより速く、ドラッグ後は1.6秒間ごくゆっくりと動くので、スナップバックしない。
- 水平方向の速度が目標の傾きを設定する。硬く減衰されたバネがそれに追従し、止まるときに鐘がわずかにオーバーシュートするのを許す。
- カーソルは40px以内で触手のビーズを押しのける。58px以内では渦と放射状のパルスも加わり、尾に向かって重み付けされる。
- 2マス移動するごとに、2D波シミュレーションに柔らかなパルスが加わります。その波はアイテムを発動させることができ、アイテムはパルスをさらに伝えることができます。
5. なぜキャラクターをデータから作るのか
私たちはAIキャラクターに関する理論から始めたわけではありません。クラゲエディターから始め、そこで実用的な利点に気づきました。キャラクターをカスタマイズ可能にするのと同じパラメータが、エージェントにとっても読み取りやすくするのです。
パラメータはエージェントにとって扱いやすい
スプライトシートはモデルにとって不透明です。私たちのクラゲは約30個の数値と少数のenumで構成され、プロンプトやJSONブロブ、ツール呼び出しに収まるほど小さいものです。
- エージェントはパラメータを読み取り、ユーザーのクラゲがどんな見た目かを知ることができます。
- 「眠そうな紫色で長い触手のやつを作って」という要望を、新しい画像を生成することなくプリセットに変換できます。各チームメイトのNPCはアートアセットではなく、データの一行です。
- 表情、発光、動きはランタイム状態なので、マスコットはエージェントが作業している間は集中した表情をしたり、混乱したときには目を回したりできます。
- パラメータは大きく変えても、結果は同じ種類のクラゲに見えます。
これがAirJellyにとって有用な部分です。マスコットの見た目と振る舞いがエージェントが読み取り変更できるデータであるため、マスコットはエージェントの身体として機能できます。不透明な画像を編集するようモデルに頼む必要は一切ありません。
なぜ今もピクセル(とASCII)が好きなのか
実用的な理由があります。ピクセルスプライトはパレットインデックスの小さなグリッドであり、ASCIIアートはすでにテキストです。どちらもコンパクトに記述でき、セル単位で編集できます。私たちが必要とする精密な変更においては、16×16のスプライトは絵画よりもモデルが即興で弄る余地を単純に少なくします。
もう一つ、より主観的な理由もあります。私たちはハードなグリッドが好きなのです。それらは機械によって作られたことに対して正直であり、1ピクセルのミスはしばしば不気味ではなく愛らしく見えます。そのトーンは、あなたのコンピュータ上に住むAIについての製品に合っています。AIツールの中でターミナルが心地よく感じられるのも、これが理由かもしれません。
つまり、私たちが行き着いたのはこれです。数値で記述され、グリッド上に描かれるキャラクター。結果としてレトロに見えますが、有用な部分は、人間にとってもエージェントにとっても正確に変更するのがいかに簡単かということです。
最後に一つ。ラボで気に入ったクラゲを調整したら、そのパラメータをエクスポートしてxiaohua@airjelly.aiまで送ってください — サイトに掲載します。すでに13匹がオフィスで暮らしていますが、もう一匹入る余地はいつでもあります。


























