Spark AR 實作 SDF Shader (上)

Lastor
Code 隨筆放置場
7 min readJun 23, 2020
Make SDF shader in Spark AR

最近接觸到了一個叫做 SDF (Signed Distance Field) 的玩意,最開始以為是類似 SVG 之類,向量繪圖相關的東西。研究了一陣子之後,發現似乎不是這樣。這邊稍微紀錄一下何謂 SDF,以及如何在 Spark AR 中實作出來。

因為文章太長,故拆成兩篇,第一篇講 SDF,第二篇講 MSDF

何謂 SDF ?

先說說這玩意的應用面,目前我知道的常見應用都集中在遊戲業與字型相關的領域。我們知道,JPG 之類的點陣圖檔,如果縮到 32 x 32 這樣的小尺寸,基本就炸了。放大看就會非常的模糊,充滿鋸齒。但使用了 SDF 技術之後,就能使它獲得偽向量的特性,放大過後也能保持一定的銳利度。

(用 64x64 以上效果比較好)

SDF vs bitmap

如此一來,就能大幅度減少貼圖占用的空間,達成輕量化。User 的讀取下載速度也會飛快許多。

但它比較適合處理一些幾何類的圖形,並且只適用於單色的圖像。例如一些 Logo、Icon 或是文字。複雜的角色立繪之類,是不適用的。

原理上,Signed Distance Field 直譯為「帶信號的距離場」。它在圖像中儲存了空間中的各個 Point 到圖像輪廓之間的距離,Signed 這邊指的是正負信號,如果 Point 在圖像內為負值,在外側則為正值。

length 為點到輪廓之間的距離

如果將這條白色的粗直線,實際轉成 SDF 圖檔,就會變成這樣。

轉成 SDF 的粗直線

SDF 實際上只是將這條線的輪廓,往外加個漸層,也就是模糊化。而這漸層就是所謂的 SDF,它包含了輪廓周圍的各像素與輪廓之間的距離資訊。

上圖中的白色到黑色的漸層,寫作 RGB 數值就是 1 (白) 到 0 (黑),而這 1 到 0 也就是像素點距離白線輪廓之間的「距離 (Distance)」。有了這張關於粗直線的 SDF Map 之後,就能透過簡單的 Step Function,指定距離大於 0.5 的話,給一個 Color A,小於的話給一個 Color B,最後就能將目標圖像的原始「輪廓」給還原回去,並填上想要的顏色。

將 SDF 還原回圖形

由於 SDF 本身是一個漸層資訊,所以一定程度上可以無視圖片縮到超小之後產生的劣化。只要透過 Step Function 還原回去,一樣可以取回原本銳利的輪廓,也就實現了一種「偽向量」圖檔的效果。

生成 SDF 圖檔

SDF 圖檔的生成方式,目前個人知道的方式有兩種,一種是透過前人寫好的 library 用程式算出來。另一種則是使用 Photoshop 的一些小技巧來做。

1. 透過程式生成
先到 Github 上,有一個叫做 msdfgen 的開源套件,是一支使用 C++ 寫的轉換工具。在上方 releases 的地方可以直接下載發布版,裡面有一個 .exe 的主程式。主要是透過 command line 來執行這個 .exe 進行生成。

這邊給個範例,詳細的設定跟參數可以參考官方的 README。

$ msdfgen.exe sdf -svg "C:\source.svg" -o output.png -size 64 64 -pxrange 4 -autoframe// 主要有三個區塊
$ msdfgen.exe <mode> <input> <option>

先指定要轉成 sdf 或其他格式,然後指定輸入的 source 格式與檔案位置,再來是輸出的檔名,以及 size 跟 pxRange。

pxRange 可以想成是模糊的範圍要多大,最後那個 autoframe 也可以手動設定,但我還沒搞得很清楚這是幹嘛的,就先用 auto 吧。

之後就會在轉換程式的根目錄看到輸出後的檔案。

比較要注意的是「input 」輸入的地方,必須要餵的是 SVG 向量圖檔,而不是一般的點陣圖。SVG 檔可以使用 Illustrator 做向量繪圖後直接輸出,也可以透過 Photoshop,使用形狀路徑繪製向量圖之後,利用「轉存」輸出成 SVG 檔。

另外,如果要生文字的話,可以直接餵給它一個字體檔跟文本內容,不需要特別製作源圖檔。

$ msdfgen.exe sdf -font C:\Windows\Fonts\arialbd.ttf 'A' ...(後略)

2. 使用 Photoshop 生成
由於 SDF 主要的特徵其實就是給外輪廓加上一個漸層過度,所以可以利用 PS 進行加工來完成這個操作。

這部分的源教學,可以參考 Twitter 的這則推文,Link

在黑色的背景圖層往上再開一個圖層,用白色繪製圖像。完成後,在圖像所在的圖層添加「圖層樣式 > 筆畫」,然後照下圖調整數值即可。

這邊尺寸的部分起到類似 pxRang 的效果,可以用來控制漸層的範圍。添加完筆畫效果之後,直接縮小存成 PNG 即可。

實作 SDF Shader

Spark AR 當中原本就有直接提供 SDF 的 Patch 可供繪製,如果有能力駕馭它的話當然可以直接畫。而另一條途徑就是透過上述方式,在繪圖軟體中畫好,轉成符合 SDF 格式的圖檔,再匯入當貼圖使用。

無論是用 SDF Patch 繪製,還是使用 SDF 貼圖,中間都必須要經過 Step Function 將圖形還原回原本的樣子。

Step 根據 Edge 數值,將 Value 轉為 0 或 1

將 SDF Map 連接到 Step Patch 上,Edge 的大小決定輪廓線的粗細,一般設定 0.5 即可。之後接到 Mix Patch 來做線性插值 (Linearly Interpolates)。

Mix 的工作方式,是輸入兩個顏色 Color0、Color1,Alpha 是 0 ~ 1 之間的數,用來決定輸出結果要顯示 Color0 還是 Color1。例如,輸入 0 會顯示 Color0,輸入 1 則顯示 Color1,輸入 0.5 則顯示兩者混色後的結果。可以簡單理解成 Photoshop 疊兩個圖層,調整圖層透明度的效果就是 Mix,只是結果相反而已。

這邊將 Step 計算過後的結果,輸出到 Mix 的 Alpha 屬性上作為遮罩。而 Color0 可以視作背景色,Color1 則為前景色。依照這張遮罩,就可以把原本黑白的 SDF 填上顏色了。

但這邊要注意的是,Step 輸出的是 Vec4,直接接到 Alpha 上的結果就會不太正確。由於 SDF 是一個灰階資訊,也就是 RGB 三個通道的值是相等的,唯獨 Alpha 通道為 1。而我們需要的其實只是 RGB 其中一條通道的值。所以透過 Swizzle Patch 單獨輸出 RGB 任一通道即可,這邊範例是輸出 R 通道。

最後,就可以看到我們用一張很小的 64 x 64 的圖檔,卻能呈現很清晰的結果。

Render result via SDF texture

下一篇: Spark AR 實作 MSDF Shader (下)

--

--

Lastor
Code 隨筆放置場

Web Frontend / 3D Modeling / Game and Animation. 設計本科生,前遊戲業 3D Artist,專擅日本動畫與遊戲相關領域。現在轉職為前端工程師,以專業遊戲美術的角度涉足 Web 前端開發。