先講結論
做一支看起來像粉筆的筆,難點幾乎都不在「怎麼畫出顆粒」, 而在怎麼讓顆粒在被重複蓋過幾百次之後還存在。 一筆看起來是一條線,程式裡卻是沿著路徑密集蓋下去的幾百枚印記。 凡是沒想清楚這件事的設計,最後畫出來都是一團平坦的灰。 下面把幾個真正會咬人的地方講清楚,不需要程式背景也看得懂。
第一個坑:隨機會被自己平均掉
最直覺的做法是:做一枚帶隨機雜訊的印章,沿著路徑一直蓋。 單獨看一枚印章確實有顆粒。但印章之間是大量重疊的, 每一次重疊都用不同的隨機值蓋在同一個位置上, 幾十次之後,這些隨機值互相抵消,趨近它們的平均 —— 一片均勻的灰。 你精心做的顆粒,被你自己蓋沒了。
解法是把顆粒從印章上搬到畫布上: 準備一張可平鋪的表面粗糙度貼圖,錨定在畫布座標上, 每一枚印記依照它落在畫布上的位置去取樣這張貼圖。 這樣一來,同一個地方被蓋十次,十次讀到的是同一塊紋理, 凹處始終是凹處、凸處始終是凸處,重疊變成互相加強而不是互相抵消。 這也正好符合實體世界:紋理本來就屬於板面,不屬於粉筆。
還有一個小細節:只用單層細雜訊,顆粒會均勻得像撒了鹽。 真實板面的粗糙度是成團的,所以要再加一層比較粗的起伏讓顆粒聚集。
第二個坑:乾筆觸不是疊出來的
另一種直覺是把粉筆想成很多半透明的小點,疊得夠多就變實。 實際的粉筆比較接近相反的描述: 它是一塊實心的沉積,被板面的凹凸咬掉一些洞。 先實心再挖洞,和先透明再疊厚,出來的東西不一樣: 前者的邊緣是破碎但明確的,後者的邊緣是霧的。
實作上這表示引擎需要一個沉積門檻: 夠濃的地方就是實的,不夠濃的地方就是空的,中間的過渡帶要夠窄。 一支基礎不透明度設得太低的筆永遠跨不過那個門檻, 畫出來像煙,永遠不像粉。
第三個坑:筆桿方向在筆立起來時是壞的
要做出粉筆側稜的效果,必須知道筆桿在螢幕上朝哪個方向。 系統確實提供這個資訊,問題是當筆接近垂直時, 筆桿在螢幕平面上的投影幾乎不剩什麼, 回報出來的角度會在相鄰兩個取樣點之間劇烈跳動。 把這個原始角度直接拿去旋轉筆觸形狀, 使用者會看到筆立著寫時痕跡在肉眼可見地抖。
比較穩的處理是把它當成一個方向而不是一個角度 —— 以單位向量的形式讀取與內插, 並在相鄰兩點之間走最短弧, 避免方位從一側跨到另一側時筆尖原地轉一整圈。
第四個坑:快筆會塌成折線
在高更新率的螢幕上,系統在一個影格之內可以收集到好幾個觸控取樣, 成批交給 App。只讀最新那一個、把整批丟掉, 等於默默丟掉使用者畫的大部分內容 —— 慢慢畫看不出來,快速甩一筆就塌成三、四段帶折角的直邊。
要拿到那些額外的取樣,畫布必須建在能存取它們的底層機制上, 而不是建在拿不到的高階手勢介面上。 這是個架構決定,不是可以事後補救的參數。
第五個坑:不做速度就沒有飛白
真的粉筆甩快了會打滑:痕跡變淡、變細、斷開。 如果引擎只把速度拿來平滑路徑,那就永遠得不到飛白, 而飛白幾乎是乾性媒材最明顯的辨識特徵。 速度必須同時淡化與細化痕跡。
這裡有一個容易踩到的縮放陷阱: 若用螢幕像素衡量速度,放大之後每次取樣之間畫布幾乎沒動, 引擎會誤判你一直在慢慢畫,於是恰好在你摳細節時飛白消失了。 用當前筆徑而不是絕對像素去衡量,同一個手上的動作在任何倍率下才有相同意義。
第六個坑:手指沒有角度,就不要編一個
手指回報的傾斜與方位是 0,而高度角 0 的意思是「筆桿完全貼平在玻璃上」。 不先判斷觸控來源就把這個值往下傳, 每一道指尖痕跡都會被拉成朝向隨機的極端糊線。 正確的處理是:只有在確認是觸控筆時才讀這些欄位, 是手指就退回成正圓的痕跡,把表現力交給速度。 壓力也要設一個下限,不然輕輕擦一下會整筆消失。
最後:存點,不存像素
把上面這些做對之後,還有一個看似無關的決定會決定 App 能做到什麼: 一張圖是存成像素,還是存成一串點?
Flying Chalk 存的是點 —— 每一筆是一串取樣點加上一個亂數種子, 畫面是把這些點重放出來的。後果相當實際: 檔案很小;還原重做是精確的,顆粒每次落回原位而不會重擲; 圖層一筆只多一個整數,而不是一整張全螢幕影像; 而且畫完之後還可以把底下的板面整個換掉,因為畫的內容從來沒有被烙進像素裡。 連圖形工具(直線、矩形、橢圓、箭頭、星形等)也是先被取樣成點列再送進同一套程式, 所以畫出來的矩形跟隨手畫的線帶著同樣的顆粒與飛白。 存像素的畫布做不到這些,除非它替每一步都存一整張圖。
常見問題
為什麼很多 App 的粉筆最後看起來是一片灰?
因為顆粒被做進了每一次蓋印裡。印記大量重疊時,不同的隨機值互相平均成灰。顆粒必須錨定在畫布座標上,重疊才會互相加強。
為什麼我快速畫線會變成幾段直線?
那是取樣不足,不是延遲。系統在一個影格內成批送來多個取樣點,只讀最新一個就等於丟掉其餘的,曲線因此塌成多邊形。
為什麼筆立起來的時候筆觸會抖?
接近垂直時筆桿在螢幕上的投影方向本身就不穩定,相鄰取樣之間會跳。把它當成方向向量來讀並走最短弧內插,才能穩住。
存點而不存像素,對使用者有什麼差別?
還原重做精確、檔案小、圖層便宜,而且畫完之後可以整塊換掉底下的板面,因為筆觸從來沒有被烙進像素裡。
相關指南
找塊黑板試試看
Flying Chalk 是 iPad 與 iPhone 上的免費粉筆黑板繪圖 App。支援 Apple Pencil 的壓力感應與傾斜角,8 支粉筆家族的筆、11 種畫面。沒有廣告、不用註冊、沒有 App 內購買。