Skip to main content

CompArc(1) サブレイヤー

前回ざっくりと個々のコンポジションアークの概要をしたので
これからは個々のコンポジションについての動作を説明していきたいと思います。

まず第一回は「サブレイヤー」について。

サブレイヤーとは

サブレイヤーとは、プログラム言語的に言えば「Import」や「Include」
DCC ツール的に言えばシーンのインポートに近い処理です。

図で表すとこのような処理になります。
各レイヤーごとのノードツリーの階層構造を維持しつつ
同階層がある場合はいいかんじに階層を維持しつつ
レイヤーをまるごとマージしていきます。

ルートレイヤーとは

まず個々で新しく「RootLayer」と呼ばれる概念が出てきました。
これは、ざっくり言うと「現在開いている USD ファイル」が RootLayer となります。

サブレイヤーの評価は、RootLayer  つまりは現在開いたレイヤーから見て
一番遠いレイヤーから上書きして行きます。

散々参考にだしているこの図で言うと

サンプルの USD はこちら

まず base.usda を usdview で開いてみると緑の玉が出てきます。

#usda 1.0
def "Model"
{
def Sphere "Sphere"
{
rel material:binding = </Material/MyMat>
}
}
def "Material"
{
def Material "MyMat"
{
token outputs:surface.connect = </Material/MyMat/testShader.outputs:surface>

def Shader "testShader"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0, 1, 0)
float inputs:metalic = 0.9
float inputs:roughness = 0.2
token outputs:surface
}
}
}

usda の中身はこんな感じ。

次に add_color.usda を開いてみると

見た目はこのように「赤い Sphere」が出ています。
usda を開いてみると

#usda 1.0
(
subLayers = [
@base.usda@
]
)

over "Material"
{
over "MyMat"
{
over "testShader"
{
color3f inputs:diffuseColor = (1, 0, 0)
}
}
}

こうなっています。
このフォーマットを見てみると、
上の base.usda に対して testShader にある inputs:diffuseColor = (1,0,0) だけが書かれ
かつレイヤーのメタデータ内に subLayers がある事が分かります。

!!! info プリムの定義部分は、他では def ~~~ になっていましたが
このファイルの場合 over になっています。
これは、サブレイヤー時に「もし定義がなければプリムを作らず、あったら上書き」
という定義になります。
なのでこの例の場合、「マテリアルの定義がすでにあったら、diffuseColor を赤にする」
という意味になります。

繰り返しになりますが、USD の特徴はコンポジションをしてシーンを構築していくことにあります。
それはどういうことかというと
各 USD は「他のレイヤーとの差分を、それぞれ保持」しているという事です。

サブレイヤーに限らず、コンポジションアークでレイヤーを合成するときには
この差分情報を評価し、最終的なステージのシーングラフを構築します。

なので、最後の final.usda を開いたときにこのような Cube が出てきますが

#usda 1.0
(
subLayers = [
@add_color.usda@
]
)

over "Model"
{
def Cube "Sphere"
{
}
}

マテリアルの色情報や構造は base.usda + add_color.usda の情報が来ていますが
最後の final.usda で、形状の Sphere プリムを Cube にオーバーライドしているので
形状が Cube に変化しつつも
差分情報以外はサブレイヤーの情報が引き継がれていることが分かります。

評価順序とレイヤースタック

それぞれのレイヤーが差分情報を持ち、オーバーライドされていくのはわかりましたが
では、このレイヤー結合はどのような優先順位で評価されるのでしょうか。
サンプルシーンで試してみます。

#usda 1.0
(
subLayers = [
@subA.usda@,
@subB.usda@
]
)

def "testPrim"
{
}

まずこんなファイルを作り、

#usda 1.0

def "testPrim"
{
string hoge = "subA"
}

サブレイヤーで読んでいる usda 中身は、こんな感じのファイル名のはいった
アトリビュートを入れておきます。

usdview でみてみると、 hoge = subA なので
1 つのレイヤーで subLayers をいれた場合は、配列の 0 番側が優先的に評価されるようです。

では、コレがどうなっているのかを usdview を使用して確認してみます。

確認したいプリムをクリックし、右下の「Layer Stack」タブをみてみます。
すると、現在選択中のプリムを合成するのに評価されたレイヤーが表示されます。

この「レイヤースタック」とはなにかというと
今回のように複数のレイヤーを結合していった際に使用したすべてのレイヤーをまとめた物
の事を指します。

それを踏まえてもうすこし複雑にしてみます。

#usda 1.0

(
subLayers = [
@subC.usda@,
@subD.usda@
]
)

def "testPrim"
{

}

subA.usda をこのように書き換えて 同じような構造の C と D を作ります。

関係図を書くとこうなります。

この結果を確認するとどうなるかというと

C が読み込まれました。

レイヤースタックを確認すると、こうなっています。
なんとなくわかってきましたが、ちょっと不確定な部分があるので

もうちょっとサブレイヤーの階層を増やしてみます。

その結果のレイヤースタック。

見て分かるとおり、末端部分から深さ優先(DFS PostOrder?)で帰的に呼ばれているのが分かります。

まとめると、サブレイヤーはルートレイヤーからサブレイヤーで指定された各レイヤーを
再帰的に上書き結合して、1 つのレイヤーにする処理のことで、
その各サブレイヤーをまとめたものを「レイヤースタック」
と呼びます。

LIVRPS 原則の「Local」

前回のコンポジションアークの説明 の説明でふれた各レイヤーの合成原則の時に、
この中に「サブレイヤー」が含まれていないことに気づくかと思います。

実は、この中の「L」とはレイヤースタックに積まれたレイヤーの結合結果
を「Local」と言います。
つまり、サブレイヤーで重ねた状態というのは
他のコンポジションアークの要素と比較して、最も強い主張を持っていて
構造のベース部分になるものと言えます。
このサブレイヤー自体もツリー構造を持ちますが
上に書いたように再帰的に(一定のルールで)合成され、
最終的に 1 つのレイヤーに結合される形になります。

というわけでサブレイヤーはこのくらい。
次は原則順にそって Inherits(継承) について説明していきたいと思います。