USD の文法を使いましてシェーダネットワークが記述できるようになっていますが...
USD の Attribute の文法を流用してむりくり頑張っているためめんどいです...
おおまかな仕組み
Blender での Material / Shader Graph とだいたいは同じです.
(Maya の HyperShade とも同等かもしれません)
Material
: マテリアル. 複数のシェーダグラフを内包(もしくはシェーダの最終的な出力がマテリアルに繋がれる). surface
などの決まった出力がある.
Shader
: シェーダノード. info:id
でノードの種類を指定(UsdUVTexture
ならテクスチャノードなど)
Connection
Blender での Shader graph 例です.
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
で対応します.
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 と言われています.
同じ値をインデックス(配列要素番号)で参照するようにしてデータ削減などにも使われるようです.
ただ,
ドキュメントには higher purpose
で使われると, ちょっと何言っているかよくわからない説明ですが, USD ライブラリを使う側が, faceVarying にもかかわらず :indices
が存在したらその情報を参考にしたほうがいいよ, 程度でしょうか.
また, usdGeom の normals
にインデックスをつけることはできません. normals
を使うの自身あまり推奨されないようですので, primvars:normals
と primvars
ネームスペースをつけて対応します.
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 のほうがいいきもするけどね))を持たせるというものできます.
ここでも :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 のシェーダ解釈などは別途必要)
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)
あたりを参考にして,
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
で行けました!
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
とかでエラーがでる)