上一篇文章文章中談到開始進行 Omniverse 開發首要由 USD 開始做起,USD 的相關資料在中文圈實在屈指可數;不禁讓我想起當我還是懵懂少年時有幸透過戴文凱教授承接資策會的 glTF 研究案,當時仍是 0.6 版草案自然是要閱讀相當多的文件。

以 C++ 進行 USD 開發的挑戰在於他的參考要比 Python 少很多,而 Pixar 官方文件雖然寫得是一清二楚,但卻缺乏一條明晰的道路。但由於 USD 的 Python module 終究是 C++ library 的 binding,換言之從 Python 的操作流程可以兌換到 C++ 上。

from pxr import Usd, UsdGeom
stage = Usd.Stage.CreateNew('HelloWorld.usda')
xformPrim = UsdGeom.Xform.Define(stage, '/hello')
spherePrim = UsdGeom.Sphere.Define(stage, '/hello/world')
stage.GetRootLayer().Save()

以 Pixar 的第一個範例來說兌換成 C++ 可以寫成如此:

int main() {
  auto stage = UsdStage::CreateNew("HelloWorld.usda");
  auto xformprim = UsdGeomXform::Define(stage, SdfPath("/hello"));
  auto sphere = UsdGeomSphere::Define(stage, SdfPath("/hello/world"));
  stage->GetRootLayer()->Save();
  return 0;
}

這是一個很單純的範例,而且外顯行為也很簡單;首先透過 UsdStage::CreateNew 建立一個 *.usd 檔案,此處之所以寫為 usda 並不是我或官方筆誤,而是 a 表示 ASCII, USD 檔案將會自動被儲存為文字形式,ASCII 格式雖然會給我們帶來更大的檔案容量,但在前期學習 USD 時我們首要理解的是 USD 如何去描述一個場景,同時對我們除錯也是有莫大幫助的。

如果你是透過 Omniverse Client 的形式連線到 Nucleus 伺服器是無法以 ASCII 建立 USD 的,這對於網路協作還有容量考量來說都不是一個有效率的實作。

::Define 則為所有 USD schema 定義 Prim 共通介面,必須傳入 UsdStage。在 USD 中絕大多數除數據型別的物件都是被包裝過的高級指標,一方面是要降低操作的門檻,一方面是要減少對物件生命週期的管理負擔。USD 中最重要的是對於編輯定義行為的理解,在這個階段你只要理解 USD 是透過路徑來識別物件即可。

對於 C++ 工程師來說,光是要成功建置一個連結 USD library 專案就考驗著對編譯參數、流程的掌握度,當你成功建置完這個簡單的 Hello World 範例卻會面臨一個更大的困難,那就是不管怎麼樣你都無法成功建立 stagestage 的內部指標會是 nullptr

這就牽涉到另一個 USD 的特殊能力: plugin,對 USD 來說,甚至連他自身所提供的 schema 都是 plugin。Python 工程師並不會遇到這個問題是因為他們在設定環境變數時已經將含有 plugin 設定的路徑加入執行環境中。但對於自己編譯執行檔的 C++ 工程師來說這個前提並不存在。若將存在 plugInfo.json 的目錄 usd 複製到執行路徑下,就能夠順利執行上面程式碼。

當然,在生產環境中我們並不見得會依照 Pixar 的路徑安排將 usd 擺放在執行目錄中,這時就必須透過PlugRegistr自己告訴 USD 你期望載入 plugin 的路徑。