usdShade のメモ

USD の文法を使いましてシェーダネットワークが記述できるようになっていますが...

https://graphics.pixar.com/usd/release/api/usd_shade_page_front.html

https://developer.nvidia.com/usd/mdlschema

USD の Attribute の文法を流用してむりくり頑張っているためめんどいです...

## おおまかな仕組み

Blender での Material / Shader Graph とだいたいは同じです.
(Maya の HyperShade とも同等かもしれません)

`Material` : マテリアル. 複数のシェーダグラフを内包(もしくはシェーダの最終的な出力がマテリアルに繋がれる). `surface` などの決まった出力がある.
`Shader` : シェーダノード. `info:id` でノードの種類を指定(`UsdUVTexture` ならテクスチャノードなど)

## Connection

Blender での Shader graph 例です.

![Screenshot from 2022-09-06 15-10-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36275/3b2aca91-d1f2-1ece-f007-cf974e1dff50.png)

USD の場合もこれと同様な感じで定義していきます.
connection は destination の input(consumer) から source の output(producer) の方向で張ります
(図でいうと右から左方向)

USD での Material 例はこんな感じです.

```
def Material "mymat" {
  float inputs:myval = 1.3

  token outputs:surface = <./pbr.outputs:surface>

  def Shader "pbr" {
    uniform token info:id = "MyPBRShader"
 
    color3f inputs:diffuseColor.connect = <../diffuseTex.outputs:rgb>
    token outputs:surface
  }

  def Shader "diffuseTex" {
    uniform token info:id = "UsdUVTexture"
    float2 inputs:st.connect = <./texcoordReader.outputs:result>
    
    float3 outputs:rgb
    
    def Shader "texcoordReader" {
      uniform token info:id = "UsdPrimvarReader_float2"
      token inputs:varname = "st"
    }
  }

}
```

`inputs`, `outputs` namespace が使われます. 自身のシェーダを新規で定義する場合は任意にできる... かなとは思います.

ややこしいのは `Material` の `outputs:surface` は cosumer(終端アトリビュート. terminal attribute) のため `Shader` での `inputs` に対応することです.
(したがって `Material` では `outputs:***` から `Shader` への attribute に connection を貼る)


また, `Shader` での output アトリビュートは定義だけで, 値や connect がアサインされることはありません.
(構文上はアサインできるがたぶん無視される?)

UsdShadeNodeGraph から派生したのは connection を持つことができる(Material).
UsdShadeShader から派生したのは connection を持たない(Shader).
https://graphics.pixar.com/usd/dev/api/usd_shade_page_front.html

のためです.
  
`Material` で `inputs:***` で定義したアトリビュートは, シェーダ側で参照(connect の targetPath)することができます
(global 変数的なものとして使う感じかしらん)  

## Predefined shader

USD コア仕様ではシェーダ関連は実は定義されておらず(Prim としての `Material`, `Shader` が定義されているくらい?), Blender や USDZ などで使われるシェーダの定義は, `usdImaging`(Hydra でレンダラなどとつなぐとき用のパッケージ?)で plugin 相当で行われています.

`USD/pxr/usdImaging/plugin/usdShaders/shaders/shaderDefs.usda`

* `UsdPreviewSurface` : プレビュー用サーフェスシェーダ
* `UsdUVTexture` : UV texture mapping. Texture tranform(`UsdTransform2d`) なども含む
* `PrimvarReader` : texture mapping 用の UV 座標をジオメトリのどの primvar(アトリビュート)にするかを指定

の 3 つになります.

## PrimVar reader

USD では, multi-UV texturing などのために, メッシュなどのジオメトリのどの UV coordinate の情報(primvar)を利用するか(読み取るか)を PrimvarReader で指定します.

型としては float, float2, ..., string, matrix4d などが使えます.
(`string` 型の PrimvarReader って何に使うのかしらん?)

PrimvarReader ノードの "varname" で, ジオメトリの primvar を指定します.

```
    def Shader "texcoordReader" {
      uniform token info:id = "UsdPrimvarReader_float2"
      token inputs:varname = "st"
    }
```


ややこしいのはここでの `varname` には `primvars` namespace を含まないことです.
`token inputs:varname = "st"` とあったら, 対応する(マテリアルがアサインされた)ジオメトリの `primvars:st` が参照されます.
(primvar reader と `primvars:st` で型の不一致が起こったらどうなるんじゃろ?)



## interfaceOnly

```
    int inputs:useSpecularWorkflow = 0 (
        connectability = "interfaceOnly"
        doc = """This node can fundamentally operate in two modes :
            Specular workflow where you provide a texture/value to the
            "specularColor" input. Or, Metallic workflow where you
            provide a texture/value to the "metallic" input."""
    )
```

のように, Schema で `interfaceOnly` となっているもの.

Shader での `input` attribute のいくつかがこれに相当します. どこかへとつなぐか, もしくは同じく `interfaceOnly` を持つ `input` から繋がれることができます. 

たとえば `inputs:diffuseColor` は `interfaceOnly` ではないので, `inputs:diffuseColor.connect = ...` でテクスチャノードに繋ぐ(テクスチャノードの情報を利用する)ことはできますが, 別のシェーダノードから, `color3 val.connect = <myshader.inputs:diffuseColor>` というのはできない(はず).

`interfaceOnly` でも, fallback としての値(`.timeSamples` 含む)を指定することもできます.
両方指定された場合, `.connect` が優先(Connection が有効であれば)されます. usdShade 解説ドキュメントでの `Valid Shader Connections Win Over Input Values` を参照ください.

## NodeGraph

よりシェーダグラフを汎用的に記述するためのもの?
よくわらぬ.

## ジオメトリでのマテリアルの割当

ジオメトリ側で `rel material:binding` でマテリアルをアサインします.

```
def Mesh "mymesh" {
  ...
  rel material:binding = <../mymat>
  ...
}
```

プレビュー用マテリアルとか, collection とか, bindMaterialAs などの strength の設定などもあります

#### GeomSubset

per-face material をしたい場合は, `GeomSubset` で対応します.

https://fereria.github.io/reincarnation_tech/65_SampleCode/Notebook/USD/Material/geomsubset_01/

#### double sided material

あんまり情報がないですが, familyName を指定することで可能になると思われます.
(`family` とかややこしい名前だね... `group` とかのほうがよいとおもう)

familyName で GeomSubset の`group`のようなものを指定することができます.

```
def GeomMesh "cube" {
    ...
    
    uniform token subsetFamily:side0:familyType = "unrestricted"
    uniform token subsetFamily:side1:familyType = "unrestricted"

    def GeomSubset "side0"
    {
        uniform token elementType = "face"
        uniform token familyName = "side0"
        int[] indices = [0, 3, 5]
    }

    def GeomSubset "side1"
    {
        uniform token elementType = "face"
        uniform token familyName = "side1"
        int[] indices = [0, 3, 5]
    }

```

また, familyName に対応する familyType を上位 GeomMesh で指定します.

`subsetFamily:<FAMILYNAME>:familyType`

familyType は subset の index が elementType のジオメトリ要素(現状は elementType face しか対応していないため, face index)がどのようにアサインされるかを指定します.


* partition : それぞれの GeomSubset の face id は GeomMesh の face を一意(unique)かつすべての face を参照する. つまり GeomMesh の faces を重なりや余りがなく subset に分解する
* nonOverlapping : 各 GeomSubset の face id は相互排他, つまりある GeomSubset の face id は他の GeomSubset に現れてはならない. `partition` とは違い GeomMesh のすべての face を参照しなくともよい(つまり subset でカバーしない face が存在してもよい)
* unrestricted : 制限なし. 

最近になり pxrUSD `dev` 版で `point` もサポートされました. point の場合も同様です.

## 法線ごと, テクスチャ UV ごとのインデックス

法線ごと, テクスチャ UV ごとにインデックスを持ちたい...
(OpenGL とかリアルタイムレンダリングでは複数インデックスあつかうの無理だったり SSBO とかで無理くり頑張るしかなかったりするが, オフラインレンダリングだったりジオメトリ/シェーダのデータ交換とかでは複数インデックス持つのは普通だったりする).

usdGeom の Primvar(Property) では, `:indices` suffix があればそのアトリビュートのインデックスを個別に指定できます.

e.g.

```
normal3f primvars:normals = [ ... ] ( interpolation = "..." )
int[] primvars:normals:indices = [...] 
```

Indexed Primvar と言われています. 

https://graphics.pixar.com/usd/dev/api/class_usd_geom_primvar.html

同じ値をインデックス(配列要素番号)で参照するようにしてデータ削減などにも使われるようです.
ただ,  
ドキュメントには `higher purpose` で使われると, ちょっと何言っているかよくわからない説明ですが, USD ライブラリを使う側が, faceVarying にもかかわらず `:indices` が存在したらその情報を参考にしたほうがいいよ, 程度でしょうか.

また, usdGeom の `normals` にインデックスをつけることはできません. `normals` を使うの自身あまり推奨されないようですので, `primvars:normals` と `primvars` ネームスペースをつけて対応します.

https://github.com/PixarAnimationStudios/USD/issues/706

### Composite(Flatten)

pxrUSD で Composite(Flatten) させると index を参照して, 元の変数(テクスチャ座標とか)を並び替えします
(tinyusdz では `flatten_by_indices`)

```
float primvars:var = [1.0, 3.1, 3.4]
int[] primvars:var:indices = [0, 0, 1, 0, 1]
```

とあると,

```
float primvars:var = [1.0, 1.0, 3.1, 1.0, 3.4]
```

となります(たぶん)

## Id attribute

primvar には,  id 配列(`インデックス`と似たものと誤解しがちであるがこちらは string id となる(token id のほうがいいきもするけどね))を持たせるというものできます.

https://graphics.pixar.com/usd/dev/api/class_usd_geom_primvar.html

ここでも `:indices` を使うことができるようです.

```
string[] primvars:myids = ["bora", "dora", ...]
int[] primvars:myids:indices = [0, 1, 0, 0, ...]
```

また,  Relation の `:idFrom` で id の配列を参照(Reference)させることもできるようですが...

```
rel primvars:myids:idFrom = [</bora/dora>, </bora/muda>]
```

pxrUSD のソースコードとかみても実例が皆無なのでよくわかりませんね...

まとめると,　`:indices` と `:idFrom` は suffix として使われるので, これをそのまま primvar の名前にするのは避ける必要があります(e.g. `primvars:indices` は NG)

## MaterialX とかその他のシェーダシステム

MaterialX のファイルを指定することで, MaterialX 対応できます(単に記述はできるというレベルなので, 利用側で MaterialX のシェーダ解釈などは別途必要)

https://graphics.pixar.com/usd/release/api/usd_mtlx_page_front.html

```
def Scope "Materials" {
    def Material "MaterialX" (
        references = [
            # USD Preview Surface MaterialX Nodes
            @./usd_preview_surface_nodes.mtlx@</MaterialX>,
        ]
    )
    {
    }
}
```

と, `mtlx`(XML ファイル! めんどいネ) を references で読み込むことで対応します.
MaterialX 自身はシェーダのパラメータ設定集みたいな感じなので, 利用側(レンダラなど)で別途シェーダは必要です(MaterialX 自体にリファレンスのシェーダがありますが, Subsurface などはレンダラ側での実装が必要となる)


MDL では特定のプロパティを追加で対応のようです.

```
def Shader "flex_material"
{
    token outputs:out
    uniform token info:implementationSource = "sourceAsset"
    uniform asset info:mdl:sourceAsset = @nvidia/core_definitions.mdl@
    uniform token info:mdl:sourceAsset:subIdentifier = "flex_material"
  ...
}
```

その他のシェーダシステムは... T.B.W.

## Connect 先の値のアクセス(C++, Python)

https://fereria.github.io/reincarnation_tech/11_Pipeline/01_USD/30_USD_Programming/01_Python/06_shader/

あたりを参考にして,

```
def Material "Mat"
{
    token inputs:st = "uv0"
    ...
            def Shader "primvareader"
            {
                ...
                token inputs:varname.connect = </Mat.inputs:st>
            }
}
```

のようなシェーダを作ったとします.
ここで `inputs:varname` の実際の値を取得したいとき, `UsdShade.Shader` の場合だと, connection がある場合は `Get()` ではリンク(connection)先の値を取得できません.
(`UsdAttribute` も同等?)

たとえばPython で,

`GetInput('varname').Get()` としても `"uv0"` ではなく `None` が帰ってくる...

真面目に Connection をたどるのが本当はいいかもしれませんが, `GetConnection()` だと単に connection の情報しかかえってこない...

```
(UsdShade.ConnectableAPI(Usd.Prim(</TexModel/boardMat>)), 'frame:stPrimvarName', pxr.UsdShade.AttributeType.Input)
```

とりあえずは `GetValueProducingAttributes` で行けました!

https://graphics.pixar.com/usd/release/api/class_usd_shade_utils.html#ad1f99058557167a9282c2531f2332bd6


```
attrs = GetInput('varname').GetValueProducingAttributes()


# [Usd.Prim(</TexModel/boardMat>).GetAttribute('inputs:frame:stPrimvarName')]
# のように Attribute のリスト返ってくる.

print("varname = attrs[0].Get())
# => "st" 
```

で値取得できました... めんどくさいネ...

ところで pxrUSD の Python binding はエラーチェックとかあんまりなくて, 呼び出し変にやるとすぐクラッシュしてつらい...
    

## 循環参照?

```
def Scope "bora" {
  token st.connect = </bora/pxrUsdPreviewSurface1SG.bora>

  def Material "pxrUsdPreviewSurface1SG"
  {
      token bora.connect = </bora.st>
  }
}
```

ユーザ定義のアトリビュートだと上記のような循環参照ができてしまうが, Shader `inputs:***` でやると schema で `interfaceOnly` あたりの設定のおかげか循環参照はできないようになっている(`usdchecker` とかでエラーがでる)