Kimi K3はデジタル万年筆のストロークをどう作ったか
Lulucat Notes に Freeform の方向性万年筆が欲しかった。仕様は手書きのスクリーンショット1枚だった。2つのモデル、見分けるための1つの問い、そして楕円のペン先を経て、ウォーターペンはあるべき姿で書けるようになった。調査、コード、検証はすべて Fireworks 上の Kimi K3 が担った。

この投稿の主役である Lulucat Notes の Metal パイプラインでレンダリングした。
Lulucat Notes には普通のペンと蛍光ペンがある。ロードマップの次のツールはウォーターペン、つまり Apple Notes や Freeform でおなじみの、縦のストロークが太く横のストロークが細くなる方向性万年筆だった。このツールの仕様は文書ではなかった。仕様はスクリーンショットだった。手書きの3行に、「pencilkit」「无边记」「这种有方向的水笔能力」という言葉が書かれていた — この方向性ウォーターペンの能力、という意味だ。
その前の Metal パイプラインと同じく、この作業は Fireworks 上で動く Moonshot のオープンモデル、Kimi K3 が担った。計測、モデル化、コード、検証のすべてである。人間が用意したのはスクリーンショットで、1つの問いに答え、実機の iPad で手触りを判断した。
仕様はスクリーンショット1枚
静止画は、ストロークがなぜ細いかを教えてはくれない。どれくらい細いか、どこが細いかだけを教えられる。だから最初のステップは計測だった。スクリーンショットを行単位、列単位で走査し、各ストロークの中心線のずれを追って向きを求め、それから走査幅を真の幅に直すためにその角度のサインを使った。
| ストローク | 方向 | 真の幅 |
|---|---|---|
| 「l」のアセンダー、「pencilkit」 | ≈ 78° | 27.3 px |
| 「k」の縦画、「pencilkit」 | ≈ 90° | 28 px |
| 力の左払い | ≈ 66° | 25.7 px |
| 筆記体のつなぎ | ≈ 8° | 11 px |
| 漢字の横画(横) | ≈ 0° | 6–11 px |
太い線と細い線の比率は ≈ 2.5。66° の点に対して
第1版:向きから幅を決める
最初のモデルは自明なものだった。入力点ごとに方向を計算し、当てはめた曲線で方向を幅に写像し、その結果をキャプチャ時に点の半径へ焼き込む。方向は因果的に推定した。直近数点の弧に対する指数減衰付き加重平均で、進行方向が反転しても推定が打ち消されないよう倍角空間で計算する。過去の点だけを使うので、ライブのストロークと確定したストロークはバイト単位で一致する。
ビルダーはユニットチェックを通過した。合成した水平、垂直、45°、反転の各パスは、理論幅 0.99、2.52、2.00、0.99 ポイントをそのまま焼き込んだ。レンダリングには新しいコードが一切要らなかった。半径を焼き込んだストロークは丸いスタンプの連なりにすぎず、当社のポイントスプライトパイプラインがすでにそれを描画していたからだ。
iPad では、人間が却下するまで十秒とかからなかった。「これはアートペンで、ウォーターペンじゃない」。
1枚の絵に2つの説明が当てはまる
なぜ手触りが悪かったのか。候補となる説明が2つあり、スクリーンショットでは切り分けられなかった。
- 方向のロック。 ペン先の幾何学が、手が何をしようと横を細く縦を太く強制する。第1版が実装したのはこれだ。
- 筆圧と速度。 ペンは筆圧駆動で、サンプルのパターンは単なる手書きのダイナミクス。下向きのストロークは自然と強く押され、つなぎは自然と速く軽い。
どちらも、横が細く縦が太いスクリーンショットを生む。違いが出るのは、横ストロークを強く押したときだ。第1版は細いままになる。筆圧ペンなら太くなる。そこで人間に1つだけ質問した。強く押した横ストロークは、太くすべきか?
「いいえ。横は細いままで」。
方向ロックで確定した。だが、まだ何かが違った。第1版も方向ロックだったのだから。
答えはストロークの端にあった
次の手がかりはアセンダーの頂点にあった。サンプルの「l」と「k」の縦画を拡大すると、2つのことが分かった。まっすぐなストロークは始点から終点まで同じ幅を保ち、ストロークの端は平らな斜めの切り口、つまりノミ型のペン先が紙から離れる形をしている。丸い点ではない。筆圧の先細りではない。

「アートペンの手触り」が実際に意味していたのはこれだ。第1版はサンプルの見た目、つまり推定方向の関数としての幅をモデル化したが、ペン自体をモデル化してはいなかった。方向推定器はセンサーだ。ノイズの多い入力で揺れ、角の周りで遅れ、すべてのストローク端を丸く丸める。本物のペン先にはこうした問題は一切ない。何も計算しないからだ。幅は幾何学だ。
第2版:楕円のペン先
最終モデルには方向推定器が一切ない。ペン先は向きを持った楕円で、長軸が水平、短軸は固定。この楕円のスタンプをストロークの経路に沿って密に置く。あとはすべて幾何学から導かれる。
-
横ストロークは開いた縁に沿って進むので、常に幅
になる。筆圧によらない一定の細い線だ。確認された仕様そのものだ。 -
縦ストロークは長軸全体を横断する。
、太い端だ。 -
斜めストロークは、進行方向に垂直な楕円の弦の幅をとる。
-
ストロークの端は楕円の切り口になる。サンプルの平らなペン先型の端が、追加コストなしで手に入る。
-
筆圧は長軸だけをスケールし、
。だから筆圧は下向きストロークのインク量を増やし、横を太くすることは決してない。
サンプルの計測から、
レンダリングに必要だったのはフラグメントシェーダー1つだけだった。頂点フォーマット、位置・直径・色はすでにすべてを運んでいた。直径が長軸で、短軸はパスごとの uniform だ。シェーダーは、丸いスタンプと同じ半ピクセルのカバレッジランプで楕円 SDF を評価する。これが、Core Graphics の参照実装(スタンプごとの fillEllipse)とピクセル単位で比較可能にする仕組みだ。検証はいつものゲートを通った。万年筆ストローク2本を含む合成コーパスを両方の方法でレンダリングし、ピクセルごとに比較した。構造的な差はゼロ、スポットチェックした横ストロークはどちらのレンダラーでも 12 px だった。

左が第1版、右が第2版。同じ手書き入力、同じパイプライン。端が物語を語る。
iPad では、新しいペンは即座に合格した。「好,很好」— よし、とてもよし。
外れたものを残す
第1版はごみ箱行きにはならなかった。これは面白いブラシだ。ただウォーターペンではないというだけだ。そこで、人間が名付けた名前、漏水的圆珠笔、漏れるボールペンの下で、新しい実験ブラシメニューの1つ目の項目として出荷した。ツールバーにはフラスコボタンが置かれ、実験のテキスト一覧をポップアップする。次を追加するにはレジストリの1行を書くだけでよい。明確なラベルを付けた実験として出荷できるなら、却下されたモデルも無駄な仕事ではない。
一晩で回したループ
計測し、モデル化し、組み立て、デバイスで確かめ、問い、作り直し、再構築し、検証するという全過程は、一晩で終わった。Fireworks 上の Kimi K3 はループの技術面全体を動かした。計測スキャンを設計し、2度目の推測ではなく切り分ける問いを提案し、証拠が覆ると自身の方向推定器を削除し、アプリに触れる前にピクセル差分ハーネスを拡張した。Fireworks の推論速度がループをインタラクティブに保った。長い Metal と Swift の差分、ピクセル分析スクリプト、コーパス用ツールがどれも十分に速く届き、残ったボトルネックは人間の判断だった。
コラボレーションの形は Metal パイプラインの投稿で書いたものと同じで、今回も成り立った。モデルは速く、正確だ。人間は感性とエンドツーエンドの検証を担う。「アートペンで、ウォーターペンじゃない」という手触りに基づくフィードバック1文で、モデルはモデリングの誤りを正確に特定し、より単純な設計に置き換えるのに十分だった。
正しいモデルは、間違ったモデルより小さかった。第2版は第1版より動く部品が少なかった。推定器も、スムージングウィンドウも、当てはめた指数もない。ペン先は幅を計算しない。ペン先こそが幅なのだ。