Spark AR 快速入門筆記 (程式向)

Lastor
Code 隨筆放置場
15 min readMay 3, 2020

最近在研究 Spark AR。試著筆記一下,方便自己日後查閱。也順便提供給有興趣的人參考。這篇文章撰寫時,最新版本號為 v87。

這是一套 Facebook 開發,用於製作 Instagram 或 Facebook 濾鏡的 3D 軟體。

具備基本的 3D 視圖操作、Node Editor、Particle 粒子系統…..等功能。但是不包含 Modeling 模塊。本身不是像 Maya、3ds Max 那類的 3D 整合製作軟體。而是比較類似…… BodyPaint 3D 或是 Zbrush 那種,只強調某一塊功能的軟體。

Spark AR 主要是利用 Facebook 的臉部辨識技術,透過攝影機來偵測我們臉部表情,或是其他動態事件 (搖頭、點擊手機螢幕…etc),然後可以觸發一個 Event,讓我們可以做進一步的操作。

例如:嘴巴張開,就讓 3D 模型旋轉。

概念上有點像是 Web 前端的事件監聽器 (addEventListener),當偵測到 click 事件,就觸發一個 function。Spark AR 則是將臉部的各種事件,都幫我們包好,弄成 API 讓程式可以調用。Script 使用的語言是 JavaScript ES6,所以 Web 工程師可以很容易的入門。

Spark AR 開發環境概要

Spark AR 目前官方「每兩周」會更新一次版本,有時會加入新功能,移除舊功能。新版本可以開舊版本,但舊版本不能開新版本的檔案,新版本 Export 的內容,舊版本也是無法 Import 的,這點要注意。

可以在官網上查詢 Update Log

偏程式人員的開發者,主要要注意兩個模塊,Script 與 Patches。

Script 應該就不用解釋,就是開 .js 檔案,直接寫。加入 Script 的操作類似 Unity。而 Patches 似乎是最近幾個版本才出現的,他主要作用是把 Script 弄成視覺化 GUI,讓不會寫程式的人也能輕鬆操作。

Patches 概念上就是 Softimage 裡面,Mental Ray 的 Render Tree,或是現在 Maya 提供的 Node Editor 那種東西。我們可以自由地把物件,或是物件屬性的 Node 拉進 Patches 進行操作,製作 Shader。

但目前並不是所有的屬性,都提供 Patches 操作。

由於 3D 繪圖的部分,主要還是基於 OpenGL 的玩意,所以想要做出一些高級效果,就很難避開學習 OpenGL 這個大坑。

基礎操作的部分,建議直接參考官方網站,Learn 部分的教學文件。全英文雖然是個痛點,但是只要具備國中英文水平,加一個 Chrome 的翻譯插件即可。

官方文件:Learn > Getting Started

修改視圖操作 (Camera Pan, Rotate, Zoom)

視圖操作的部分,這軟體預設的快捷鍵有點奇怪,建議自行改成 Maya 或是 3ds Max 的操作方式。

修改快捷鍵:File > Preferences > Shortcuts > Viewport

在這邊可以找到 Pan、Rotate、Zoom 這些選項。

介面快速導覽

  1. 左上 Scene (場景),呈現場景中物件的列表。

由於這套做的東西是 Mobile base,所以物件全都會包在 Device (手機裝置)裡面。在上面點擊右鍵,可以 Add 東西到場景裡,基本 3D model 這邊只有提供 plane。加進來的東西,會被自動 Parent 到 Focal Distance 底下。

這邊只要先有個概念,東西都要放到 Focal Distance 底下即可。

2. 左下 Assets (資源),尚未被加入 Scene 的專案資源。

這邊的概念跟 AE 或是 Premiere 那類後製 / 剪片軟體很像。需要先把資源加到專案中的 Assets 區塊,然後拖曳到 Scene 裡面。

資源包含了,外部軟體製作的 3D 模型、貼圖、材質球、Script……etc

點擊下面的 Add Asset,就可以加入預設材質球,或是空的 Script。要注意的是,Script 檔案要新增 or 刪除,都需要透過 Spark AR 操作。如果是在檔案總管,或是 VScode 中操作,Spark AR 會不認。

另外,除了匯入自己製作的模型、貼圖之外,也可以從 Spark AR 的 Library 去匯入東西。裡面有基礎 3D Model (方塊、三角錐那些),或是一些做好的 Patches 等等。可以去翻翻 Patches,參考這玩意怎麼用。

3. 右方 Properties (屬性),控制場景物件的屬性。

這邊應該就不用多做說明了,就是 Maya 的 Attributes 那類東西。不懂 3D 的人,建議可以先去爬一些常規 3D 軟體的基礎教學,Maya、3ds Max、Blender 都可以,概念大同小異。

4. 操作面板

第一個按鈕,可以打開 Patches Editor 或是 Console,程式開發人員的主要工作區。其他的按鈕,滑鼠移過去都有彈出說明,這邊應該是蠻好懂的。

Patches Editor 概要

這一塊直接跟著官方教學,實做一些範例會理解的比較快。

官方教學:Learn > Patches Editor

這玩意基本上就是 Node Editor,這一塊做的相當強大,比 Maya 那該死的 Hypershade 要好懂太多了。邏輯很類似 Softimage 的 Render Tree,有碰過的話相信很好上手。

這邊做一個簡單快速的範例。
讓一個變數 value 去控制一個 plane 的 x 軸位移,並且做一些簡單運算。

  1. 在左邊 Scene 右鍵,加入一個 plane。
  2. 在右方屬性區,點擊 Position 左邊的箭頭,將這個 Node 拉進 Patches Editor。
  3. 在 Patches Editor 右鍵,直接輸入 value,建立一個 Value Node (節點)。
  4. 再建立一個 Add,加法節點,第二欄輸入 0.1,按住 Value 右邊的小箭頭(output),拖曳連接到 Add 第一欄左邊的小箭頭,使其連結。
  5. 再建立一個 Pack 節點,把 Add 的輸出 output 連結給 Pack 的第一欄。
  6. 最後再連到 plane0 的 3D position 上頭。

這一波操作,就是用 Value 作為 controller,以它為 root 去控制 plane0 的 X 軸向位移。過程中透過 Add 加法,做簡單的數學運算。

// 寫成 code 會類似這樣
let value = 0.3
plane0.transform.x = value + 0.1

由於 plane0 的 position 節點,需要接收 (x, y, z) 三個軸向的值,而 Value 卻只有一個值。所以透過 Pack 節點,可以將三個獨立的值打包在一起。

類似這種感覺:

const valueA = 0
const valueB = 1
const valueC = 2
const pack = [valueA, valueB, valueC] // 打包

這個簡單範例,不需要 y 跟 z,就直接用 Pack 節點上的 0 即可。這樣就可以輸出一個 Vector3 的資料,提供給 plane0 的 position。

用滑鼠去點擊 Value 節點,可以看到下方有一個寫著 Number 的下拉選單,打開可以看到所有可選的資料型別。可以自由將 value 切換成想要的型別。

Boolean、Number、Text (String) 這些就不多提了,跟 JavaScript 是一樣的。Pulse 這個比較特別一點,這是脈衝事件,當布林每變一次 true 的時候,會觸發一次 callback。

例如,我設一個變數,當這變數等於 1 時,顯示 plane0,反之隱藏:

// 寫成 code 的感覺
let value = 0
if (value === 1) { plane0.visible = true }
else { plane0.visible = false }

Pulse 的概念會像 Web 的事件監聽器,可以做成當 flag 為 1 時觸發一次 callback。將 value 改為 0,再改為 1,會觸發一次 Pulse,改變 plane.visible 的布林值。而非單純的 1 是 true,非 1 是 false。

Switch 節點跟 Pulse 是一組的,常一起出現
// Reative 為 Spark AR 的 API
const value = Reative.val(0)
let hidden = false
// 監聽 value 是否等於 1, true 時觸發 callback
value.eq(1).onOn().subscribe(() => {
plane0.visible = !hidden
})
// 類似 Web 的事件監聽器
DOM.addEventListener('click', event => { ... })

其他 Color 跟 Vector2 ~ Vector4 是出自 OpenGL 相關電腦圖學的概念,為了讓程式能模擬三維座標,所以將 Number 型別多追加了一個 Vector 向量型別。這樣才能區分 (x, y, z) 這些座標,或是顏色的 RGB 和 RGBA。

而 Patches 主要的用途就是,「用某個值去控制另一個值」,中間可以自由的穿插數學計算,從加減乘除到 power、三角函數、log 都是可以的。或是條件判斷,等於 (Equals)、大於 (Greater Than)、大於等於 (Greater or Equals) 之類的,應有盡有。

把這個玩熟,很大程度內是不用特地去寫 Script 的。

Script 概要

Script 的官方文件內容相對的少一些,尤其是 API 文件對初學者來說有點難看懂。可以先看過官方的 Script Overview 再去翻 API 文件。

物件的屬性,可以透過 Patches 或是 Script 做控制。Script 也可以輸出值到 Patches,或是從 Patches 拿值,來做橋接。

Script 是以 JavaScript ES6 為基礎來撰寫。由於 Spark AR 採用全面響應式的設計,響應式的概念就是前端框架,React 或是 Vue 的那種概念,當 data 改變時,會即刻重新渲染畫面。

例如說,我要讓某個 3D 物件的 Rotate 隨著嘴巴的開闔度做改變。必須要做成響應式,才能做到每當嘴巴開闔 onChange 時,也即刻改變 3D 物件的 Rotate。

為了達到響應式效果,使得 Script 必須都要包裹一層 Spark AR 的 API 才有辦法工作。所以概念上就是先把需要的 module 給 import 進來,再透過這些 API 來操作。

const Scene = require('Scene')              // 用來取得場景物件
const Diagnostics = require('Diagnostics') // 就是 console.log
const Reactive = require('Reactive') // 可把 value 包成響應式
const num = Reactive.val(0) // 宣一個被 API 包裹的 Number

經過 Spark AR 的 API 之後,所有的值都會變成響應式,官方說明是寫說,包裝成響應式的值,叫做 Signal。

而要向 Patches 那樣,讓某個值去控制另一個值,則叫做 Signal Binding (信號綁定),寫法上非常簡單。例如我讓 plane0 的移動,綁定 plane1 的旋轉。只要寫個等號賦值即可。

// 將 rotate X 綁定給 position X
plane0.transform.x = plane1.transform.rotationX

要在 Script 中取得場景物件,也就是要在 Scene 裡面 find 物件。只要這樣寫就可以了 (v85 以前)。

const plane0 = Scene.root.find('plane0')

但是 v85 之後,全面改成 Promise 結構。使用 find 的話,會看到 console 出現一個警告訊息,說 find 已被棄用,請改用 findAll 或是 findFirst。這兩個都是 Promise 函式,要搭配 .then() 才能使用。

關於 Promise 化之後怎麼寫,後面會稍微提一下。

如果要做加減乘除之類的數學運算,會相當的麻煩。因為直接用 JS 的方式寫,響應式效果就沒了。假設要讓嘴巴的開闔度加 0.1 之後,綁定給 plane0 的位移,就得透過 Reactive module 來做加法。

// 引入需要的 module
const Scene = require('Scene')
const FaceTracking = require('FaceTracking')
const Reactive = require('Reactive')
// 取得場景物件, 取得嘴巴開闔度
const plane0 = Scene.root.find('plane0')
const openness = FaceTracking.face(0).mouth.openness
// NG, 加法的錯誤寫法
plane0.transform.x = openness + 0.1
// OK, 加法的正確寫法
plane0.transform.x = Reactive.add(openness, 0.1)

if else 這類邏輯判斷,也必須要包一層 Spark AR 的 API,否則只會執行 1 次,做不到響應式效果。

由於全都得包一層,這讓 Script 寫起來非常不舒暢,稍微複雜一點的運算,就得包很多層形成各種巢狀,這讓可讀性變得有點低。

至於有哪些 API 可以用,可以透過 VS code 的自動完成 (Autocomplete) 來查看,官方都有在上面寫說明。或是直接去翻閱那難懂的 API 文件。比較新的 API 官網上都還沒加上,但是 VS code 的說明是有加上的。

透過官方寫好的 JSDoc 代碼提示,查看使用方式

自動完成有一個小缺陷,就是任何 find 回來的物件,都沒有自動完成的輔助。這就得辛苦的去翻文件,有些東西放的位置相當詭異,很不直覺。

另外,可以利用 Spark AR 的 Diagnostics.log() 把那個物件打印出來,裡面也會寫它有哪些 property 跟 method 可以用。但是寫不全就是。

透過 Spark AR Console 查看屬性

Script 當中,除了多出了 OpenGL 的 Vector 那些型別之外,Spark AR API 所產生的東西,也有個各種不同的 Class。例如 Scene 中 find 回來的 plane 模型,叫做 Plane Class。而材質球則會是 MaterialBase Class。

其他的數值 Signal 也有各自的 Class,例如 Boolean 是 BoolSignal Class,向量則有 VectorSignal Class。可以隨時利用 Diagnostics.log() 去打印出來,裡面會寫它是哪種 Class。然後再去翻 API 文件,查閱有哪些 method 可以用。

Signal 類都放在文件: Scripting Object Reference > ReativeModule
其他物件則在各自的 Module 裡:SceneModuleMaterialModule

要看懂它文件到底是怎麼分類的,需要點時間。只要能先知道它是用 Class 在分的,可以少繞很多遠路。

v85 Promise 概要

從 v85 版本更新之後,所有查詢 / 訪問專案資料的語法,都得改用 Promise 語法。包含 find 模型、材質球、貼圖……等等。

這個改動,讓 Facebook 上的 Spark AR 官方討論群掀起了不小的浪濤。當然也有一些強者老外分享了一些可讀性跟可維護性很高的寫法。

改用 Promise 之後,正常人的思考方式應該都是用 then chain 來寫,而這會造成比較深的巢狀結構。

// 將材質球 plane_mat 賦予給 plane0
Scene.root.findFirst('plane0').then(plane0 => {
Materials.findFirst('plane_mat').then(plane_mat => {
plane0.material = plane_mat
})
})

這部分有老外提出了使用 Promise.all 來集中管理的做法。

Promise.all([
// Find assets from Project
Scene.root.findFirst('plane0'),
Materials.findFirst('plane_mat')
]).then(onReady)
function onReady(assets) {
const [plane0, plane_mat] = assets
// do something...
}

這樣的寫法非常的巧妙,在開頭就明示了這個檔案調用了哪些 assets,這讓我們不用在茫茫大海之中,去尋找那些 assets。而 Promise.all() 的格式,無形中也提供了類似框架的效果,可以統一不同工程師的寫法,讓可維護性瞬間飆升。

其他推薦的學習資源

Spark AR Community
Facebook 官方討論區,有很多老外會分享 Tips。

Spark AR TV
這邊蒐集了 Youtube 上的影片教學資源,可以利用 Youtube 自動翻譯來生吃。

The Book of Shaders
電腦圖學相關,Fragment Shaders 入門指南

Game Dev Tutorials - BRACKEYS
講解 Unity 為主,遊戲開發的程式 and 數學知識

--

--

Lastor
Code 隨筆放置場

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