コンテンツにスキップ

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つがあるレイヤーを作成します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#usda 1.0
(
    endTimeCode = 30
    startTimeCode = 1
)

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

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

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

まずはClipを作ります。

1
2
3
4
5
6
7
8
9
#usda 1.0

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

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

次に、Manifestを作ります。

1
2
3
4
5
#usda 1.0
def "RotateAnimation"
{
    float3 xformOp:rotateXYZ
}

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#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を使用してみます。

1
2
3
4
5
6
7
8
9
#usda 1.0

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#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です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#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を使った場合はどうなるかなど、
より詳しいことはまた次回。