[Omniverse] MaterialXを使う – その3 : separate

[Omniverse] MaterialXを使う – その2」の続きです。
以下についてメモ書きです。

  • テクスチャのRGBAチャンネルを分解し、MetallicやRoughnessのfloatとして使用 (separate)
  • NormalMapの表現
  • floatの引き算 (subtract)
  • UnityのマテリアルをMaterialX(mtlxファイル)で表現

ここではOmniverse Create 2022.3.1を使用しました。

テクスチャのRGBAチャンネルから1つを取り出す

"separate"タグを使うことで、テクスチャのRGBまたはRGBAから個々のチャンネル値をfloatで取得します。

以下が参考のドキュメントです。

MaterialX: Supplemental Note
https://materialx.org/assets/MaterialX.v1.38.Supplement.pdf

実際のmtlxの記述は、以下が参考になります。
https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/resources/Materials/TestSuite/stdlib/channel/channel.mtlx

以下は、テクスチャ画像(png)ファイルのR成分を取り出しています。

<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">

  <!-- Get texture -->
  <nodegraph name="NG_texture" fileprefix="../images/">
    <tiledimage name="image_texture" type="color4">
      <input name="file" type="filename" value="tile_image.png" />
    </tiledimage>
    <output name="out" type="color4" nodename="image_texture" />
  </nodegraph>

  <!-- Separate texture into RGBA -->
  <nodegraph name="NG_separate">
    <separate4 name="separate" type="multioutput">
      <input name="in" type="color4" nodegraph="NG_texture" output="out" />
    </separate4>
    <output name="out" type="float" nodename="separate" output="outr" />
  </nodegraph>
</materialx>


"NG_texture"のNodeGraphでテクスチャをRGBAを持つcolor4として取得します。
次に、"NG_separate"のNodeGraphで"separate4"タグを使用しています。
これはcolor4またはvector4を4つに分解します。
RGBのcolor3を分解する場合は"separate3"タグを使います。

<separate4 name="separate" type="multioutput">
</separate4>

の type="multioutput" は忘れずにつけるようにしてください。

<input name="in" type="color4" nodegraph="NG_texture" output="out" />

の指定で、nodegraphで指定されるノードグラフ名、outputで指定される出力を与えています。
これはテクスチャのRGBA(color4)が入力されます。

<output name="out" type="float" nodename="separate" output="outr" />

の指定で、nodenameで指定したノードのR成分をfloatとして取り出します。
output="outr"はR成分の取り出し。
output="outg"はG成分の取り出し。
output="outb"はB成分の取り出し。
output="outa"はA成分の取り出し。

この場合、テクスチャのRGBAにパックされたRoughness/Metallicなどを分解してfloatにする、という流れができそうですね。

Unityの標準的なマテリアルをMaterialXに割り当てる

では、実用的な例としてUnityのBaseColor Map、MetallicSmoothness Map、Normal MapをMaterialXのstandard_surfaceに渡してみましょう。
Occlusionはstandard_surfaceにはないようでした。

Unityで以下のうち、Occlusion以外の3つのテクスチャを使用しました。

テクスチャ名 説明
table_zataku_baseColor.png Base Map
table_zataku_MetallicSmoothness.png R:Metallic
A:Smoothness
table_zataku_normal.png Normal Map
table_zataku_occlusion.png Occlusion Map

MetallicSmoothnessマップ

Unityでは、MetallicSmoothnessは1つのテクスチャにパックされています。

参考 :
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/lit-shader.html#channel-packing

以下のようにチャンネルごとに要素が分かれます。

  • [R] Metallic
  • [G] Occlusion
  • [B] None
  • [A] Smoothness

Smoothness値はRoughness = 1.0 – Smoothnessとして計算できます。
多くのフォーマットではRoughness値を使うことが多いと思われます。
MaterialXもRoughessを使うため、Smoothness値は反転させる必要があります。

Unityでの表現

UnityのURPではマテリアルを以下のように指定しました。

以下のように木のテーブルを配置しています。

これをMaterialXのmtlxファイルに渡していきます。

いくつか引っかかった部分があるため、先にそれを書いておきます。

mtlx : テクスチャは1つのnodegraphにまとめる

テクスチャは"tiledimage"タグで指定しますが、複数扱う場合は1つのnodegraphにまとめて入れないとエラーになるようでした。
これらの記載例は後述します。

mtlx : NormalMapの表現

NormalMapはテクスチャをvector3型で指定し、normalmapタグに渡す。
standard_surfaceタグでは"normal"に渡したら表現できました。

<nodegraph name="NG_textures" fileprefix="../images/">
  <image name="normalMap_texture" type="vector3">
    <input name="file" type="filename" value="normal.png" />
  </image>

  <normalmap name="normalMap" type="vector3">
    <input name="in" type="vector3" nodename="normalMap_texture" />
  </normalmap>
  <output name="out_normal" type="vector3" nodename="normalMap" />
</nodegraph>

<standard_surface name="SR_unity" type="surfaceshader">
  <input name="normal" type="vector3" nodegraph="NG_textures" output="out_normal" />
</standard_surface>

mtlx : Unityのマテリアルをmtlxで表現

最終的なmtlxファイルの内容を記載します。

<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">

  <!-- Textures -->
  <nodegraph name="NG_textures" fileprefix="../images/zataku/">
    <tiledimage name="baseColor_texture" type="color3">
      <input name="file" type="filename" value="table_zataku_baseColor.png" colorspace="srgb_texture" />
    </tiledimage>
    <tiledimage name="metallicSmoothness_texture" type="color4">
      <input name="file" type="filename" value="table_zataku_MetallicSmoothness.png" />
    </tiledimage>
    <image name="normalMap_texture" type="vector3">
      <input name="file" type="filename" value="table_zataku_normal.png" />
    </image>

    <normalmap name="normalMap" type="vector3">
      <input name="in" type="vector3" nodename="normalMap_texture" />
    </normalmap>

    <output name="out_baseColor" type="color3" nodename="baseColor_texture" />
    <output name="out_metallicSmoothness" type="color4" nodename="metallicSmoothness_texture" />
    <output name="out_normal" type="vector3" nodename="normalMap" />
  </nodegraph>

  <!-- Separate texture into RGBA -->
  <nodegraph name="NG_separate">
    <separate4 name="separate" type="multioutput">
      <input name="in" type="color4" nodegraph="NG_textures" output="out_metallicSmoothness" />
    </separate4>
    <output name="out_metallic" type="float" nodename="separate" output="outr" />
    <output name="out_smoothness" type="float" nodename="separate" output="outa" />
    <output name="out_occlusion" type="float" nodename="separate" output="outg" />
  </nodegraph>

  <!-- Convert Smoothness to Roughness -->
  <nodegraph name="NG_roughness">
    <subtract name="subtract" type="float">
      <input name="in1" type="float" value="1.0" />
      <input name="in2" type="float" nodegraph="NG_separate" output="out_smoothness" />
    </subtract>
    <output name="out" type="float" nodename="subtract" />
  </nodegraph>

  <standard_surface name="SR_unity" type="surfaceshader">
    <input name="base" type="float" value="1" />
    <input name="base_color" type="color3" nodegraph="NG_textures" output="out_baseColor" />
    <input name="metalness" type="float" nodegraph="NG_separate" output="out_metallic" />
    <input name="specular_roughness" type="float" nodegraph="NG_roughness" output="out" />
    <input name="normal" type="vector3" nodegraph="NG_textures" output="out_normal" />
  </standard_surface>

  <surfacematerial name="UnityMaterial" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="SR_unity" />
  </surfacematerial>
</materialx>

name="NG_textures"のnodegraphには、3つのテクスチャを格納しました。

タグ名 name 説明
tiledimage baseColor_texture BaseColorテクスチャ
tiledimage metallicSmoothness_texture MetallicSmoothnessテクスチャ
image normalMap_texture NormalMapテクスチャ

BaseColorテクスチャはRGBを使うcolor3型としてます。
MetallicSmoothnessテクスチャは、Unityの該当テクスチャをそのまま使用しています。
R:Metallic、A:SmoothnessなのでRGBAのcolor4の型にしています。
NormalMapテクスチャは色ではなくベクトル情報になるため、vector3型にしています。

NormalMapについては、以下の記載で法線のvector3に変換しています。

<image name="normalMap_texture" type="vector3">
  <input name="file" type="filename" value="table_zataku_normal.png" />
</image>

<normalmap name="normalMap" type="vector3">
  <input name="in" type="vector3" nodename="normalMap_texture" />
</normalmap>

この"NG_textures"のNodeGraphのoutputは以下のようにしました。

<output name="out_baseColor" type="color3" nodename="baseColor_texture" />
<output name="out_metallicSmoothness" type="color4" nodename="metallicSmoothness_texture" />
<output name="out_normal" type="vector3" nodename="normalMap" />

それぞれ型(type)が違うのに注意してください。

"NG_textures"の"out_metallicSmoothness"のRGBAを分離して、Metallic/Smoothness値を取得します。

  <nodegraph name="NG_separate">
    <separate4 name="separate" type="multioutput">
      <input name="in" type="color4" nodegraph="NG_textures" output="out_metallicSmoothness" />
    </separate4>
    <output name="out_metallic" type="float" nodename="separate" output="outr" />
    <output name="out_smoothness" type="float" nodename="separate" output="outa" />
    <output name="out_occlusion" type="float" nodename="separate" output="outg" />
  </nodegraph>

前述の"separate4"を使って
Rは"out_metallic"、Aは"out_smoothness"、Gは"out_occlusion"のfloat値を取得しました。

Smoothness値を使って"1.0 – Smoothness"を計算します。
これがRoughness値になります。

  <nodegraph name="NG_roughness">
    <subtract name="subtract" type="float">
      <input name="in1" type="float" value="1.0" />
      <input name="in2" type="float" nodegraph="NG_separate" output="out_smoothness" />
    </subtract>
    <output name="out" type="float" nodename="subtract" />
  </nodegraph>

"subtract"タグを使用し、"in1"から"in2"を引いた値を計算します。
この場合、"NG_roughness"のNodeGraphの"out"がfloat型のRoughness値になります。

最後に、BaseColor/Metallic/Roughness/Normalをstandard_surfaceタグに渡します。

  <standard_surface name="SR_unity" type="surfaceshader">
    <input name="base" type="float" value="1" />
    <input name="base_color" type="color3" nodegraph="NG_textures" output="out_baseColor" />
    <input name="metalness" type="float" nodegraph="NG_separate" output="out_metallic" />
    <input name="specular_roughness" type="float" nodegraph="NG_roughness" output="out" />
    <input name="normal" type="vector3" nodegraph="NG_textures" output="out_normal" />
  </standard_surface>

  <surfacematerial name="UnityMaterial" type="material">
    <input name="surfaceshader" type="surfaceshader" nodename="SR_unity" />
  </surfacematerial>

Omniverse Createにmtlxファイルと形状のfbxを読み込み

このmtlxファイルをOmniverse Createに読み込みました。
Omniverse Createでのmtlxファイルの認識は「[Omniverse] MaterialXを使う」もご参照くださいませ。

fbxファイルでMeshをインポートします。
このfbxファイルは、Unityで使っていたものと同じものを指定しました。
Omniverseにfbxを読み込む際は"Convert to USD"でUSDファイルに変換し、
そのUSDファイルをStageに読み込みました。

※ USD以外の形状ファイルを使うと、(特にテクスチャ周りが)キャッシュとずれることがあります。
そのため、OmniverseではfbxやglTFファイルをそのまま読み込めはしますが、
一度USDに変換し、ネイティブな形(=USD)で扱うことをお勧めします。

読み込んだmtlxファイルから取得したMaterialを、各Meshに割り当てていきます。

これで、Unityの時と同じマテリアル表現になりました。
テクスチャは加工せずに読み込めるのを確認できました。

今回は、separateでテクスチャのRGBA成分を分解し、Unityのマテリアル構造をMaterialXで表してみる、という内容でした。
次回はNoiseについて説明予定です。