前回の記事でOculus RiftをOGREで使うデモを作ってみたのですが,そのときにぶち当たった問題です.
Oculus Riftでは一つのレンダーターゲットの左右半分々々に,両眼から見たシーンをそれぞれ描画する必要があります. このためには一つのレンダーターゲットに,複数のビューポートを設定します.
OGREでは通常以下のようになります.OGREのビューポート(Ogre::Viewport
)は「レンダーターゲットにカメラを
渡してビューポートを生成する」という形で作成します.
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オーダーは最も小さいものにしておきます.
たとえば以下のようにします.
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
を何らかの形で継承します.
class Hoge : public Ogre::RenderTargetListener { // .. void Init() { Ogre::RenderTarget *renderTarget = /* ... */; renderTarget->addListener(this); // 初期化時にリスナーを登録しておく. }
Ogre::RenderTargetListener::preRenderTargetUpdate
メソッドを,以下のようにオーバーライドして実装しておきます.
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のユーザーコードから 直接呼び出されることを意図されたものではないので,本当に安全なのかどうかは分かりません (軽くライブラリのコードを追った限りは害のある挙動はしないようですが).