Lulucat

Kimi K3 如何打造數位鋼筆筆畫

張高歌張高歌

我們想在 Lulucat Notes 裡加入 Freeform 那種帶方向的鋼筆筆畫。規格說明只是一張手寫截圖。經過兩個模型、一個判別問題和一個橢圓筆尖,水筆終於寫出了應有的效果。研究、程式碼和驗證由運行在 Fireworks 上的 Kimi K3 完成。

更新於

米色紙上,一支橢圓筆尖水筆寫出的英文草體「fountain pen」位於一行中文書法之上:直向筆畫粗,橫向筆畫細。

由本文介紹的 Lulucat Notes Metal 管線算繪。

Lulucat Notes 有一支普通筆和一支螢光筆。路線圖上的下一個工具是水筆——你在 Apple Notes 和 Freeform 中見過的那種帶方向的鋼筆:直向筆畫較粗,橫向筆畫較細。這個工具的規格說明不是一份文件,而是一張截圖:三行手寫文字,分別是「pencilkit」、「无边记」和「这种有方向的水笔能力」——也就是這種帶方向的水筆能力。

和之前的 Metal 管線一樣,這項工作由在 Fireworks 上執行的 Moonshot 開放模型 Kimi K3 完成:測量、建模、程式碼和驗證。人只提供截圖、回答一個問題,並在真實 iPad 上判斷手感。

規格說明是一張截圖

靜態圖片無法告訴你筆畫為什麼細;它只能告訴你有多細、在哪裡細。因此第一步是測量。我們逐行逐列掃描截圖,追蹤每道筆畫的中線漂移,從而得到方向,再用該角度的正弦把掃描寬度換算成真實寬度:

筆畫方向真實寬度
「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° 那個點,得到 ——幾乎與 線性相關,在垂直方向上達到峰值。換句話說,這是一枚斜體筆尖:一條橫向細縫,橫向書寫永遠無法把它撐開。

第一版:由方向決定寬度

第一版模型很直觀:為每個輸入點計算方向,透過擬合曲線將方向映射為寬度,並在取樣時把結果烘進該點的半徑。方向以因果方式估計——對弧線上最後幾個點做指數衰減加權平均,並在倍角空間中計算,使移動方向反轉時不會相互抵消。它只使用過去的點,因此即時筆畫和提交後的筆畫逐位元組一致。

建構器通過了單元檢查:合成的水平、垂直、45° 和反轉路徑都寫入了理論寬度——0.99、2.52、2.00 和 0.99 點。算繪完全不需要新增程式碼:半徑已經寫入的筆畫就是一串圓形印章,我們的 point-sprite 管線本來就能繪製它們。

在 iPad 上,人只用約十秒就否決了它:「這是一支美術筆,不是水筆。」

兩個解釋都符合這張圖

為什麼手感不對?有兩個候選解釋,而截圖無法區分它們:

  • 方向鎖定。 筆尖幾何形狀強制橫筆變細、直筆變粗,無論手如何移動。這是第一版實作的機制。
  • 壓力與速度。 鋼筆由壓力驅動,樣本中的模式只是手寫動力學:下行筆自然會按得更重,連接筆畫自然更快、更輕。

兩種解釋都能產生橫細直粗的截圖。區別在於用力按橫筆時會發生什麼。第一版會讓它保持細;壓感筆會把它變粗。因此我們只問人一個問題:用力按下的橫筆應該變粗嗎?

「不。橫筆保持細。」

方向鎖定得到確認。但問題還沒有解決,因為第一版同樣鎖定了方向。

答案在筆畫的末端

下一個線索在上伸筆畫的頂端。放大樣本中的「l」和「k」豎幹後,可以看到兩件事:直筆從頭到尾保持相同寬度,而筆畫末端是平的斜切口——鑿形筆尖抬離紙面時留下的形狀。不是圓點,也不是壓感收尖。

樣本中兩道手寫上伸筆畫豎幹的近距離裁切圖,每道筆畫的頂端都以平的斜切口收尾,豎幹全程粗細均勻。

這就是人所說的「美術筆手感」。第一版建模的是樣本的外觀——把寬度作為估計方向的函數——而不是本身。方向估計器是一種感測器:輸入有雜訊時會抖動,遇到轉角會滯後,還會把每個筆畫末端都收成圓形。真正的筆尖沒有這些問題,因為它什麼都不計算。寬度就是幾何形狀。

第二版:橢圓筆尖

最終模型完全沒有方向估計器。筆尖是一個定向橢圓:長軸水平,短軸固定。沿筆畫路徑密集放置這個橢圓的印章,其他一切都由幾何自然產生:

  • 橫筆沿著橢圓的開放邊緣移動,因此無論壓力多大,寬度始終是 ——一條恆定的細線。這正符合確認過的規格。

  • 直筆穿過完整長軸:寬度為 ,達到最粗。

  • 斜筆的寬度是垂直於行進方向的橢圓弦寬,

  • 筆畫末端是橢圓切口——樣本中平坦的筆尖形末端,不需額外處理就能得到。

  • 壓力只縮放長軸,,因此壓力只會增加下行筆的墨量,永遠不會讓橫筆變粗。

根據樣本測量,我們保留 pt 和 pt,比例仍為 2.5。印章沿弧線每隔 0.5 點放置;短半徑為 0.55 點時,最壞情況下的邊緣漣漪約為十分之一個像素。

算繪只需新增一個片段著色器,其他都不用改。頂點格式——位置、直徑、顏色——已經包含全部資訊:直徑表示長軸,短軸是每個 pass 的 uniform。著色器使用與圓形印章相同的半像素覆蓋率漸變計算橢圓 SDF,這讓它在像素層級可與 Core Graphics 的參考實作(每個印章呼叫一次 fillEllipse)比較。驗證依照常規流程:包含兩道鋼筆筆畫的合成測試集分別用兩種方式算繪,再逐像素比較——結構差異為零;抽查的一道橫筆在兩個算繪器中都測得 12 px。

同一個手寫單詞「fountain」的兩種算繪並排:左邊用方向縮放直徑的圓形印章畫成,末端是圓的;右邊用橢圓印章畫成,末端是平的筆尖切口,寬度也更穩定。

左邊是第一版,右邊是第二版。同樣的手寫輸入,同一條管線。末端說明了一切。

在 iPad 上,新筆立即通過了:「好,很好」——好。很好。

留下這個失誤

第一版沒有被丟掉。它是一支確實有趣的筆刷,只是不是水筆。因此,它以人給的名字——漏水的圆珠笔——作為新的實驗筆刷選單第一項發布。工具列增加了一個燒瓶按鈕,點擊後會彈出實驗項目的文字列表;新增下一項只需在註冊表中加一行。被否決的模型只要能作為明確標註的實驗發布,就不算浪費。

一個晚上的迴圈

完整的閉環——測量、建模、建構、上裝置、提問、重新建模、重新建構、驗證——花了一個晚上。運行在 Fireworks 上的 Kimi K3 負責整個技術環節:設計測量掃描,提出判別問題而不是再次猜測,在證據轉向時刪除自己的方向估計器,並在修改 app 前擴充像素差異測試工具。Fireworks 的推理速度讓閉環保持互動性——長篇 Metal 和 Swift diff、像素分析指令稿、語料工具都來得夠快,剩下的瓶頸仍然是人的判斷。

協作模式與我們在 Metal 管線那篇文章中寫的一樣,而且再次奏效:模型快速、精確;人負責品味與端到端驗證。一句基於手感的回饋——「美術筆,不是水筆」——就足以讓模型定位準確的建模錯誤,並用更簡單的設計取代它。

正確的模型比錯誤的模型更小。第二版發布時包含的活動元件比第一版少——沒有估計器,沒有平滑視窗,沒有擬合指數。筆尖不計算寬度。筆尖就是寬度。