Skip to main content

Value Clips を使おう(基本構造編)

USD は、コンポジションアークをりようして、複数のレイヤーから1つのシーングラフを作る ことができますが、それだけだとあるアトリビュートの TimeSample される値は もっとも強いレイヤーの値になるため、時間方向に複数のレイヤーを合成するといったことが できません。 (サブレイヤーやリファレンスで、時間のオフセットやスケールはできる)

そういった場合に、より柔軟に対応できるようにする機能が「Value Clip」という機能です。

USD の Value Clip とは、様々なアトリビュートの TimeSampling、つまりはアニメーションを 使用するときに、レイヤーを複数のファイルに分割して ビデオ編集ツールのように、結合することができます。

これがどういうときに使えるかと言うと、

  • Crowd/BackGround 複数のキャラクターに対して適応する Clip を作成してシーケンスやサイクルを組んで さまざまなパターンを作りたい場合
  • 1つのフレームが超巨大なデータの場合

などが、公式の Grossaryで例として挙げられています。

これだけだとよくわからないので、サンプルを確認しながら構造を確認してみます。

ValueClip の基本構造

ValueClip は、大きく分けると3つの要素で構成されています。 1つがめ Stage、いわゆるアニメーションをさせたい Prim が存在するレイヤーです。 2つめが Clip、これは Stage のある Prim のアトリビュートに対して指定する値を持つレイヤーです。 3つめが Manifest、Manifest はある Clip のうち、実際に Stage の Prim に対して反映させる アトリビュートを指定するためのレイヤーです。

まずはシンプルな Cube1 つがあるレイヤーを作成します。

#usda 1.0
(
endTimeCode = 30
startTimeCode = 1
)

def Cube "TestModel" ()
{
float3 xformOp:rotateXYZ
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
}

ValueClip は、TimeSampling いわゆるアニメーションのための機能なので startTimeCode endTimeCode を指定する必要があります。

今の段階ではなんのアニメーションもありません。 これに対して ValueClip を指定します。

まずは Clip を作ります。

#usda 1.0

def "RotateAnimation"
{
float3 xformOp:rotateXYZ.timeSamples = {
0: (0, 0, 0),
30: (0, 359, 0)
}
}

Prim に対して、Rotate するアニメーションが指定されています。

次に、Manifest を作ります。

#usda 1.0
def "RotateAnimation"
{
float3 xformOp:rotateXYZ
}

Manifest は、構造的には Clip のレイヤーと構造は同様ですが、 こちらには実際の値は持たず、「TimeSample したいアトリビュートのみ」が 記載されています。

Manifest とはなんのために存在しているかというと、 複数の Clip を使用して合成したい場合、どのアトリビュートをサンプリングするか を決めるためには、すべての Clip を参照しなければなりません。 そうではなく、どのアトリビュートをサプリングがどうかを判断するための材料として 使用されます。 Clip に TimeSampling があるアトリビュートがあったとしても、 この Manifest にアトリビュートがなければ、ValueClip は動きません。 つまりはこの Manifest が Prim のうち「どれを実際に使うか」選ぶためのレイヤーとして 機能します。

準備はできたので、 この Clip と Manifest を使うようにします。

#usda 1.0
(
endTimeCode = 30
startTimeCode = 1
)

def Cube "TestModel" (
clips = {
dictionary default = {
double2[] active = [(1, 0)]
asset[] assetPaths = [@d:/work/py37/USD/clip/rotate.usda@]
asset manifestAssetPath = @d:/work/py37/USD/clip/manifest.usda@
string primPath = "/RotateAnimation"
}
}
){
float3 xformOp:rotateXYZ
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
}

assetPaths というのが、Clip レイヤーのことで、 primPath とは、Clip 内のどの Prim を Clip の対象にするか?という情報になります。 active は、今のステージのフレーム のときに どの Clip を使うのか?という情報です。 この場合は、1フレームから rotate.usda を使う...というふうに書かれています。

ValueClip は、他のコンポジションと違い、メタデータとして書かれているというのも 特徴です。

その結果。 Clip のレイヤーにあるアニメーションが、TestModel に適応されたのがわかります。 ですが、これだけだと1つの Clip が読み込まれただけなので 普通にコンポジションしたのとあまり違いがありません。

次に、複数の Clip を使用してみます。

#usda 1.0

def "RotateAnimation"
{
float3 xformOp:rotateXYZ.timeSamples = {
0: (0, 0, 0),
30: (0, 0, 359)
}
}

さっきとは違い、Z 軸で回転します。

#usda 1.0
(
endTimeCode = 30
startTimeCode = 1
)

def Cube "TestModel" (
clips = {
dictionary default = {
double2[] active = [(1, 0),(2, 0),(3, 0),(4, 0),(5, 0),(6, 1),(7,1),(8, 1),(9, 1),(10, 1)]
asset[] assetPaths = [@d:/work/py37/USD/clip/rotate.usda@,
@d:/work/py37/USD/clip/rotate_2.usda@]
asset manifestAssetPath = @d:/work/py37/USD/clip/manifest.usda@
string primPath = "/RotateAnimation"
}
}
){
float3 xformOp:rotateXYZ
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
}

Clips を書き換えます。 Z 軸回転するレイヤーを assetPaths に追加して、 active で、今の CurrentTime のときにどのレイヤーを使うかを指定します。 今回は1~ 5 までは Y 軸回転、6 ~ 10 が Z 軸回転をするレイヤーを読むようにしました。

結果。 時間方向に、参照するレイヤー(Clip)が変わっているのがわかるかと思います。

usdview をみると、Clip を使用しているアトリビュートの Layer Stack を見ると Clip のレイヤーが表示されているのがわかります。

CurrentTime を移動して再度確認すると、LayerStack のレイヤーも変わっています。

Template

上の例だと、参照先の Clip は、CurrentTime と assetPaths の Index によって紐付けされていました。 そうではなく、 clip.#.usda のように、CurrentTime の(あるいはマッピングされた)数字の Clip のレイヤーを参照しにいくようにするのが Template です。

#usda 1.0
(
endTimeCode = 4
startTimeCode = 1
)

def "TestModel" (
clips = {
dictionary B = {
asset manifestAssetPath = @d:/work/py37/USD/manifest.usda@
string primPath = "/ModelB"
string templateAssetPath = "d:/work/py37/USD/clip/B/clip.#.usda"
double templateEndTime = 4
double templateStartTime = 1
double templateStride = 1
}
}
)
{
double a
}

Template の場合でも、Manifest と Clip は共通ですが、Clip を読み込む Prim の 構造が変わります。 assetPaths で、配列で指定していたところが templateAssetPath に対して name.#.usda のように、数字部分を # で書き表した Path で指定し、 その Template のどの範囲を Clip として読み込むかをを startTime,endTime で 指定します。

このようにすると、CurrentTime が1なら clip.1.usda の Clip を参照...といったような 使い方ができます。 これは、最初にあげられた使用例のうち 巨大なデータを扱う場合などに、全フレームを別レイヤーとして作成したい場合などに 使いやすいのではないかと思います。

まとめ

というわけで、まずは基本的な構造からみてきましたが これを使えば、アニメーションのデータをアトリビュート・フレーム単位で 別レイヤーとして作成できるというのがわかりました。

これを使えば、レイヤーの構造の自由度があがる(=複雑化する)気がしますが じゃあ他のコンポジションと組み合わさった時にどうなるか 複数の Clip を使った場合はどうなるかなど、 より詳しいことはまた次回。