フォーマットの概要は
自前 AR アプリとかで USDZ を直接読むための USDC file format のメモ
https://qiita.com/syoyo/items/dbfecbc16468e6108d0d
を参照ください.
ここでは serialization のためのメモを残しておきます.
Tokens
シーン(Stage)で使われる全トークンをリストアップし, \0
で繋いで一つのバッファとして LZ4 圧縮かけます.
pxrUSD では Token は一意性を保つために一元管理しているので, そのデータベースを dump して作っているのかと思いきやシーンの Prim をトラバースしてそれぞれ新規に token のデータベースを作っています...
Strings
USDC では String(改行があったりと通常の文字列)も Token にエンコードし, StringIndex でトークンへのインデックスで管理します. なぜかこの StringIndex の配列は LZ4 圧縮はかけません.
これも Prim などをトラバースして収集します.
Paths
Prim や Property のパスリストです.
インデックスベースでの階層構造の情報もここに含みます.
以下の 3 つが含まれます.
- encodedPaths(Path index. 数はかならずしも総 path 数と同じとは限らない)
- elementTokens(elementPath 名(Prim や property の名前)のトークンのインデックス)
- jumps(階層構造のための jump index)
elementToken は Property の場合 negative になります.
jumps は木構造を二重連鎖木で表現のためでしょうか.
詳細はソースコード参照ください.
rel
などで指定する絶対パスはそのままのパス文字列情報としては保存されません.
階層構造に分解して implicit に保存(読むときは階層構造を復元しないと絶対パスがわからない)することになります.
また, Prim の階層構造に含まれないパスも考慮して(たとえば rel myrel = </bora/dora>
と指定しているが "bora" Prim が無いようなとき)パス階層を作ってあげる必要があります(crate には存在するが, 復元する場合は Prim tree(Spec) には出てこないようなもの).
構造上相対パスは扱いが難しいとおもいましたが, USDC ではやはり相対パスは扱えず, すべて絶対パスとして扱うようです.
USDA で相対パス指定はできるが, 読み込み時に絶対パスに変換される.
def Xform "hello"
(
)
{
def Sphere "world"
(
)
{
rel mypath = <../hello>
}
}
のような場合, 読み込んだ時点で絶対パスへ変換され
def Xform "hello"
(
)
{
def Sphere "world"
(
)
{
rel mypath = </hello/hello>
}
}
パスのリストとしては
/
/hello
/hello/hello
/hello/world
となります.
Fields(ValueRep)
Property など各種データを ValueRep にエンコードが必要になります.
インライン化できるかどうか(単位行列)もここで判定してエンコードしてあげる必要があります.
(シリアライズ側ではインライン化対応は必須というわけではないので, 任意ではあるでしょう)
FieldSet
Prim の情報も Property の情報も FieldSet で一括でまとめ, SpecType
でその FieldSet の役割を決める.
- FieldSet[0] : Xform (SpecType = PseudoRoot)
- FieldSet[1] :
xformOpORder
(SpecType = Connection) - FieldSet[2] :
xformOp:translate
(SpecType = Connection)
のような.
Specs, SpecType
Prim の場合は SpecPrim のみっぽい
Property の場合は Attribute か Relationsip のみの模様.
(.connect
は Attribute)
Property
rel mytarget
みたいに空の rel 定義以外は, アトリビュートの型情報 typeName
(token 変数) を持つ. token で文字列なので float
, matrix4d
などとなる.
Attribute
- 値を持たない attribute(e.g.
float outputs:result
など) - デフォルト値を持つ attribute(
float val = 3.1
).default
Field がある. - TimeSamples attribute*(
float val.timesamples = ...
). `
Relationship
rel target
のように定義だけでターゲットパスが無いもの.
specType = Relationship
field は variability
のみのケースが多いと思われる.
-
typeName
はなし -
variability
は optional
Connection Target, Relationship Target
connection が存在することをでっち上げるために connectionChildren
or targetChildren
も追加しておくとよい模様. それぞれ connectionPaths
(ListOp), targetPaths
(ListOp)に.
connectionChildren
: [/path/to/mat]
connectionPaths
: explicit list op: [/path/to/mat]
のように2つ定義を FieldSet に入れてあげる.
その他
primChildren
(TokenVector
) で子ノードの名前のリストがある.
Prim の階層構造は別途エンコードされるが, これは表示時(or トラバース時)の順序を指定するのに利用されます.
たとえばファイル(or C++ 内部)上では
/bora
/dora
のような順序で保存されていたとしても, primChildren = ["dora", "bora"]
とあれば,
/dora
/bora
の順で復元します.
properties
なども同様の役割を持つ... と思いきや properties
は補助的にあるだけで, pxrUSD ではこの順でソートしたりはしませんでした...
`
配列データの圧縮
pxrUSD では, 16 要素以上ある場合に圧縮の試みが行われます.
int, uint, int64, uint64 の配列は Int compress されます.
half, float, double の場合は, すべて int で表現できれば int compress します.
もしくは Lut で表現できれば Lut 化します.
それ以外の型(float3[]
など)は圧縮されません.
したがって face indices や, skin weight などの一部が圧縮されるだけで, 頂点座標などは圧縮されないのでどれだけ圧縮効果があるかは不明です.
また, 配列データの圧縮は必須ではないです.
(VRep で isCompressed flag を立たせなければ, 非圧縮でも USDC データとしては合法です)
LZ4 圧縮
0x7E000000(LZ4 の最大サイズ. およそ 2GB)単位で Chunk として圧縮する. 最大 127 chunk まで. したがっておよそ(圧縮して) 200 GB 程度までは一つのバッファとして扱えるようになっている.
chunk の個数は最初の 1 byte.