前回の記事でOculus RiftをOGREで使うデモを作ってみたのですが,そのときにぶち当たった問題です.
Oculus Riftでは一つのレンダーターゲットの左右半分々々に,両眼から見たシーンをそれぞれ描画する必要があります. このためには一つのレンダーターゲットに,複数のビューポートを設定します.
OGREでは通常以下のようになります.OGREのビューポート(Ogre::Viewport
)は「レンダーターゲットにカメラを
渡してビューポートを生成する」という形で作成します.
1 2 3 4 5 6 7 8 9 10 | Ogre::SceneManager *sceneManager = /* ... */ ; Ogre::RenderTarget *renderTarget = /* ... */ ; auto camera1 = sceneManager->createCamera( "camera1" ); auto vp1 = renderTarget->addViewport(camra1, 0, 0.0f, 0.0f, 0.5f, 1.0f); // 左半分のビューポート auto camera2 = sceneManager->createCamera( "camera2" ); auto vp2 = renderTarget->addViewport(camra2, 1, 0.5f, 0.0f, 0.5f, 1.0f); // 右半分のビューポート ... |
Ogre::RenderTarget::addViewport()
がビューポートを生成するメソッドです。引数は、カメラ、Zオーダー、
ビューポートの左上の位置,幅高さ,となります.
参考:Basic Tutorial 8 – Ogre Wiki
通常はこれで事足りるわけですが,Direct3D11レンダーシステムを使う場合これはうまく動作しません.
フォーラムにも報告があったのですが,
D3D11レンダーシステムでこの方法を使うと,最後に描かれたビューポートのみが有効になります.
↑左がD3D9レンダーシステムを使った描画結果,右は同じプログラムをD3D11レンダーシステムで動作させた描画結果
何故こういうことが起きるかというと,D3D11のフレームバッファのクリア操作がビューポートやシザーの設定を無視するためです.
Differences between Direct3D 9 and Direct3D 11/10:
Unlike Direct3D 9, the full extent of the resource view is always cleared. Viewport and scissor settings are not applied.
要するに左半分のビューポートを描いた後,右半分のビューポートを描くときに左半分を含めたレンダーターゲット全域をクリアしてしまっているわけですね.
これはD3D11と,DirectX9やOpenGLとの大きな相違点の一つかも知れません.
おそらくD3D11からは「フレームバッファのクリアは一回で済ませろ」ってことなのでしょう.
OGREはそのようなバックエンドAPIを想定していなかったようで,ビューポートのクリアとは独立にレンダーターゲットを クリアするような手段が用意されていません.
回避策1: レンダーターゲットに空のシーンを描く
若干無駄が多いですが,レンダーターゲットに,空のシーンと関連付いたレンダーターゲット全体を覆うダミーのビューポートを設定する という回避策が考えられます.
Ogre::Viewport
にはsetClearEveryFrame(bool)
というメソッドがあります.これは,ビューポートの更新毎に
フレームバッファをクリアするかどうかの設定です.「最初の一回だけのクリア」を実現するには,
上述のダミーのビューポート以外はこれをオフに設定します.またダミーのビューポートのZオーダーは最も小さいものにしておきます.
たとえば以下のようにします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Ogre::SceneManager *mainSceneManager = /* メインのシーンのためのシーンマネージャ */ ; Ogre::SceneManager *dummySceneManager = /* ダミーのシーンマネージャ(シーングラフは空にしておく) */ ; Ogre::RenderTarget *renderTarget = /* ... */ ; // ダミーのカメラを設定(このカメラのビュープロジェクション変換は使わないので何も設定しなくていい). auto dummyCamera = dummySceneManager->createCamera( "dummyCamera" ); auto dummyViewport = renderTarget->addViewport(dummyCamera, 0); // 最も下側のビューポートとして設定(Z=0) auto camera1 = sceneManager->createCamera( "camera1" ); auto vp1 = renderTarget->addViewport(camra1, 1, 0.0f, 0.0f, 0.5f, 1.0f); // 左半分のビューポート(Z=1) vp1->setClearEveryFrame( false ); // ビューポートごとにクリアしない. auto camera2 = sceneManager->createCamera( "camera2" ); auto vp2 = renderTarget->addViewport(camra2, 2, 0.5f, 0.0f, 0.5f, 1.0f); // 右半分のビューポート(Z=2) vp2->setClearEveryFrame( false ); // ビューポートごとにクリアしない. ... |
この方法はD3D9/OpenGLレンダーシステムでも動きました.先日のOculus Riftのデモではこの回避策を使っています.
回避策2: 無理矢理クリアする
上記の回避策は空のシーンのためのシーンマネージャを生成する必要があり,シーングラフの為のメモリが無駄になります(空なので大したこと無いとは思いますケド).
一応もう少し軽い回避策があります.ただし,将来のバージョンに渡って安全に使える方法かどうかは分かりません.
OGREでは以下のように更新処理が行われます.
- すべての
RenderTarget
を反復して必要なものを更新する. - 各
RenderTarget
について,割り当てられたViewport
のうち必要なものを更新する.- 通常このときに
Viewport
ごとのクリアが行われる.
- 通常このときに
Ogre::RenderTargetListener
を
使って2の処理の前後に追加の処理を挟み込むことが可能です.
なので、2の処理の前にフレームバッファのクリア処理を挟み込みます.
このためにはOgre::RenderTargetListener
を何らかの形で継承します.
1 2 3 4 5 6 7 8 9 | class Hoge : public Ogre::RenderTargetListener { // .. void Init() { Ogre::RenderTarget *renderTarget = /* ... */ ; renderTarget->addListener( this ); // 初期化時にリスナーを登録しておく. } |
Ogre::RenderTargetListener::preRenderTargetUpdate
メソッドを,以下のようにオーバーライドして実装しておきます.
1 2 3 4 5 6 7 8 9 10 11 12 | Ogre::Root *mRoot; // ... virtual void preRenderTargetUpdate ( const Ogre::RenderTargetEvent &evt) /*override*/ { auto rs = mRoot->getRenderSystem(); std::unique_ptr<Ogre::Viewport> vp( new Ogre::Viewport( nullptr , evt.source, 0.0f, 0.0f, 1.0f, 1.0f, 0)); rs->_setRenderTarget(evt.source); rs->_setViewport(vp.get()); rs->clearFrameBuffer(FBT_COLOUR | FBT_DEPTH | FBT_STENCIL, Ogre::ColourValue::Black); } // .. }; |
事前にメインとなるビューポート全てにsetClearEveryFrame(false)
を呼び出しておく必要がある点は回避策1と同様です.
Ogre::RenderSystem::clearFrameBufferを呼び出して レンダーターゲットをクリアしています.
その前に,カメラと紐付いていないViewport
を生成して,_setRenderTarget
と_setViewport
を呼び出しています.
少なくともD3D9/11/OpenGLレンダラでの動作を確認しました.
ただアンダースコアで始まるAPIは,publicなメソッドではあるもののOGREのユーザーコードから 直接呼び出されることを意図されたものではないので,本当に安全なのかどうかは分かりません (軽くライブラリのコードを追った限りは害のある挙動はしないようですが).