【ゲーム制作】No.23 両眼立体視②(Direct3D11)

 

今回のテーマは「両眼立体視」で前回の続きとなります。
Direct3D立体視を行う為のソース作成がメインとなります。

立体視を行う為には、大きく分けて2つのフェーズがあります。

 

(1).マルチビューポート機能により右目用・左目用の複数ウインドウを作成する。
(2).右目用・左目用のView行列と射影行列を作る。

 

以下、それぞれの作成について述べていきます。


(1).マルチビューポート機能により右目用・左目用の複数ウインドウを作成する。

 

これは以前のブログで紹介したサイト「MaverickProjectの147.ステレオグラム」
参照頂けれは良いかと思います。
私もフルコピペで作成出来ました。

 

発生した問題としては、「SV_ViewportArrayIndexを使うとポリゴン欠けが起こる」です。
普通、SV_ViewportArrayIndex=0なら左目用、SV_ViewportArrayIndex=1なら右目用
と表示するウインドウを変えることが出来るのですが、
SV_ViewportArrayIndex=0の場合のみポリゴンの欠けが起こったり
モデル一式がまるまる表示されなくなるといった症状が起きました。
調査しましたが修正方法を見つけることが出来ず、仕方なく複数パスに分けて
描画することで回避することにしました。


(2).右目用・左目用のView行列と射影行列を作る。

これはサンプルサイトが複数見つかりましたので
これから情報を得て作り上げてみました。

 

①MaverickProjectの147.ステレオグラム

おなじみのこちらのサイトです。
このサイトにもサンプルコードがありましたので
動作してみました。

その結果ですが・・
残念ながらサンプルコードのビルドが出来ず
動作の確認は出来ませんでした。
"HalfLambert2_HalfLambert2_VS_Main.h"
"HalfLambert2_HalfLambert2_GS_Main.h"
"HalfLambert2_HalfLambert2_PS_Main.h"
の3本のファイルがアップされていない為です。
ファイルが無いのではコンパイルしようがない・・

まあこのサンプルである立体視部分のコードは、
View行列に適当な値を加えるという形で
汎用性は無く、私が求める情報は無いなという結論です。

 

②月刊誌、CQ出版社<インターフェース>2011年1月号

こちらも以前紹介した雑誌。
この雑誌の「OpenGLを使って立体視の絵を作ろう!」という記事があり
こちらにもサンプルコードがあるので動かしてみました。
このサンプルコードはOpenGLなのでGLUTを使ってコンパイルします。
(ちなみに記事はネット上にはありませんがサンプルコードなら
こちらにアップしてあります。)

そして、その結果ですが・・
サンプルはoff-axis法でView行列は両目平行です。
ソースはシンプルで扱いやすいです。
OpenGLなので私は今回はキャンセルしましたが
使える方ならばこれをメインにしても良いかもしれません。
ただ、表示された立体視のサンプルは、立体度が弱く
効いているのかわかりにくいなぁという印象でした。
また、ソースには誤りがあります。
誤:glTranslated(eye * distance, 0, 0);
正:glTranslated(eye, 0, -distance);

これはサンプルコードを動かしたものです。立体的に見えるでしょうか・・?

 

③GitHubのd3d-stereo-sample
Direct3Dのサンプルということで、こちらも動かしてみました。
ビルドするには、Visual StudioC++/WinRTVisualStudio拡張機能(VSIX)をインストール
必要がありました。あと細かい修正がいくつかありました。

そして、その結果ですが・・
どうやらこのサンプルは3Dステレオ機能を持つディスプレイが必要とのことで
私の環境では有効にはならず立体視を確認することは出来ませんでした。
一応その後、私の立体視サンプルに移植して立体視を確認しましたが
立体になってるかなぁ程度で特に得られる情報はありませんでした。

 

④off-axis法を用いたステレオグラフィックスの奥行き感に関する研究
PDFです。立体視に対する情報をいろいろと調べたんですが、
立体視を目的としたView行列と射影行列の作り方という点では、
この資料が一番だと思いました。

そして、その結果ですが・・
サンプルコードを移植して立体視を確認してみましたが
まずまずの結果に見えました。サンプルはOpenGLを使用しており
Diret3Dに変更する必要があり苦労しました。
(というかDirect3Dへの移植はまだ完成では無くバグが残ってそうな感じです)

 

以下ソースコードです。

// 「off-axis 法を用いたステレオグラフィックスの奥行き感に関する研究」の移植(off-axis法)

HOUZMETHOD_VD    perspective_view_t::get_stereogram_matrix(    rmatrix4x4_t&    m_左目_ビュー行列,
                                                            rmatrix4x4_t&    m_右目_ビュー行列,
                                                            rmatrix4x4_t&    m_左目_射影行列,
                                                            rmatrix4x4_t&    m_右目_射影行列) const
{
    //p_camera->set_position(initv(0,0,-50));        // 参考:カメラの位置
    //const cvector3_t    pos        = initv(0, 0, 0);    // 参考:モデルの位置

    extern int    gui_i_pupillary_distance;    // 視差量(左目と右目の距離) 調節用のパラメータ変数
    const float_t    gui_f_pupillary_distance    = gui_i_pupillary_distance * 0.01f;


    // View Matrix

    const cvector3_t    side    = transpose(get_orthonormal_basis()(0));    // カメラの向き。横方向。X軸。
    const cvector3_t    up        = transpose(get_orthonormal_basis()(1));    // カメラの向き。縦方向。Y軸。
    const cvector3_t    front    = transpose(get_orthonormal_basis()(2));    // カメラの向き。奥方向。Z軸。

    cvector3_t    v_両目_カメラの位置        = get_center_position();
    cvector3_t    v_両目_視点                = front;
    cvector3_t    v_両目_上視点            = initv(0,+1,0,0);    // 変数upを使用しても良い。

     // to_view()はD3DXMatrixLookAtLH()相当
    m_左目_ビュー行列    = to_view( v_両目_カメラの位置, to_orthonormal_basis( front, v_両目_上視点));    // 左目用のビュー行列。カメラの向きに平行。

    m_右目_ビュー行列    = to_view( v_両目_カメラの位置, to_orthonormal_basis( front, v_両目_上視点));    // 右目用のビュー行列。カメラの向きに平行。

    // ビュー行列使用時はtranspose()で転置すること。

    // Projection Matrix

    float_t    f_距離N        = 1.0;        // N:(視点から前方クリッピング面までの距離) 値はPDFの実験の項から借用した。
    float_t    f_距離L        = 50.0;        // L:(視点からスクリーン面(ディスプレイ面)までの距離) 値はPDFの実験の項から借用した。
    float_t    f_距離F        = 150.0;    // F:(視点から後方クリッピング面までの距離) 値はPDFの実験の項から借用した。
    float_t    f_左右眼の区別    = 0.0;    // E:(左右眼の区別(右眼= 1.0, 左眼=-1.0)
    float_t    f_視点間距離    = 6.5 * 0.5 + gui_f_pupillary_distance;    // D:(視点間距離の1/2の値) 値はPDFの実験の項から借用した。
    float_t    f_スクリーンの幅    = 30.4;    // W:(スクリーン(ディスプレイ)の横の大きさ) 値はPDFの実験の項から借用した。
    float_t    f_スクリーンの高さ    = 27.2;    // H:(スクリーン(ディスプレイ)の縦の大きさ) 値はPDFの実験の項から借用した。

    {    // 左目
        f_左右眼の区別    = -1.0;    //(右眼= 1.0, 左眼=-1.0)
        float_t    射影行列P_パラメータ_A    = 2.0 * f_距離L / f_スクリーンの幅;
        float_t    射影行列P_パラメータ_B    = 0;
        float_t    射影行列P_パラメータ_C    = 2.0 * f_距離L / f_スクリーンの高さ;
        float_t    射影行列P_パラメータ_D    = 0;
        float_t    射影行列P_パラメータ_E    = -( f_距離F + f_距離N) / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_F    = -2.0 * f_距離F * f_距離N / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_EDx    = f_距離F / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_FDx    = -f_距離N * f_距離F / ( f_距離F - f_距離N);
        float_t    回転行列R_パラメータ_sinθ    = f_左右眼の区別 * f_視点間距離 / sqrt( pow( f_左右眼の区別 * f_視点間距離, 2.0) + f_距離L * f_距離L);
        float_t    回転行列R_パラメータ_cosθ    = f_距離L / sqrt( pow( f_左右眼の区別 * f_視点間距離, 2.0) + f_距離L * f_距離L);

        // 元のコード
        //rmatrix4x4_t    射影行列P    = initm(    射影行列P_パラメータ_A,    0,                        射影行列P_パラメータ_B,    0,
        //                                        0,                        射影行列P_パラメータ_C,    射影行列P_パラメータ_D,    0,
        //                                        0,                        0,                        射影行列P_パラメータ_E,    射影行列P_パラメータ_F,
        //                                        0,                        0,                        -1,                        0);
        
        // Direct3D用に変更
        rmatrix4x4_t    射影行列P    = initm(    射影行列P_パラメータ_A,    0,                        射影行列P_パラメータ_B,    0,
                                                0,                        射影行列P_パラメータ_C,    射影行列P_パラメータ_D,    0,
                                                0,                        0,                        射影行列P_パラメータ_EDx,    射影行列P_パラメータ_FDx,
                                                0,                        0,                        1,                        0);

        rmatrix4x4_t    回転行列R    = initm(    回転行列R_パラメータ_cosθ,    0,    -回転行列R_パラメータ_sinθ,    0,
                                                0,                            1,    0,                                0,
                                                回転行列R_パラメータ_sinθ,    0,    回転行列R_パラメータ_cosθ,        0,
                                                0,                            0,    0,                                1);

        rmatrix4x4_t    移動行列T    = initm(    1,    0,    0,    f_左右眼の区別 * f_視点間距離,    // 元のコードでは「-f_左右眼の区別」。変更した。
                                                0,    1,    0,    0,
                                                0,    0,    1,    -f_距離L,
                                                0,    0,    0,    1);

        m_左目_射影行列    = mul( 射影行列P, mul( 回転行列R, 移動行列T));

        // 射影行列使用時はtranspose()で転置すること。
    }

    {    // 右目
        f_左右眼の区別    = 1.0;    //(右眼= 1.0, 左眼=-1.0)
        float_t    射影行列P_パラメータ_A    = 2.0 * f_距離L / f_スクリーンの幅;
        float_t    射影行列P_パラメータ_B    = 0;
        float_t    射影行列P_パラメータ_C    = 2.0 * f_距離L / f_スクリーンの高さ;
        float_t    射影行列P_パラメータ_D    = 0;
        float_t    射影行列P_パラメータ_E    = -( f_距離F + f_距離N) / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_F    = -2.0 * f_距離F * f_距離N / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_EDx    = f_距離F / ( f_距離F - f_距離N);
        float_t    射影行列P_パラメータ_FDx    = -f_距離N * f_距離F / ( f_距離F - f_距離N);
        float_t    回転行列R_パラメータ_sinθ    = f_左右眼の区別 * f_視点間距離 / sqrt( pow( f_左右眼の区別 * f_視点間距離, 2.0) + f_距離L * f_距離L);
        float_t    回転行列R_パラメータ_cosθ    = f_距離L / sqrt( pow( f_左右眼の区別 * f_視点間距離, 2.0) + f_距離L * f_距離L);

        // 元のコード
        //rmatrix4x4_t    射影行列P    = initm(    射影行列P_パラメータ_A,    0,                        射影行列P_パラメータ_B,    0,
        //                                        0,                        射影行列P_パラメータ_C,    射影行列P_パラメータ_D,    0,
        //                                        0,                        0,                        射影行列P_パラメータ_E,    射影行列P_パラメータ_F,
        //                                        0,                        0,                        -1,                        0);
        
        // Direct3D用に変更
        rmatrix4x4_t    射影行列P    = initm(    射影行列P_パラメータ_A,    0,                        射影行列P_パラメータ_B,    0,
                                                0,                        射影行列P_パラメータ_C,    射影行列P_パラメータ_D,    0,
                                                0,                        0,                        射影行列P_パラメータ_EDx,    射影行列P_パラメータ_FDx,
                                                0,                        0,                        1,                        0);

        rmatrix4x4_t    回転行列R    = initm(    回転行列R_パラメータ_cosθ,    0,    -回転行列R_パラメータ_sinθ,    0,
                                                0,                            1,    0,                                0,
                                                回転行列R_パラメータ_sinθ,    0,    回転行列R_パラメータ_cosθ,        0,
                                                0,                            0,    0,                                1);

        rmatrix4x4_t    移動行列T    = initm(    1,    0,    0,    f_左右眼の区別 * f_視点間距離,    // 元のコードでは「-f_左右眼の区別」。変更した。
                                                0,    1,    0,    0,
                                                0,    0,    1,    -f_距離L,
                                                0,    0,    0,    1);

        m_右目_射影行列    = mul( 射影行列P, mul( 回転行列R, 移動行列T));

        // 射影行列使用時はtranspose()で転置すること。
    }
}

 

⑤オリジナルコード
いろいろな資料を得て私が作ったオリジナルのコードです。
立体視を目的として、右目用と左目用のView行列と射影行列を作ります。
資料としたサイトは以下の2つです。
3D ベーシック講座 第2回 AG-3DA1での立体映像の記録
液晶シャッタメガネ(時分割方式)を用いた立体視の実現

 

この画像は上のリンク先に貼っているものです。
小さい画像ですが、ちゃんと立体に見えると思います。
この画像を参考にして、球の位置から
球の傾きを求めれば良い訳です。
それで立体視した画像が表示出来ます。

 

以下ソースコードです。

HOUZMETHOD_VD    perspective_view_t::get_stereogram_matrix(    rmatrix4x4_t&    m_左目_ビュー行列,
                                                            rmatrix4x4_t&    m_右目_ビュー行列,
                                                            rmatrix4x4_t&    m_左目_射影行列,
                                                            rmatrix4x4_t&    m_右目_射影行列) const
{
    // 各種パラメータ変数
    extern int    gui_i_pupillary_distance;    // 視差量(左目と右目の距離)
    extern int    gui_i_pupillary_distance2;    // 基準面距離(カメラから基準点までの距離)
    const float_t    gui_f_pupillary_distance    = gui_i_pupillary_distance * 0.01f;
    const float_t    gui_f_pupillary_distance2    = gui_i_pupillary_distance2 * 0.01f;


    // View Matrix

    const cvector3_t    side    = transpose(get_orthonormal_basis()(0));    // カメラの向き。横方向。X軸。
    const cvector3_t    up        = transpose(get_orthonormal_basis()(1));    // カメラの向き。縦方向。Y軸。
    const cvector3_t    front    = transpose(get_orthonormal_basis()(2));    // カメラの向き。奥方向。Z軸。

    cvector3_t    v_両目_カメラの位置        = get_center_position();
    cvector3_t    v_左目_位置        = get_center_position() + side * gui_f_pupillary_distance;
    cvector3_t    v_右目_位置        = get_center_position() + -side * gui_f_pupillary_distance;
    cvector3_t    v_両目_遠視点    = get_center_position() + front * gui_f_pupillary_distance2;
    cvector3_t    v_左目_視点        = normalize( v_両目_遠視点 - v_左目_位置);
    cvector3_t    v_右目_視点        = normalize( v_両目_遠視点 - v_右目_位置);
    cvector3_t    v_両目_上視点    = initv(0,+1,0,0);    // 変数upを使用しても良い。

    // to_view()はD3DXMatrixLookAtLH()相当
    const rmatrix4x4_t    m_左目_新ビュー行列    = to_view( v_左目_位置, to_orthonormal_basis( v_左目_視点, v_両目_上視点));    // 左目用のビュー行列。遠視点へ視点。
    const rmatrix4x4_t    m_右目_新ビュー行列    = to_view( v_右目_位置, to_orthonormal_basis( v_右目_視点, v_両目_上視点));    // 右目用のビュー行列。遠視点へ視点。

    m_左目_ビュー行列    = m_左目_新ビュー行列;
    m_右目_ビュー行列    = m_右目_新ビュー行列;

    // ビュー行列使用時はtranspose()で転置すること。


    // Projection Matrix

    // pers_proj_LH()の実装
    //const element_type0 cot            = rcp(tan( fov_y * 0.5f));
    //const element_type0 z_factor    = far_z / ( far_z - near_z);
    //return initm(    cot / aspect,    0,        0,            0,
    //                0,                cot,    0,            0,
    //                0,                0,        z_factor,    -near_z * z_factor,
    //                0,                0,        +1,            0);

    m_左目_射影行列    = pers_proj_LH(
                    get_fov_y(),                    
                    2.0 / 3.0,    //get_aspect(),ビューポートが横幅の半分のサイズになるのでアスペクト比を変更する
                    get_near_range(),
                    get_far_range());

    m_右目_射影行列    =     m_左目_射影行列;

    // 射影行列使用時はtranspose()で転置すること。
}

 

今回の目標について

前回目標として以下の3点を挙げていました。

 

①「3DS並の立体感を実現したい」
⓶「ビュー射影行列を得る関数を作りたい」
③「飛び出す立体視を実現したい」

 

今回で①と②は実現出来たと思いますが
③はちょっと実現出来ませんでした。
「飛び出す立体視」という技術が
殆ど見当たらないんですよね。。
何か情報があったら教えて欲しいです。
今回は以上です。