最適化


Java3D のパフォーマンスを上げるコーディング方法についての解説です。 ほとんどは Retained Mode に関する事です。 分かり難い訳もありますが、ご了承ください。
この解説は この 記事を参考に書かれています (英語)。

API / Java3D 1.1 の最適化 / コツ / 戻る / トップページ


最適化のための API

Java3D API の中には最適化を目的としたものがいくつか用意されています。 つまり、これらの API をうまく使う事で最適化できるわけです。
 
Capability bits
Capability bit によってそのオブジェクトの値が 変化するかどうかがわかるので、 最適化処理が施される可能性があります。
 
コンパイル
BranchGroup クラスと SharedGroup クラスにある compile() メソッドでその Group にぶら下がっているオブジェクト群をコンパイルできます。 コンパイルされたオブジェクトは Capability bit で設定された属性のみ変更できます。 逆に言えば、Capability bit で設定されていない属性は 変更されないことを利用した内部形式に変換され、 パフォーマンス向上に利用されます。
 
有効範囲
Bounds オブジェクトは Light, Behavior, Fog, Clip, Background, BoundingLeaf, Sound, Soundscape などのオブジェクトに設定するように作られています。 これは有効範囲を設定する事で、 有効範囲外のレンダリング時にそれらのオブジェクトの処理を 無視できるからです。
 
Rendering の順序
SceneGraph は木構造のため、 Node には順番があるわけではありません (例えば、配列で物体を管理していたら配列の添え字という順番がありますね)。 その点を利用して OrderedGroup クラスや半透明の物体などの特殊なもの以外に対しては 物体の描画順をパフォーマンスを向上するように変えています。
 
Appearance の構成
Shape3D オブジェクトは Geometry オブジェクトと Appearance オブジェクトを参照してます。 そして、その Appearance オブジェクトは さらに多くの別のオブジェクトへの参照 のみで構成されています。 この参照のみで構成されている点を利用して最適化を行う事ができます。 Appearance オブジェクトが参照しているオブジェクトの属性の変更を 調べる手段を簡単化し、 低レベルのレンダリング API の変更を最小限にとどめることができます。

Java3D 1.1 で行われている最適化

Java3D 1.1 で現在行われている最適化について紹介します。
 
ハードウェア
Java3D は低レベルのレンダリング API として OpenGL と Direct3D を使っています。 OpenGL や Direct3D はハードウェアアクセラレーションが 受けられるので、結果として Java3D もハードウェアアクセラレーションを受けています。
 
コンパイル
現在の Java3D はコンパイルを使った最適化を 1 つだけ行っています。 BranchGroup オブジェクトはコンパイルされると その下にぶら下がっている Shape3D オブジェクトを 検索する時間を短縮するように処理されます。 変化しない Shape3D オブジェクト群を 1 つにまとめて BranchGroup オブジェクトに関連付けます。
 
状態でソートされた Rendering
SceneGraph が木構造である事を利用して物体の Rendering 順序を最適化しています。 Rendering される物体はその特性で並び替えられます。 並び替えの基準となる特性は順番に Light, Texture, Geometry Type, Material, localToVworld transform となります。
 
View Frustum Culling
Java3D は view frustum culling を実装しています。 view frustum culling は特定の Canvas3D オブジェクトに対する 処理が行われる時に行われます。 これは低レベルグラフィックス API が処理するオブジェクト数を減らす事ができます。
 
マルチスレッド
Java3D API はマルチスレッド環境を意識して作られています。 現在の実装は完全にマルチスレッド化されています。 可視判定やレンダリング、Behavior のスケジューリング、 音声のスケジューリング、入力のスケジューリング、衝突判定 などは並列実行されます。

最適化のコツ

最適化、特に高速化するためのコツを解説します。
 
Capability bits
アプリケーションの機能を良く検討して必要な Capability bit のみをセットします。 Capability bit がセットされていない属性については 様々な最適化が受けられます。
 
有効範囲
様々な Leaf Node に対しての空間的な広がりを考えて それに応じて Bound オブジェクトを決めます。 こうする事でレンダリングする場所から 離れている場所にあるオブジェクトの処理が省略され、高速化されます。 ただし、幾何学的な Bound オブジェクトには適応されず、 自動的に計算された Bound オブジェクトにのみ適応される点に注意してください。
 
Shape3D オブジェクトの数
現在の Java3D の実装では Shape3D オブジェクトの使用には ある量のオーバーヘッドがあります。 Shape3D オブジェクトの数を減らした方が良いという事です。 しかし、空間的な局所性のない Shape3D オブジェクトは view frustum culling を効果的に無効にして 逆にパフォーマンスを向上させます。 そのため、view frustum culling の影響を考えてバランスの取れた Shape3D オブジェクトの使い方を実験してみる事が大切です。
 
幾何学形とそのフォーマット
大抵のレンダリングハードウェアは 長い triangle strip をレンダリングする時に 最大のパフォーマンスが得られます。 しかし、大抵のファイルに記録されている幾何学情報は ばらばらの三角形や小さな triangle fan (ポリゴン) で構成されてしまっています。 Java3D のユーティリティーパッケージには com.sun.j3d.utils.geometry.Stripifier クラスがあります。 これは、与えられた幾何学形を長い triangle strip に変換するクラスなので、 試してみる価値はあります。 また、大抵のレンダリングハードウェアは single triangle fan の長いリストよりは ばらばらの三角形の長いリストのほうが速く処理できます。 Stripifier クラスは今後、より効率的な stripification ができるように更新されていきます。
 
Appearance, Texture, Material の参照
Java3D は物体の表示順をソートしてパフォーマンスを上げようとします。 その機能を生かすためにできるだけ Shape3D オブジェクト間で Appearance, Texture, Material オブジェクトを共有したほうが効率的になります。
 
スレッド
スレッド機能のある Java 言語でのプログラミングは強力である一方、 上手に注意深く使わないとパフォーマンスを落としかねません。 Java のスレッドを使うに当たって注意すべき点が 2,3 点あります。
 
demand driven fashion
1 つめはスレッドを demand driven fashion で使う事です。 これはするべき処理がある時のみスレッドを走らせるという事です。 無駄に走っているスレッドは Java3D も含めて システムから処理時間を奪ってしまいます。
 
プライオリティー
2 つめはスレッドのプライオリティーを適切に設定する事です。 ほとんどの JavaVM はプライオリティーを積極的に使うようになっていきます。 低すぎるプライオリティーはスレッドを枯渇させてしまいますし、 高すぎるプライオリティーはシステムの残りを枯渇させてしまいます。 プライオリティーをどうしようか迷った時は デフォルトのプライオリティーを使ってください。
 
Behavior クラス
3 つめはそのアプリケーションが本当にそのスレッドを 必要としているのか検討する事です。 1 フレームに 1 回だけ実行すればよいものであれば 毎フレーム動いてくれる Behavior クラスを使った方が効率的です。
 
Java3D のスレッド
Java3D は実装にたくさんのスレッドを使用していますので、 上に書いたような予防策が必要です。 ほとんどの場合、Java3D はそのスレッドを効率的に使います。 demand driven fashion ですし、 デフォルトのプライオリティーを使っていますが、 その方法に完全にのっとっていない場合もわずかながらあります。
 
Behavior
その様な例の 1 つめは 未決定の WakeupOnElapsedTime 基準にある Behavior scheduler です。 この場合、最小 WakeupOnElapsedTime 基準に満ちた時に wakeup しなければならないからです。 そのため、WakeupOnElapsedTime クラスを使う事で Behavior scheduler を必要以上に動かしてしまうことがあります。
 
衝突判定
Java3D のスレッドがうまく制御されない 2 つめの例は衝突判定がされる時です。 衝突判定される時、衝突判定用スレッドは事実上無駄に動いています。 これは現在の実装の欠点なので、将来的には修正されるでしょう。 衝突判定システムを使う代わりに 1 フレームに 1 回 PickSegment クラスか PickShape クラスで picking を使う方法があります。 これは特殊な形のアプリケーションとして使えます。 アプリケーションは幾何学形状に基づいた衝突判定ではなく、 bound に基づいた衝突判定を使うべきです。
 
音声
3 つめの例は Sound subsystem です。 現在の音声レンダリングエンジンの限界のために 音声の使用は内部で高いプライオリティーのスレッドを 起動させてしまいます。 この事は逆にパフォーマンスに影響を与えてしまいます。
 
一般のスレッド
Java3D は完全にマルチスレッド化されているので、 システムにある CPU の数を増やす事で 大きくパフォーマンスを向上させる事ができます。 正確にアニメーションをさせたいアプリケーションには 2 つの CPU で十分です。 音声や衝突判定などの機能を使い出すともっと多くの CPU が役に立ってきます。 注意: Solaris 環境で使う時は ネイティブスレッドが有効である点に注意してください。 Green thread は複数の CPU を活用しません。
 
Occlusion Culling のための Switch クラスの使用
もし、アプリケーションが 1 人称の視点で 動作環境を詳しく把握しているのであれば、 Switch クラスを簡単な occlusion culling の実装に使えます。 Switch オブジェクトの子のうち、 今見えていないものを OFF にします。 もし、この種の知識があれば この方法はかなり便利なテクニックとなります。
 
アニメーションのための Switch クラスの使用
ほとんどのアニメーションは物体に効果を与える変換を 変えていく事でできています。 アニメーションがかなり単純で繰り返すものだったら flipbook テクニックがアニメーションに使えます。 単純にアニメーションの全フレームを Switch オブジェクトにぶら下げ、 SwitchValueInterpolator オブジェクトをその Switch オブジェクトに使います。 この方法はスムーズなアニメーションのために メモリ消費量を増やしてしまいます。
 
OrderedGroup
OrderedGroup クラスやそのサブクラスの実体は その他の Group ほど高いパフォーマンスは得られません。 これは物体の表示順をソートする最適化が受けられないためです。 OrderedGroup を使わない代価案があるのでしたら そちらを使った方が効率的です。
 
LOD Behavior
複雑なシーンでは 詳細な描写を必要としない物体をレンダリングするのに使う Geometry クラスを減らすために LOD Behavior を使う事でパフォーマンスの向上が望めます。 高速なレンダリング速度を得るために メモリー消費量を増やすという選択肢もあります。
 
Picking
もし、厳密な幾何学形状に基づいた picking が必要ないのでしたら Bound に基づいた picking を使います。
 
物体の移動と ViewPlatform の移動
全部の物体を単純に移動する時は物体 1 つ 1 つを動かすのではなく、 ViewPlatform オブジェクトを動かすべきです。 当然の事ですが、 変更する Transform3D オブジェクトの数が減るからです。

戻る