[Omniverse] OmniGraphで3Dの時計を制御

  • by

OmniGraphを使ったミニプロジェクトとして、3Dのアナログ時計とデジタル時計を制御するサンプルを作成しました。
GitHubに全プロジェクトを公開しています。

https://github.com/ft-lab/Omniverse_OmniGraph_ClockSample

Extensionとしてのカスタムノードの技術説明などはGitHub上に書いていますのでそちらをご参照くださいませ(ただし英語のみ)。
ここでは使い方と流れ、レンダリングでのTipsを説明していきます。

Omniverse Create 2022.3.3で動作確認しました。

使い方

https://github.com/ft-lab/Omniverse_OmniGraph_ClockSample
こちらをいったんローカル環境にダウンロードします。
ここには、カスタムOmniGraphノードのExtension、USDの3Dモデルファイル(アナログ時計とデジタル時計の2つ)が含まれています。
なお、学習用ですのでご自由にお使いください。

以下のようなフォルダ構成になっています。

[docs]
[extension]
  [ft_lab.OmniGraph.GetDateTime]   Extension本体
[images]
[usds]
  [Clock]
    [textures]
    clock.usd    アナログ時計の3Dモデル
  [ClockDigital]
    [textures]
    clock_digital.usd   デジタル時計の3Dモデル
  clock_stage.usd       clock.usd, clock_digital.usdを参照するステージ

[ft_lab.OmniGraph.GetDateTime]フォルダを、Omniverse Createが検索できるExtensionのパスに複製します。

Extensionの検索パス

Omniverse Create 2022.3.3でのExtensionの検索パスは以下になります。

  • [Omniverseのインストールフォルダ]/pkg/create-2022.3.3/exts

Omniverse LauncherのSettingsより、Omniverseのインストールフォルダを確認できます。

別途、指定のパスを検索Omniverse Createを起動後にメインメニューの[Window]-[Extensions]を選択し、
Extensionウィンドウを表示します。

歯車アイコンをクリックすると、右にExtension Search Pathsが表示されます。
Omniverse環境に限らず、ユーザ単位のExtensionとして使用したい場合は"c:/users/[User Name]/documents/kit/shared/exts"に格納するほうがいいかもしれません。

ft_lab.OmniGraph.GetDateTimeを有効化

Extensionsウィンドウで、"GetDateTime"で検索します。

このExtensionを有効にします。

clock_stage.usdを開く

GitHubよりダウンロードしたファイルより、"usds/clock_stage.usd"をOmniverse Createで開きます。

2つの時計に現在時刻が表示されていますでしょうか。
この時計の制御が今回のテーマになります。

グラフを確認

それでは、これがどのように制御されているかグラフを確認してみます。
StageパネルのPush Graphを右クリックし、Open Graphを選択します。

Generic Graphタブが表示され、以下のようにグラフが表示されました。

ここでの時計で使用しているカスタムのOmniGraphノードは、Examplesのカテゴリ内に格納されています。

ノード名 説明
Get DateTime 現在の日時を取得
Rotation By Time 時分秒が与えられたとき、それぞれの回転のXYZ(度数)を返す
Time Output To LCD 時分秒が与えられたとき、AM/PM、7セグメントLEDの表示を制御

Get DateTime

現在の日時を取得するノードです。
outputのみの属性を持ちます。
int型でYear/Month/Day/Hour/Minute/Secondを返します。

Rotation By Time

時分秒が与えられたとき、アナログ時計の時分秒のそれぞれの針の回転のXYZに変換します。

"Default RotateXYZ"は、針のデフォルトの回転値です。12時を指す針の回転を指定します。
"Rotation Axis"は、ローカルでの針の回転軸(0:X, 1:Y, 2:Z)です。
このとき、デフォルトの回転値XYZのうち軸になる要素は初期値0で、プラス方向の回転で時間が進むものとします。
Hour/Minute/Secondは、Get DateTimeノードで取得した時分秒をint型で渡します。

outputは、"Hour RotationXYZ"が時間の回転値、"Minute RotationXYZ"が分の回転値、"Second RotationXYZ"が秒の回転値、となります。

Write Attribute : グラフでPrimの回転に値を格納するには ?

対象の針のPrim(Meshでなくてもよいです。この場合は親のXformを使用しました)をグラフにドロップします。

Create Node for Primの選択肢が表示されます。
"Write Attribute"を選択します。
これは、指定のPrimに値を書き込むノードです。

Propertyパネルの"Attribute Name"で"xFormOp:rotateXYZ"を選択します。

これで、このノードは回転値XYZを受け取ることができるようになりました。

Rotation By Timeノードの"Hour RotationXYZ"から、先ほどのWrite Prim Attributeの"Value"につなぎます。

これを時分秒それぞれの針で接続することで、アナログ時計の回転が実装できました。

以下のようにアナログ時計が進んでいるのを確認できます。

Time Output To LCD

仮想のデジタル時計の液晶画面で、AM/PMの表示/非表示、7セグメントLEDのそれぞれの要素を表示/非表示することで時間と分の数値を表示します。

ノードは以下のようにinputのみです。

"HourNum10 Prim"は時間の10ケタ目のPrimを指定。
"HourNum1 Prim"は時間の1ケタ目のPrimを指定。
"MinuteNum10 Prim"は分の10ケタ目のPrimを指定。
"MinuteNum1 Prim"は分の1ケタ目のPrimを指定。
"AM Prim"はAMのPrimを指定。
"PM Prim"はPMのPrimを指定。
Hour/Minute/Secondは、Get DateTimeノードで取得した時分秒をint型で渡します。

液晶パネルの構成

液晶パネルから少し浮かせた位置で、AM/PM/LED1/LED2/LED3/LED4の6つのPrimと「:」のPrimを配置しています。

LEDは子のPrimとして、A/B/C/D/E/F/Gを持つ構成になっています。

7セグメントLEDは以下のような構成になります。

それぞれをOn/Offするわけですね。

Read Bundle : Primパスをグラフで渡すには ?

ここではPrimパスが欲しいですので、そのためのノードを追加します。
対象のPrimをグラフにドロップします。

7セグメントLEDは、親のXformをドロップします。

Create Node for Primの選択肢が表示されます。
"Read Bundle"を選択します。
これは、Prim自身を取得するノードです。

このノードはOutputがたくさんありますが、"Source Prim Path"が対象のPrimパスになります。

これをTime Output To LCDノードの各種Primパスを指定するinputに接続します。

これで、デジタル時計の現在の時間と分を確認できました。

ノードのTips

ここで使えるノードのTipsをいくつか紹介します。

ノードをたたむ

ノードの属性が多すぎて縦に長い場合、右上の「・・・」の部分をクリックすると折りたためます。

何回かクリックすると、折り畳みを切り替えることができます。

ノードをグループ化

ノードのメニューから「Ui(BETA)」カテゴリの「Backdrop(BETA)」をグラフにドロップします。

この上にノードを配置することでグループ化できます。
機能ごとにまとめておかないと、後々どれが何をしている組み合わせか追跡しにくくなるため、これは多用することになると思います。
バックドロップの右下の白い箇所をドラッグするとリサイズできます。

レンダリングの調整

この時計のシーンは、いくつかレンダリングを調整しています。
RTX-Real-Timeを使用しました。

Render SettingsのRay Tracingを見ていきます。

Indirect Diffuse Lighting

"Indirect Diffuse GI"をOnにします。

これにより、RTX-Real-Timeでも間接照明がそれらしくなります。

Reflections

"Roughness Cache Threshold"を1.0、
"Max Reflection Bounces"を3、
に変更しました。

"Max Reflection Bounces"はメタリックの反射の反射回数の最大です。
金属質が相互反射するシーンの場合は、デフォルト1では少ないです。

"Roughness Cache Threshold"は特に金属の表現では大事な調整になります。
ラフネスの簡易化をどの値から行うか、という敷居値の指定になります。
この値が0.0の場合は、常にジリジリのノイズが動く結果になりました。
デフォルト0.3の場合は、汚いです。
1.0でよくなりました。

書き方は雑ですが、ラフネスをリアルタイムで表現するための落としどころの調整、といった感じでしょうか。

Eco Mode

"Eco Mode"をOffにする、これがすごい大事です。

"Eco Mode"は、一定時間が経過した場合、動きがない場合にGPU計算を止める指定です。
画面自身が変化しない場合は有効なのですが、今回の時計のようにアニメーションが行われる場合は、常に動いていないと不都合が起きます。

"Eco Mode"をOnにして時計を観察してみましょうか。
動画にしてみました。


お分かりいただけただろうか、、、ドックンドックンいってますよね。
このような現象は、"Eco Mode"をOffにすることで解決できます。

ただ、"Eco Mode"Off時はGPUをぶん回すことになりますのでエコではありません。
用途に合わせて使用することになると思われます。

今回の伝えたかったこと

今回は、OmniGraphの実例として3D空間上の仮想のアナログ時計とデジタル時計をつないでみました。
あえてOmniGraphノードの開発については説明していません。
開発についてはGitHub ( https://github.com/ft-lab/Omniverse_OmniGraph_ClockSample )を参照してくださいませ。

ノードさえ用意すればプログラムが絡まないのが分かると思います。
3Dモデルとノードは同列にあり、それをつなぐ考え方が見えてくれば目的が達成されています。
もっと大きなシステムの場合、例えばハードウェアが直接ノードの先につながっていた場合はどうでしょうか。
もし、デジタルツインで工場と直結したい場合、無数のノードが接続されている感じ、エモいですよね。
繋ぎ変えるだけで制御を調整できる、、かもしれません。

Extensionでの「拡張」とは少しというか、かなり違うというのを今回のサンプルで理解できれば目標を達成したことになります。

今回はここまでです。