Unreal Engine のユニバーサル シーン デスクリプション (USD)

ユニバーサル シーン デスクリプション (USD) の概要と、これを Unreal Engine 4 で使用する方法について説明します。

Choose your operating system:

Windows

macOS

Linux

映画やゲームなどの 3D グラフィック プロダクションでは、大量のデータを生成して保存し、送信することがよくあります。これらのデータはアート パイプラインにあるさまざまなソフトウェア (Unreal Engine、Maya、Houdini、Blender など) から送られるもので、シーン デスクリプションはそれぞれ独自の形式で提供されます。

ユニバーサル シーン デスクリプション (USD) 交換形式は、そうした背景から Pixar によって開発されたオープンソースの形式で、多くの要素アセットで構成される可能性のある任意の 3D シーンを、堅牢かつスケーラブルに交換および拡張できるようにするためのものです。USD は、3D ジオメトリとシェーディングの読み取り、書き込み、編集、および迅速なプレビューを行える豊富なツールセットを提供するだけでなく、要素アセット (モデルなど) またはアニメーションの交換も可能です。

また他の交換パッケージとは異なり、USD では、任意の数のアセットを仮想セット、シーン、ショットに組み立てて編成することができます。その後、単一のシーン グラフ内で、単一の一貫した API を使ってアプリケーション間でそれらを送信し、(オーバーライドとして) 非破壊的に編集できます。

USD を使用する理由

USD では、大量のデータを複数の 3D アプリケーション間で移動するための共通言語が提供されます。これにより、アート パイプラインに関してより柔軟に決断を下せるようになり、反復的および非破壊的な方法を使って、複数の 3D アーティスト (アニメーター、ライティング/シェーディング アーティスト、モデラー、FX アーティストなど) によるコラボレーションを促進できます。

Unreal Engine における USD サポート

現時点で、Unreal Engine では USD Stage と双方向の Python ワークフローを通じて USD をサポートしています。

[USD Stage] ウィンドウ。

スタティック メッシュやマテリアルのようなネイティブの Unreal Engine アセットに USD データを変換するのではなく、USD Stage アクタ と USD Stage エディタ ウィンドウを使用して、USD Stage ワークフローで USD データをネイティブに操作できます。これにより、USD データをよりすばやく Unreal Engine に取り込んだり、USD コンテンツの元の構造をより明確に表示したり、ディスク上にあるソース USD ファイルに変更を加えた際のライブ アップデート処理を行ったりできるようになります。

USD Stage では次の機能が提供されます。

  • 3D データの「プリミティブ」表現 - スタティック メッシュ、スケルタル メッシュ、HISM、マテリアル、ライト、カメラ、バリアント、アニメーション、ブレンド シェイプ。

  • 属性の非破壊的な編集。

  • USD シーン グラフと階層のビジュアライゼーション。

  • ペイロードを使用したコンテンツのロード/アンロード。

  • USD Preview Surface を使ったテクスチャのサポート。

  • PreviewSurface と DisplayColor によるマテリアルのサポート。

  • Alembic や Custom Path Resolver などの USD プラグインのサポート。

  • ランタイム時における USD 機能のサポート。詳細については、「 USD ランタイム サポート 」を参照してください。詳細については、「 USD ランタイム サポート 」を参照してください。

  • .usd .usda .usdc .usdz 形式のロード。

ユニバーサル シーン デスクリプションの詳細については、Pixar の「 Introduction to USD 」を参照してください。

Python ワークフローでの作業の詳細については、「 Python スクリプティング 」を参照してください。

サポートされるアクション

USD Stage アクタ を使用することで、USD データで次のアクションを実行できます。

Unreal Engine へのインポート

USD Stage で表示されるコンテンツ ( スタティック メッシュ スケルタル メッシュ ライト フォリッジ ランドスケープ ) は、次の方法で Unreal Engine にインポートできます。

  • [File] > [Import Into Level (レベルにインポート)] を使用する。このプロセスでは、アセット (スタティック メッシュ、スケルタル メッシュ、マテリアル、テクスチャなど) とアクタの両方がインポートされます。

  • コンテンツ ブラウザ [Add/Import (追加/インポート)] ボタンを使用する。このプロセスでは、アセットのみがインポートされます。

  • コンテンツ ブラウザ にファイルをドロップ アンド ドロップする。このプロセスでは、アセットのみがインポートされます。

  • USD Stage エディタ [Action] > [Import] オプションを使用する。このプロセスでは、アセットとアクタの両方がインポートされます。インポート プロセスが完了すると、USD Stage のアセットは コンテンツ ブラウザ からの新しいアクタに置換されます。

アニメーションの作成と編集

USD ファイルに格納されているアニメーションには、 USDStageActor プロパティ パネルにある、関連するレベル シーケンスを使ってアクセスできます。

USD Level Stage を選択し、プロパティ パネルのシーケンスをダブルクリックしてレベル シーケンスを開く。

レベル シーケンス内の [Transform] トラックに USD xform のアニメーションが表示されます。その他の形式のアニメーション (浮動小数値、ブール値、スケルタル ボーンなど) は Time トラックに表示されます。上の画像にある USD アニメーション データは、アニメーションの継続期間の各タイムコードで、キー/値のペアで表現されています。

USD Stage で生成されたレベル シーケンス全体を通じて、USD Stage でスポーンされたアクタにバインドし、Transform トラックでアニメーションを追加することができます。

USD ランタイム サポート

Unreal Engine では、レベル内の USD Stage アクタ にある Set Root Layer ブループリント ノードの呼び出しを通じて、ランタイム時における USD ファイルのロードをサポートしています。

Set Root Layer ノード。

このノードは、エディタでの処理と同じように、必要なアセットを作成して、アクタとコンポーネントをレベル内にスポーンします。USD Stage アクタ のさまざまなプロパティを制御するための追加のブループリント関数には次のようなものがあります。

USD でよく使用されるブループリント関数。

ブループリント関数

説明

Get Generated Assets

特定のプリミティブ パス内のプリミティブ用に生成されたアセットを取得し、それを配列に配置します。USD Stage アクタ とプリミティブ パスを入力として使用します。

Get Generated Components

特定のプリミティブ パス内のプリミティブ用に生成されたコンポーネントを取得します。USD Stage アクタ とプリミティブ パスを入力として使用します。

Get Source Prim Path

USD Stage にある特定のオブジェクト用のプリミティブ パスを取得します。USD Stage アクタ とオブジェクト参照を入力として使用します。

Get Time

ターゲット USD Stage アクタ 内の現在のタイムスタンプを取得します。USD Stage アクタ をターゲットとして受け取ります。

Set Initial Load Set

ロードする初期のペイロードを設定します。USD Stage アクタ を入力として使用します。次のオプションがあります。

  • Load All :初期にすべてのペイロードをロードします。

  • Load None :初期にペイロードをロードしません。

Set Purpose to Load

ロードする初期の Purpose を指定します。USD Stage アクタ と整数値を入力として使用します。

  • 0 = Default (デフォルト)

  • 1 = Proxy (プロキシ)

  • 2 = Render (レンダリング)

  • 3 = Guide (ガイド)

Set Render Context

USD Stage のレンダリング コンテキストを設定します。USD Stage アクタ をターゲットとして受け取り、レンダリング コンテキストへの参照を入力として受け取ります。

Set Time

USD Stage の現在のタイムスタンプを設定します。USD Stage アクタ をターゲットとして受け取り、浮動小数値を入力として受け取ります。

Purpose 属性やその他の USD 用語の詳細については、Pixar の「 USD Glossary 」を参照してください。

このプロセスを使用することで、USD ファイルのコンテンツをランタイム時にロードして表示できるアプリケーションを作成できます。

ランタイム時に USD インポータを有効にするには、「 UE_(バージョン)\Engine\Source 」フォルダにある「 Project.Target.cs 」ファイルに次の行を追加します (「Project」は対象のプロジェクトの名前です)。

GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");

例:

public class YourProjectTarget :TargetRules
{
public YourProjectTarget( TargetInfo Target) : base(Target)
{
 Type = TargetType.Game;
 DefaultBuildSettings = BuildSettingsVersion.V2;
 ExtraModuleNames.AddRange( new string[] { "YourProject" } );

 GlobalDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");
}
}

Nvidia MDL のサポート

Unreal Engine では、Nvidia MDL USD スキーマを使って MDL サーフェス マテリアルをサポートしています。Nvidia MDL の詳細については、Nvidia の「 USD Shader Attributes 」を参照してください。

マルチ ユーザー編集のサポート

マルチ ユーザー編集は、以下を含む多くの USD Stage の操作でサポートされています。

  • プリミティブの追加と削除。

  • プリミティブの名前変更。

  • プリミティブの属性の編集。

  • 可視性の切り替え。

  • 現在のステージを開く、閉じる、または変更する操作。

USD プロジェクトでマルチ ユーザー編集を有効にするには、 USD Multi-User synchronization プラグインを有効にします。

USD のマルチ ユーザー編集では、各クライアントの USD Stage の [Root Layer (ルート レイヤー)] プロパティが同期されて、すべてのユーザーが同じ USD ファイルを使用するようになります。具体的には、各クライアントで同じ USD Stage をローカルで開いて、それぞれ独自のシステムでアセットとコンポーネントをスポーンし、最後にこれらのアセットに対して行われた操作のみを同期します。

マルチ ユーザー編集セッション中は、すべてのユーザーが同じファイル パスを使って USD ファイルにアクセスすることが重要です。それぞれのクライアントで同じファイルにアクセスできることを確実にするために、ターゲット USD ファイルをプロジェクト フォルダ内に格納し、ソース コントロールを使って管理することをお勧めします。

Unreal Engine でのマルチ ユーザー編集の詳細については、「 マルチ ユーザー編集 」を参照してください。

現時点では、マルチ ユーザー セッション中に削除したプリミティブを元に戻すことはできません。

USD インポート プラグインを有効化する

Unreal Editor で USD ファイルを使用するには、 [Plugins (プラグイン)] メニューから USD インポータ プラグインを有効にする必要があります。

  1. [Edit (編集)] メニューから [Plugins] を選択します。

    メニューを使って [Plugins] メニューを開く。

  2. [Plugins] ウィンドウにある検索バーを使って「 USD 」を検索します。 USD Importer プラグイン を有効にしてエディタを再起動します。

    [Plugins] ウィンドウの検索バーを使って USD プラグインを検索する

エディタを再起動すると、 [Window (ウィンドウ)] メニューに新しく [USD Stage エディタ] オプションが表示されます。

USD Stage エディタ を有効にする。

[Place Actors (アクタを配置)] パネルには、レベルに追加可能な新しい USD アクタがいくつか表示されます。 Level:

[Place Actors] パネルに追加された新しい USD アクタ。

USD を Unreal Engine 4 で使用する

Unreal Engine で USD コンテンツの作業を行う際は、USD Stage エディタと USD Stage アクタを使用します。

USD Stage エディタのウィンドウ

番号

説明

1

階層

2

プロパティ

3

レイヤー

USD Stage のワークフロー

USD Stage アクタは、ロードされた USD ファイルのコンテンツを格納するコンテナとして機能し、レベル内におけるそのデータのアンカーを提供します。USD ファイルからロードされ、ビューポートに表示される 3D シーン オブジェクトは、Unreal Engine のほとんどの機能と完全な互換性があり、他のアクタと同じように扱うことができます。他の USD ファイルに存在するコンテンツ (アニメートされたスケルタル メッシュを含む) を参照するプリミティブをさらに追加することもできます。

USD Stage に加えた変更を USD Stage エディタ の [File] > [Save] メニューを使って保存すると、これらの変更が USD ファイルに書き戻されます。

USD Stage での作業の詳細については、「 USD Stage エディタr クイック スタート 」を参照してください。

Unreal Engine では、USD ファイルを開いた際に USD Stage にロードされたアセットのライトマップを自動的に作成しません。このため、静的ライトをビルドした際にシーンが真っ暗になることがあります。

Python スクリプティング

USD で Python スクリプトを活用すると、ユーザー インターフェースを使うと困難で時間のかかるシーンの編集やバッチ処理など、さまざまな操作を柔軟に実行できるようになります。柔軟性の高い Python スクリプトを [Output Log (アウトプット ログ)] パネルから実行することで、多数のプリミティブの属性を非表示にしたり、編集したりといった操作をすばやく自動化できます。

[Output Log] は [Window] > [Developer Tools (デベロッパー ツール)] メニューにあります。

Python スクリプトを Unreal Engine で使用するには、まず Python Editor Script Plugin を有効にする必要があります。

Python スクリプトを Unreal Engine で使用するには、Python Editor Script Plugin を有効にします。

  1. [Edit (編集)] メニューを開いて [Plugins (プラグイン)] オプションを選択し、 [Plugins] パネルを開きます。

  2. [Plugins] ウィンドウの [Scripting (スクリプティング)] セクションに移動します。 [Python Editor Script Plugin] を見つけて、その [Enabled] ボックスをオンにします。

  3. エディタを再起動します。

Unreal Engine で Python スクリプトを使用する詳細については、「 Python を使用したエディタのスクリプティング 」を参照してください。

ユースケース

Unreal Engine とともにシッピングされる USD SDK バージョンが「21.05」にアップグレードされた際に、USDLux Light スキーマ内のいくつかの属性の名前が変更されました。問題を最小限に抑えるために、Unreal Engine は、USDLux プリミティブ属性の名前を 21.05 バージョンの命名規則に従って変更する Python スクリプトとともにシッピングされます。

`

from pxr import Usd, Sdf, UsdLux
import argparse

def rename_spec(layer, edit, prim_path, attribute, prefix_before, prefix_after):
    path_before = prim_path.AppendProperty(prefix_before + attribute)
    path_after = prim_path.AppendProperty(prefix_after + attribute)

    # We must check every time, because adding a namespace edit that can't be applied will just cancel the whole batch
    if layer.GetAttributeAtPath(path_before):
        print(f"Trying to rename '{path_before}' to '{path_after}'")
        edit.Add(path_before, path_after)

def rename_specs(layer, edit, prim_path, reverse=False):
    prefix_before = 'inputs:' if reverse else ''
    prefix_after = '' if reverse else 'inputs:'

    # Light
    rename_spec(layer, edit, prim_path, 'intensity', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'exposure', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'diffuse', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'specular', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'normalize', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'color', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'enableColorTemperature', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'colorTemperature', prefix_before, prefix_after)

    # ShapingAPI
    rename_spec(layer, edit, prim_path, 'shaping:focus', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:focusTint', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:cone:angle', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:cone:softness', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:ies:file', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:ies:angleScale', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shaping:ies:normalize', prefix_before, prefix_after)

    # ShadowAPI
    rename_spec(layer, edit, prim_path, 'shadow:enable', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shadow:color', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shadow:distance', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shadow:falloff', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'shadow:falloffGamma', prefix_before, prefix_after)

    # DistantLight
    rename_spec(layer, edit, prim_path, 'angle', prefix_before, prefix_after)

    # DiskLight, SphereLight, CylinderLight
    # Note: treatAsPoint should not have the 'inputs:' prefix so we ignore it
    rename_spec(layer, edit, prim_path, 'radius', prefix_before, prefix_after)

    # RectLight
    rename_spec(layer, edit, prim_path, 'width', prefix_before, prefix_after)
    rename_spec(layer, edit, prim_path, 'height', prefix_before, prefix_after)

    # CylinderLight
    rename_spec(layer, edit, prim_path, 'length', prefix_before, prefix_after)

    # RectLight, DomeLight
    rename_spec(layer, edit, prim_path, 'texture:file', prefix_before, prefix_after)

    # DomeLight
    rename_spec(layer, edit, prim_path, 'texture:format', prefix_before, prefix_after)

def collect_light_prims(prim_path, prim, traverse_variants, light_prim_paths, visited_paths):
    if not prim:
        return

    if prim_path in visited_paths:
        return
    visited_paths.add(prim_path)

    # Traverse manually because we may flip between variants, which would invalidate the stage.Traverse() iterator
    for child in prim.GetChildren():

        # e.g. /Root/Prim/Child
        child_path = prim_path.AppendChild(child.GetName())

        if UsdLux.Light(child):
            light_prim_paths.add(child_path)

        traversed_grandchildren = False
        if traverse_variants:
            varsets = child.GetVariantSets()
            for varset_name in varsets.GetNames():
                varset = varsets.GetVariantSet(varset_name)
                original_selection = varset.GetVariantSelection() if varset.HasAuthoredVariantSelection() else None

                # Switch selections only on the session layer
                with Usd.EditContext(prim.GetStage(), prim.GetStage().GetSessionLayer()):
                    for variant_name in varset.GetVariantNames():
                        varset.SetVariantSelection(variant_name)

                        # e.g. /Root/Prim/Child{VarName=Var}
                        varchild_path = child_path.AppendVariantSelection(varset_name, variant_name)

                        collect_light_prims(varchild_path, child, traverse_variants, light_prim_paths, visited_paths)
                        traversed_grandchildren = True

                        if original_selection:
                            varset.SetVariantSelection(original_selection)
                        else:
                            varset.ClearVariantSelection()

        if not traversed_grandchildren:
            collect_light_prims(child_path, child, traverse_variants, light_prim_paths, visited_paths)

def update_lights_on_stage(stage_root, traverse_variants=False, reverse=False):
""" Traverses the stage with root layer `stage_root`, updating attributes of light prims to/from USD 21.05.

    The approach here involves traversing the composed stage and collecting paths to prims that are UsdLux lights
    (flipping through variants or not according to the input argument), and later on traverse all the stage's 
    layers and renaming all specs of of light prim attributes to 21.05 (by adding the 'inputs:' prefix) or to
    the schema before 21.05 (by removing the 'inputs:' prefix).

    We traverse the composed stage first to make sure we're modifying exclusively UsdLux light prim attributes,
    avoiding modifications to a Sphere's "radius" attribute, for example.
    """
    stage = Usd.Stage.Open(stage_root, Usd.Stage.LoadAll)
    layers_to_traverse = stage.GetUsedLayers(True)

    # Collect UsdLux prims on the composed stage
    light_prim_paths = set()
    visited_paths = set()
    collect_light_prims(Sdf.Path("/"), stage.GetPseudoRoot(), traverse_variants, light_prim_paths, visited_paths)

    print("Collected light prims:")
    for l in light_prim_paths:
        print(f"\t{l}")

    # Traverse all layers, and rename all relevant attributes of light prims
    visited_paths = set()
    for layer in layers_to_traverse:
        # Batch all rename operations for this layer in a single namespace edit
        edit = Sdf.BatchNamespaceEdit()

        def visit_path(path):
            attr_spec = layer.GetAttributeAtPath(path)
            if attr_spec:
                prim_path = attr_spec.owner.path

                # Only visit each prim once, as we'll handle all UsdLux properties in one go
                if prim_path in visited_paths:
                    return
                visited_paths.add(prim_path)

                if prim_path in light_prim_paths:
                    rename_specs(layer, edit, prim_path, reverse)

        layer.Traverse("/", visit_path)

        if len(edit.edits) == 0:
            print(f"Nothing to rename on layer '{layer.identifier}'")
        else:
            if layer.CanApply(edit):
                layer.Apply(edit)
                print(f"Applied change to layer '{layer.identifier}'")
            else:
                print(f"Failed to apply change to layer '{layer.identifier}'")

    # Save all layers
    for layer in layers_to_traverse:
        if not layer.anonymous:
            layer.Save()

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Update light prims to USD 21.05')
    parser.add_argument('root_layer_path', type=str,
                    help='Full path to the root layer of the stage to update e.g."C:/MyFolder/MyLevel.usda"')
    parser.add_argument('--v', '--traverse_variants', dest='traverse_variants', action='store_true',
                    help='Whether to also flip through every variant in every variant set encountered when looking for light prims')
    parser.add_argument('--r', '--reverse', dest='reverse', action='store_true',
                    help='Optional argument to do the reverse change instead:Rename 21.05 UsdLux light attributes so that they follow the schema from before 21.05')
    args = parser.parse_args()

    update_lights_on_stage(args.root_layer_path, args.traverse_variants, args.reverse)

`

このスクリプトは、「 Engine/Plugins/Importers/USDImporter/Content/Python/usd_unreal/update_lights_to_21_05.py 」にある USDImporter ソース ファイルに含まれています。

このスクリプトを [Output Log] から実行するには、次の手順に従ってください。

  1. [Window] > [Developer Tools] > [Output Log] を選択して [Output Log] ウィンドウを開きます。

  2. コマンドライン フィールドの左側にある [Cmd] ドロップダウンをクリックして [Python] を選択します。

    ウィンドウからコマンドラインを使って Python スクリプトを実行する。

  3. コマンドライン フィールドに次のコマンドを入力します: "C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\Importers\USDImporter\Content\Python\usd_unreal\update_lights_to_21_05.py" "C:/path/to/root_layer.usda"

    "C:/path/to/root_layer.usda" 」の部分は USD ファイルへのパスです。

    上記のサンプルには、Unreal Engine インストール ディレクトリへのデフォルトのパスが含まれています。使用しているバージョンの Unreal Engine をデフォルトの場所にインストールしていない場合は、パスを適宜調整してください。

  4. Enter キーを押してコマンドを実行します。

Python を USD で使用する詳細については、「 Using USD in your Art Pipeline 」を参照してください。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
キャンセル