USDの実践メモ 03 : マテリアルの作成


[前回](https://qiita.com/snowxcrash/items/4e6e58e13c04dafc5749)に続き、今回はマテリアルを作成します。

## マテリアルの作成

マテリアルを作成します。マテリアルとは次に挙げるシェーダーネットワークのコンテナとして機能させるもので，この考えを理解しておくことが重要です。

```Python
from pxr import Usd, UsdGeom, UsdShade, Sdf, Gf

def create_material(stage, path):
    material = UsdShade.Material.Define(stage, path)
    return material
```

## シェーダーの作成

シェーダーを作成します。先ほども述べたように，マテリアルはコンテナですからシェーダーはマテリアルの Sdf パスの下に作成するようにします。まず，PBR シェーダー（UsdPreviewSurface）から作成していきます。*metallic* と *roughness* の両プロパティに加えて，マテリアルの色と透過度も設定してみました（USDPreviewSurface については公式マニュアルの[ここ](https://openusd.org/release/spec_usdpreviewsurface.html)に詳しい説明があります）。

```Python
def create_shader(stage, path):
    shader = UsdShade.Shader.Define(stage, path)
    shader.CreateIdAttr('UsdPreviewSurface')

    # Inputs
    shader.CreateInput('metallic', Sdf.ValueTypeNames.Float).Set(0.0)
    shader.CreateInput('roughness', Sdf.ValueTypeNames.Float).Set(0.4)
    shader.CreateInput('diffuseColor', Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(1, 0, 1))  # Magenta
    shader.CreateInput('opacity', Sdf.ValueTypeNames.Float).Set(1)

    return shader
```

先ほどのマテリアルの定義を書き換えて，シェーダーの surface output に接続するための「ターミナル」を設けるようにします。

```Python
def create_material(stage, path):
    material = UsdShade.Material.Define(stage, path)

    # Terminal outputs
    material.CreateSurfaceOutput()
    
    return material
```

マテリアルと PBR シェーダーをつなぎ，さらにメッシュをマテリアルにバインドします。これらはメインプログラムで行うことにします（ステージ作成からメッシュ作成までは前回までの記事と同じです）。

```Python
if __name__ == '__main__':
    # ステージ作成
    stage = create_stage()

    # ルートノード作成
    rootNode = create_node(stage, '/World', is_defaultPrim=True)

    # メッシュ作成
    mesh = create_mesh(stage, rootNode.GetPath().AppendChild('Mesh'))

    # マテリアル作成
    material = create_material(stage, rootNode.GetPath().AppendChild('Material'))

    # シェーダー作成
    shader = create_shader(stage, material.GetPath().AppendChild('PBRShader'))

    # マテリアルとシェーダーの接続
    material.GetSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), 'surface')

    # メッシュとマテリアルのバインド
    api = UsdShade.MaterialBindingAPI(mesh)
    api.Apply(mesh.GetPrim()).Bind(material)

    # USDファイルにエクスポート
    stage.GetRootLayer().Export('./Material.usda')
```

出力は次のとおりになりました。usdchecker もパスしています。

```USD
#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 0.01
    upAxis = "Y"
)

def Xform "World"
{
    def Mesh "Mesh" (
        prepend apiSchemas = ["MaterialBindingAPI"]
    )
    {
        uniform bool doubleSided = 0
        int[] faceVertexCounts = [4, 4]
        int[] faceVertexIndices = [0, 1, 4, 3, 1, 2, 5, 4]
        rel material:binding = </World/Material>
        point3f[] points = [(-1, 0, 0), (0, 0, 0), (1, 0, 0), (-1, 1, 0), (0, 1, 0), (1, 1, 0)]
        texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1)] (
            interpolation = "faceVarying"
        )
        int[] primvars:st:indices = [0, 1, 2, 3, 5, 4, 7, 6]
    }

    def Material "Material"
    {
        token inputs:frame:stPrimvarName = "st"
        token outputs:surface.connect = </World/Material/PBRShader.outputs:surface>

        def Shader "PBRShader"
        {
            uniform token info:id = "UsdPreviewSurface"
            color3f inputs:diffuseColor = (1, 0, 1)
            float inputs:metalic = 0
            float inputs:opacity = 1
            float inputs:roughness = 0.4
            token outputs:surface
        }
    }
}
```

```bash
$ usdchecker Material.usda 
Success!
```

マテリアルを表示してみます。前回作成したメッシュがマテリアルの表面にバインドされました。

![material_300x151.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659949/18d92c7b-0b1e-d9c0-d51a-9d2e4b152c34.png)

## まとめ

マテリアルを作成して，前回作成したメッシュをこの面にバインドすることができました。次回はここに画像を貼り付けていきます。

## 参考資料

ft-lab (Yutaka Yoshisaka) さん，あんどうめぐみさん による以下のサイトが理解の助けになりました。

https://github.com/ft-lab/Documents_USD/blob/master/doc/usd_create_material.md

https://ft-lab.jp/blog_3dcg/?p=4836

https://fereria.github.io/reincarnation_tech/usd