[Omniverse] [Unity] Omniverse Unity Connectorでのマテリアルの表現 (UsdPreviewSurface)

  • by

Omniverse Unity Connector(201.0.0-beta)を使用します。
Omniverse Unity ConnectorからエクスポートされるUSDファイルでのマテリアル表現について書いていきます。

UnityのRender Pipelineにより指定できるパラメータが変わる箇所があります。
今回はURPで確認していきます。
また、複数のマテリアルを説明するとややこしいため、USDエクスポート時のUsdPreviewSurfaceを対象とすることにします。
UsdPreviewSurfaceでいくつかのパラメータの仕様を把握していくようにしましょう。

UsdPreviewSurfaceの仕様はPixarのUSDのサイトで説明されています。
https://openusd.org/release/spec_usdpreviewsurface.html

以下の環境で確認しました。

Unity : 2022.3.6f1
Omniverse Unity Connector : 201.0.0 beta
Omniverse USD Composer : 2023.2.0 beta

USDのエクスポート時にサポートしているUnity上のShader

USDのエクスポート時に、Unityの以下のShaderのパラメータを渡されます。

参考 : https://docs.omniverse.nvidia.com/connect/latest/unity/manual/materials.html

Render Pipeline Shader名
Built-in Standard
URP Universal Render Pipeline/Lit
Universal Render Pipeline/Complex Lit
HDRP HDRP/Lit

USDエクスポート時のマテリアルの種類

Omniverse Unity ConnectorのSettingsで「MDL」か「UsdPreviewSurface」を選択できます。

Unity EditorからUSDのエクスポート時に、
MDLを選択している場合は、OmniPBRまたはOmniGlassとして出力します。
OmniGlassはHDRPで特定の条件下( https://docs.omniverse.nvidia.com/connect/latest/unity/manual/materials.html#hdrp-exporting-omniglass )でエクスポートされます。
UsdPreviewSurfaceを選択している場合は、UsdPreviewSurfaceとして出力します。

Unity : URPで使用できるマテリアル

Unityで次のような形状を配置しました。
アセットとしてfbxをインポートし、マテリアルをUnity上で再割り当てしました。

1つの形状に対して4つのマテリアルを割り当てています。
なお、これらのテクスチャはSubstance 3D Painter ( https://www.adobe.com/jp/products/substance3d-painter.html ) で作成/出力しました。
Substance 3D PainterはサブスクリプションではないバージョンがSteamで購入できます。
https://store.steampowered.com/app/2199970/Substance_3D_Painter_2023/

"Universal Render Pipeline/Lit"を使う場合、以下のようなパラメータあります。
PBRマテリアルの標準的なテクスチャを与えました。
以下は木の部分のマテリアルです。

パラメータ名 説明
Base Map RGB : Base Color Map
A : Opacity Map
Metallic Map R : Metallic
G : Occlusion (Built-in,URPの場合は未使用)
B : None
A : Smoothness
Normal Map Normal Map
Occlusion Map Occlusion Map

Base Mapには、ベースカラー(Diffuse/Albedoとも呼ばれる)のテクスチャを指定し、
アルファには不透明度を指定します。
この木のマテリアルの場合はアルファは未使用です。

Unityの場合はMetallic Mapに複数のテクスチャ要素が1枚のテクスチャにパックされています。

Metallic(R)は真っ黒の指定ですべて0.0。
Smoothness(A)にSmoothnessの指定を行っています。
なお、PBRマテリアルで使われるRoughnessは、Roughness = 1.0 – Smoothnessとして計算できます。

Normal Mapは以下のようなテクスチャを指定。

Occlusion Mapは以下のようなテクスチャを指定。
ここではMetallic Mapのパックされたテクスチャとは別に指定しています。
URP/HDRPでは、もしMetallic MapにOcclusion要素も含ませている場合は
同一テクスチャの[G]チャンネルから参照して共有できます。

これが基本的なPBRマテリアルのパラメータになりますが、それ以外にEmissive Map(発光)、テクスチャのタイリングやオフセットの指定もUSDに渡すことができます。

USD : エクスポートしてマテリアルを確認

マテリアルをUsdPreviewSurfaceとしてUSDファイルのエクスポートを行います。
Unity Editor上でメインメニューの[Omniverse]-[Export]を選択してエクスポートします。
usdファイルの中身を確認するため、usdaとして出力してみました。

なお、Hierarchyウィンドウで対象GameObjectを選択して右クリック。
"Export TO USD"で選択したGameObjectだけをエクスポートすることができます。

USD Composer : USDの確認

確認はOmniverse USD Composerを使って行います。
読み込んだ後、分かりやすいようにEnvironmentsで空の背景を指定しました。

出力されたファイルは以下の通りです。

IceBox.usda
[Materials]
  [body]
    reizouko_body_AlbedoTransparency.png
    reizouko_body_MetallicSmoothness_metallic.png
    reizouko_body_MetallicSmoothness_roughness.png
    reizouko_body_Normal.png
    reizouko_body_Occlusion_occlusion.png
  [cyoutugai]
    reizouko_cyoutugai_AlbedoTransparency.png
    reizouko_cyoutugai_MetallicSmoothness_metallic.png
    reizouko_cyoutugai_MetallicSmoothness_roughness.png
    reizouko_cyoutugai_Normal.png
    reizouko_cyoutugai_Occlusion_occlusion.png
  [door]
    reizouko_door_AlbedoTransparency.png
    reizouko_door_MetallicSmoothness_metallic.png
    reizouko_door_MetallicSmoothness_roughness.png
    reizouko_door_Normal.png
    reizouko_door_Occlusion_occlusion.png
  [left_tomegu]
    reizouko_left_tomegu_AlbedoTransparency.png
    reizouko_left_tomegu_MetallicSmoothness_metallic.png
    reizouko_left_tomegu_MetallicSmoothness_roughness.png
    reizouko_left_tomegu_Normal.png
    reizouko_left_tomegu_Occlusion_occlusion.png
  [Wood]
    wood1_BaseColor.png
    wood1_Normal.png

usdaファイルを中心として、「Materials」フォルダにマテリアル名ごとのテクスチャが出力されています。
「body」を確認します。
これは形状の木の部分のマテリアルです。

  • reizouko_body_AlbedoTransparency.png
    BaseColor Map(RGB)とOpacity(A)のテクスチャ。

    OpacityはUnityと同じで、アルファチャンネルが使用されます。
    テクスチャはそのまま出力されます。
  • reizouko_body_MetallicSmoothness_metallic.png

    Metallicは存在しないので黒色。
  • reizouko_body_MetallicSmoothness_roughness.png

    Roughnessは、UnityのSmoothnessを(1.0 – Smoothness)にしたテクスチャが出力されます。
  • reizouko_body_Normal.png
  • reizouko_body_Occlusion_occlusion.png

MetallicSmoothnessマップがそれぞれグレイスケールごとに分解されているのを確認できます。

法線マップのNormal Mapの値が1.0ではない場合は、テクスチャにベイクされてから出力されます。
OcclusionマップOcclusin Mapの値が1.0ではない場合は、テクスチャにベイクされてから出力されます。

USDファイル内のUsdPreviewSurfaceを確認

UsdPreviewSurfaceについては「USDのUsdPreviewSurfaceとOmniverseのMDL(OmniPBR/OmniGlass)」もご参照くださいませ。
USDComposer 2023.2.0 betaではUsdPreviewSurfaceについては強化されており、transform2dにも対応しています。

さて、ここでは「UsdPreviewSurface」としてマテリアルをエクスポートしました。
USD Composerでは、このマテリアルは次のように構成されています。

UsdPreviewSurfaceの中身については、usdaファイルを出力してそれをテキストエディタで確認するのが分かりやすいです。

Shaderのid="UsdPreviewSurface"がマテリアルのベースノードになります。
テクスチャではない値は、ここに記載されます。
テクスチャは".connect"で別ノードに接続されます。

def Shader "PBRShader"
{
    uniform token info:id = "UsdPreviewSurface"
    float inputs:clearcoat = 0
    float inputs:clearcoatRoughness = 0.01
    color3f inputs:diffuseColor.connect = </World/MaterialTest/Looks/body/diffuseTexture.outputs:rgb>
    color3f inputs:emissiveColor = (0, 0, 0)
    float inputs:ior = 1.5
    float inputs:metallic.connect = </World/MaterialTest/Looks/body/metallicTexture.outputs:r>
    normal3f inputs:normal.connect = </World/MaterialTest/Looks/body/normalTexture.outputs:rgb>
    float inputs:occlusion.connect = </World/MaterialTest/Looks/body/occlusionTexture.outputs:r>
    float inputs:opacity = 1
    float inputs:roughness.connect = </World/MaterialTest/Looks/body/roughnessTexture.outputs:r>
    int inputs:useSpecularWorkflow = 0
    token outputs:surface
}

color3fの色については、usdファイル内ではリニアに変換された色が入ります。
その他、いくつか注目する点を列挙します。

UsdPreviewSurface : Diffuse Color Mapの色の乗算について

UnityでBase Color MapとBase Colorの色の両方を指定した場合、結果は乗算合成されることになります。

RGB(1, 1, 1)の白色の場合はテクスチャがそのまま使用されます。

このテクスチャと色の乗算合成が発生する場合、usdaファイルでは以下のように「inputs:scale」に色が入ります。
これは、リニアに変換した色が入っています。

def Shader "diffuseTexture"
{
    uniform token info:id = "UsdUVTexture"
    asset inputs:file = @./Materials/body/reizouko_body_AlbedoTransparency.png@
    float4 inputs:scale = (0.5754441, 0.4013995, 0.2873762, 1)
    token inputs:sourceColorSpace = "sRGB"
    float2 inputs:st.connect = </World/MaterialTest/Looks/body/stReader.outputs:result>
    token inputs:wrapS = "repeat"
    token inputs:wrapT = "repeat"
    float3 outputs:rgb
}

「inputs:scale」を使ったテクスチャに対する色の乗算はマニアックな使い方と言えます。
MDLを使っているOmniPBRでは、もっとスマートなパラメータ指定になっています。

UsdPreviewSurface : テクスチャの繰り返し(tiling)

UnityでTilingを使用します。

エクスポートで出力されたusdaファイルを見ると、以下のようになりました。

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

    def Shader "PBRShader"
    {
        uniform token info:id = "UsdPreviewSurface"
        float inputs:clearcoat = 0
        float inputs:clearcoatRoughness = 0.01
        color3f inputs:diffuseColor.connect = </World/MaterialTest/Looks/tile/diffuseTexture.outputs:rgb>
        color3f inputs:emissiveColor = (0, 0, 0)
        float inputs:ior = 1.5
        float inputs:metallic = 0
        float inputs:opacity = 1
        float inputs:roughness = 0.5
        int inputs:useSpecularWorkflow = 0
        token outputs:surface
    }

    def Shader "stReader"
    {
        uniform token info:id = "UsdPrimvarReader_float2"
        token inputs:varname.connect = </World/MaterialTest/Looks/tile.inputs:frame:stPrimvarName>
        float2 outputs:result
    }

    def Shader "diffuseTexture"
    {
        uniform token info:id = "UsdUVTexture"
        asset inputs:file = @./Materials/tile/tile_image.png@
        token inputs:sourceColorSpace = "sRGB"
        float2 inputs:st.connect = </World/MaterialTest/Looks/tile/transform2d.outputs:result>
        token inputs:wrapS = "repeat"
        token inputs:wrapT = "repeat"
        float3 outputs:rgb
    }

    def Shader "transform2d"
    {
        uniform token info:id = "UsdTransform2d"
        token inputs:in.connect = </World/MaterialTest/Looks/tile/stReader.outputs:result>
        float inputs:rotation = 0
        float2 inputs:scale = (4, 4)
        float2 inputs:translation = (0, 0)
        float2 outputs:result
    }
}

Diffuse Colorを見ると、
"PBRShader" → "diffuseTexture" → "transform2d" → "stReader"の流れを確認できます。
この中の"transform2d"に"inputs:scale = (4, 4)"を確認できます。

これをUSD ComposerのMaterial Graphで見ると、以下のようになりました。

USD Composer 2023.2.0 betaでは、UsdPreviewSurface使用時のMaterial Graphは不安定な部分があります。
ノードを移動させるとテクスチャが消えることがあります。
そのため、現状はマテリアルの構造を知りたい場合はusdaファイルをテキストエディタで確認するのが確実です。
Material Graphを使わなければ、USD ComposerのUsdPreviewSurfaceは問題なく検証に使えました。

UsdPreviewSurface : 半透明の使用 (Opacity)

USD(UsdPreviewSurface)の半透明の表現は以下の2つがあります。

  • 完全な不透明(OPAQUE)にThresholdを指定するパターン
  • Transparentを指定するパターン

共にBase Colorのアルファ値を不透明度として使用します。

不透明(OPAQUE)にThresholdを指定するパターン

これはURPの指定です。
Built-in、HDRPの場合は少しUIが変わります。

"Surface Type"に"Opaque"を指定しています(デフォルト)。
Alpha ClippingチェックボックスをOnにし、Threshold(Cutoutする際のしきい値)を0.0以上の値にします。
USD(UsdPreviewSurface)では"opacityThreshold"が使われ、
この値が0.0の場合はTransparent相当、0.0より大きい場合はCutout表現になります。

なお、UnityでもUSD(UsdPreviewSurface)でも、テクスチャのBase Color Map(Diffuse Color Map)を使う場合は、アルファ値が不透明度(Opacity)として使用されます。

usdaでは以下のように出力されました。

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

    def Shader "PBRShader"
    {
        uniform token info:id = "UsdPreviewSurface"
        float inputs:clearcoat = 0
        float inputs:clearcoatRoughness = 0.01
        color3f inputs:diffuseColor.connect = </World/MaterialTest/Looks/leaf/diffuseTexture.outputs:rgb>
        color3f inputs:emissiveColor = (0, 0, 0)
        float inputs:ior = 1.5
        float inputs:metallic = 0
        normal3f inputs:normal.connect = </World/MaterialTest/Looks/leaf/normalTexture.outputs:rgb>
        float inputs:opacity.connect = </World/MaterialTest/Looks/leaf/opacityTexture.outputs:a>
        float inputs:opacityThreshold = 0.5
        float inputs:roughness = 0.5
        int inputs:useSpecularWorkflow = 0
        token outputs:surface
    }

    def Shader "stReader"
    {
        uniform token info:id = "UsdPrimvarReader_float2"
        token inputs:varname.connect = </World/MaterialTest/Looks/leaf.inputs:frame:stPrimvarName>
        float2 outputs:result
    }

    def Shader "diffuseTexture"
    {
        uniform token info:id = "UsdUVTexture"
        asset inputs:file = @./Materials/leaf/leaf_03_20141206_diffuse.png@
        token inputs:sourceColorSpace = "sRGB"
        float2 inputs:st.connect = </World/MaterialTest/Looks/leaf/stReader.outputs:result>
        token inputs:wrapS = "repeat"
        token inputs:wrapT = "repeat"
        float3 outputs:rgb
    }

    def Shader "normalTexture"
    {
        uniform token info:id = "UsdUVTexture"
        float4 inputs:bias = (-1, -1, -1, 0)
        asset inputs:file = @./Materials/leaf/leaf_03_20141206_normal.png@
        float4 inputs:scale = (2, 2, 2, 1)
        token inputs:sourceColorSpace = "raw"
        float2 inputs:st.connect = </World/MaterialTest/Looks/leaf/stReader.outputs:result>
        token inputs:wrapS = "repeat"
        token inputs:wrapT = "repeat"
        float3 outputs:rgb
    }

    def Shader "opacityTexture"
    {
        uniform token info:id = "UsdUVTexture"
        asset inputs:file = @./Materials/leaf/leaf_03_20141206_diffuse.png@
        token inputs:sourceColorSpace = "raw"
        float2 inputs:st.connect = </World/MaterialTest/Looks/leaf/stReader.outputs:result>
        token inputs:wrapS = "repeat"
        token inputs:wrapT = "repeat"
        float outputs:a
    }
}

"PBRShader"ノード内の"inputs:opacity"が不透明度のマッピングです。
"opacityTexture"ノードでDiffuse Color Mapと同じテクスチャが指定されて、"outputs:a"でアルファ値を参照しているのを確認できます。 
"PBRShader"ノード内の"inputs:opacityThreshold"でThresholdを確認できます。

UnityとUSDエクスポートを行ったUSD Composerの比較は以下のようになりました。

Transparentを指定するパターン

以下のような形状を配置しました。
カップと水のマテリアルとしてTransparentを割り当てています。

これはURPの指定です。
Built-in、HDRPの場合は少しUIが変わります。
"Surface Type"に"Transparent"を指定しています。
また、Base Mapのアルファ値を0.0に近づけて半透明にしています。

なお、Built-in,URPではIOR(屈折率)の指定がありません。
そのため、Omniverse Unity ConnectorはUSdPreviewSurfaceのiorをデフォルトの1.5として出力します。

USD Composerでは以下のようになりました。

ior=1.5は意図しない結果のため、それぞれのマテリアルパラメータのiorを1.0に変更すると以下のようになりました。

少しわかりにくいため、Environmentsを使って背景を変更。
コップと水のMeshの"Cast Shadows"をOffにしました。

※ Omniverse Unity Connector 201.0.0 betaでは"Cast Shadows"はUSDエクスポートで渡されません。

以下のようになりました。

UsdPreviewSurfaceの半透明については、USD Composer 2023.2.0 betaでは表現が気になります。
OmniPBRを使うほうがよりUnityに近い結果が得られます。
以下はOmniverse Unity ConnectorからMDL(OmniPBR)でエクスポートし、USD Composerで表示しました。

ClearCoat

UnityのURPでは"Universal Render Pipeline/Complex Lit"を使用することで、ClearCoatを扱うことができます。

おそらくURPのClearCoatはHDRPのClearCoatよりも後に追加されたと思われ、パラメータはHDRP時よりも整合性が取れていると感じました。
テクスチャは[R]にClearCoatのマスク値、[G]にClearCoatのSmoothnessを与えます。

以下のようなテクスチャを与えた場合を考えます。

マテリアルパラメータは以下のように指定しました。

ベースのSmoothnessを0.5と指定しています。
ClearCoatテクスチャの値をそのまま使用するため、ClearCoat – Maskを1.0、ClearCoat – Smoothnessを1.0にしました。
これらの値はテクスチャのピクセルごとの値に乗算されて表現されます。

テクスチャ上の赤色(RGB(1, 0, 0))は、マスク値 1.0、Smoothness値0.0となります。
テクスチャ上の黄色(RGB(1, 1, 0))は、マスク値 1.0、Smoothness値1.0となります。
マテリアルの持つSmoothnessに対して、このClearCoatのパラメータが加わることになります。

Unity上の形状にマテリアルを割り当てると以下のようになりました。

これをUSDエクスポートしました。
なお、エクスポート時のマテリアルはMaterial Typeで「UsdPreviewSurface」を選択するようにしてくださいませ。

「MDL(OmniPBR, OmniGlass)」ではClearCoatパラメータが存在しないため反映されません。

USD Composerで読み込むと以下のようになりました。
Roughnessが分かりやすいように背景を変更しました。

ClearCoatのために出力されるテクスチャは以下の2つです。

  • ClearCoatMat_clearcoatMask.png

    ClearCoatのマスク値がグレイスケールで出力されます。
    0.0がClearCoatなし、1.0でClearCoat最大です。
  • ClearCoatMat_clearcoatRoughness.png

    ClearCoatのRoughness値がグレイスケールで出力されます。これはUnityのSmoothnessより「Roughness = 1.0 – Smoothness」が与えられます。
    0.0がRoughness 0.0、1.0がRoughness 1.0です。

USD Composer上で、ClearCoatを割り当てたUsdPreviewSurfaceは以下のようになります。

このClearCoatをUSD Viewで表示してみました。

同様に反映されているのを確認できました。
ClearCoatはグリグリ動かすと分かりやすいかと思います。

USD Composerで色合いを確認する場合

USD Composerでレンダリングの色合いを確認する場合、
Render Settings – Post Processing – Tone Mapping Operatorを"Linear (Off)"を指定することで、Post Processingがかからない状態に近づけることができます。

デフォルトでAcesですので、Unityとレンダリング結果を比較する場合はTone Mappingも確認するようにしてくださいませ。

今回は、Unity ConnectorからのUSDエクスポートでUsdPreviewSurfaceについて確認しました。
次回はMDL(OmniPBR/OmniGlass)を確認予定です。