【ゲーム制作】No.18 ビルボード(Direct3D11)

 

今回は ビルボードになります。
ビルボードとは何なのか、まったく知識が無い状態で始めたので
このテストもゼロからの構築となりました。
ビルボードは比較的Web上に多く情報があった為参考になりました。

今回参考になったサイトは以下です。

①「指定の形状が視線方向を向くようにしたい (スクリプト)
Shade3Dというプラットフォームでの実装ですが、
自分が最も望んでいる内容でした。途中で切り上げてしまった為
ちゃんと動作は確認していません。いずれしたいと思っています。

 

// ビルボードの表示テスト
HOUZMETHOD_VD text_out_effect_t::billboard_main_test( const ID3D11DeviceContextPtr&    device_context)
{
    try
    {
        // テストの為にビルボード化+回転されたボックスを表示させます。


        // 回転させる為の角度の変数です。

        static float_t    s_angle    = -1.0;    // 回転用の角度
        s_angle    = s_angle > 360 ? 0 : s_angle + 1;    // 0 ~ 360


        // カメラの位置を取得します。
        // これはGPUのエフェクト変数として別途用意しており、それを流用しています。

        cvector4_t    view_position_W    = zero();
        if( m_h_view_position_W)
            m_h_view_position_W->GetFloatVector( (float32_t*)view_position_W.get_block_ptr(0));    // GPU制限の為、GPUのfloat精度をfloat32固定にします。


        // カメラの位置を示すベクトルを求めます。
        // 「カメラの位置 - ボックスの位置」で求めることが出来ます。

        const cvector4_t    v_object_center_pos_inW    = initv( 0, 5, 0, 0);    // ボックスの位置
        const cvector3_t    v_eye_dir    = normalize( view_position_W - v_object_center_pos_inW);


        // カメラの位置を示すベクトルが上を向きすぎて
        // 上ベクトルと近づくと、ベクトルの誤差が増え映像が破綻します。
        // そうならないように処置を加えます。(ただし完璧ではありません。ちょっとマシになる程度・・)
        cvector3_t        v_up    = initv( 0, 1, 0);
        const float_t    dot_v    = abs( dot( v_eye_dir, v_up));
        if( dot_v >= 0.98)
            v_up    = initv( 0, 0, 1);


        // カメラの位置を示すベクトルから正規直交行列を作ります。
        // これは、X軸はY軸とZ軸の外積、Y軸はUP方向、Z軸はカメラの位置を示すベクトル(v_eye_dir)の
        // 回転行列になります。
        // ボックスにこの行列を掛けると、ボックスの正面がカメラの位置を示すベクトル(v_eye_dir)になるよう
        // 回転されます。
        const rmatrix4x4_t    m_diff    = to_orthonormal_basis( v_eye_dir,  v_up);


        // ワールド行列を作ります。
        rmatrix4x4_t    world    = identity();

        world    = scaling< 4 >(cvector3_t(initv( 1.0, 1.0, 0.1)) );    // ボックスの半径

        // X軸で回転するケースです。(可能な限り表示しています。すぐに破綻してしまいます。。)
        //world    = transpose( mul( world, transpose( rmatrix4x4_t( rotate_x( to_radian(s_angle)) ) ) ) );
        //world    = mul( transpose( m_diff ), world);

        // Y軸で回転するケースです。(可能な限り表示しています。すぐに破綻してしまいます。。)
        //world    = transpose( mul( world, transpose( rmatrix4x4_t( rotate_y( to_radian(s_angle)) ) ) ) );
        //world    = mul( transpose( m_diff ), world);

        // Z軸で回転するケースです。これは正常に表示できます。これがデフォルトになります。
        world    = mul( transpose( m_diff ), world);
        world    = mul( world, rotate_z( to_radian(s_angle)) );

        // ボックスの位置を適用します。
        cvector3_t    v_translation    = initv(0,+5,0);    // 上のボックスの位置と同じ値であること。
        rmatrix4x4_t    translation    = translate< 3 >( v_translation);
        world    = mul( translation, world );
        

        // ボックスを表示します。
        // 今回のテストでは独自のライブラリを使って表示していますが、
        // 普通に表示させて問題はありません。

        // 視錐台の頂点データをアップする為にバーテックスバッファをマップ(Open)します。
        get_D3D11_geometric_shape_effect_ptr()->map_vb_shape_orders(device_context);    // geometric_shape_effect_tオブジェクトを静的操作する拡張。(2024/2/25)

        // ボックスを表示させる関数。引数は左から、形状の種類(球やボックスなどを選択)、ワールド行列、色、ワイヤーフレームの有無になります。
        get_D3D11_geometric_shape_effect_ptr()->push_draw_shape_order( shape_order_type_box, world, color_t(1,0,0,1), false);

        // バーテックスバッファをアンマップ(Close)します。
        get_D3D11_geometric_shape_effect_ptr()->unmap_vb_shape_orders(device_context);    // geometric_shape_effect_tオブジェクトを静的操作する拡張。(2024/2/25)

    }
    catch( const general_exception&)
    {
        throw;
    }
}

 

// 以降はto_orthonormal_basis()のコードです。

// 見づらいですので参考程度でお願い致します。

template< uint_t row, typename traits_type, typename storage_type >
HOUZ_FORCE_INLINE packed_matrix< 4, 4, traits_type, matrix_row_major, matrix_storage_value > HOUZCALL    to_orthonormal_basis    (    /*[in]*/const packed_vector< row, traits_type, matrix_col_major, storage_type >&    front)
{
    typedef    packed_matrix< 4, 4, traits_type, matrix_row_major, matrix_storage_value >    matrix_type;
    typedef    matrix_type::rvector_type    rvector_type;
    typedef    matrix_type::element_type    element_type;

    rvector_type    tangent, binormal, normal;

    normal    = normalize( transpose(front));

    const rvector_type    abs_normal    = abs( normal);
    const element_type    normal_min    = min( abs_normal);

    if( abs_normal(0) == normal_min)
        binormal    = initv(1,0,0);
    else
    if( abs_normal(1) == normal_min)
        binormal    = initv(0,1,0);
    else
        binormal    = initv(0,0,1);
    tangent        = normalize( cross( binormal, normal));
    binormal    = normalize( cross( normal, tangent));
    assert( abs( dot( tangent, binormal)) < 0.1f);
    assert( abs( dot( binormal, normal)) < 0.1f);
    assert( abs( dot( normal, tangent)) < 0.1f);

    return initv( tangent, binormal, normal, last(1));
}

template< uint_t row, typename traits_type, typename storage_type0, typename storage_type1 >
HOUZ_FORCE_INLINE packed_matrix< 4, 4, traits_type, matrix_row_major, matrix_storage_value > HOUZCALL    to_orthonormal_basis    (    /*[in]*/const packed_vector< row, traits_type, matrix_col_major, storage_type0 >&    front,
                                                                                                                                    /*[in]*/const packed_vector< row, traits_type, matrix_col_major, storage_type1 >&    up)
{
    typedef    packed_matrix< 4, 4, traits_type, matrix_row_major, matrix_storage_value >    matrix_type;
    typedef    matrix_type::rvector_type    rvector_type;
    typedef    matrix_type::element_type    element_type;

    static const rvector_type zero_        = zero();
    static const element_type threshold    = 0.7f; // 0.7 ≒ cos(π/4)

    const rvector_type binormal    = normalize( transpose(up));
    const rvector_type normal    = normalize( transpose(front));

    if( abs( dot( binormal, normal)) < threshold)
    {
        const rvector_type    tangent    = normalize( cross( binormal, normal));
        return initv( tangent, cross( normal, tangent), normal, last(1));
    }
    else
        return to_orthonormal_basis( front);
}

【ゲーム制作】No.17 シャドウマッピング②(Direct3D11)

 

グレーのボックスはバウンディングボックス、赤いボックスは最小最大ベクトルから作成したボリュームになります。画像には含めていませんが視錐台(light_frustum)を表示することも出来ます。

 

・前回の更新日は2033年6月28日あたりでしたので

約9月ぶりの更新になります。お待たせして申し訳ございません。

 

・更新が遅れた理由ですが、ふとしたことから
完成済みだと思っていたシャドウマップの実装が
足りていないことがわかり、その実装を始めたことがきっかけでした。
(スポットライトが未完成なだけで、ディレクショナルライトは

完成出来ていると思っていましたが実は何も実装されていなかった)

 

・しかし実装を始めたところ、設計を100%ゼロから始めたので、

バグや設計ミスが多く起こり進捗が全然進まなくなってしまいました。
これが更新に遅れた原因になります。

例えば右へ回転すると影が正しく表示されるのに

左へ回転すると影が消えるとか、そういったバグの

発生が止まらない、そんな感じでした。

 

・今回の実装はuniform shadowmap(ディレクショナルライト)になります。


・uniform shadowmap(平行光)の実装では

シャドウマップ用の射影行列を作る必要があります。(行列は正射影を使います。)

そして正射影を行う為のパラメータとして最小と最大の2つのベクトルが必要となります。

この2つのベクトルを作ることが、処理の最終的な目的となります。


・私の場合は実装に戸惑いリリースが送れましたが

この実装を組み立てること自体はそんなに難しいことではありません。(複数のバウンディングボックスオブジェクトから最小と最大の2つの点を見つければよいだけです)

よってこの実装から得られる情報は殆ど無いと思います。今後の参考程度に見て頂けたらと思います。


・今回もソースは載せますが、全てではなく実装の一部になります。(全部載せたいが難しい・・)
それ以外の細かいフォローはコメントに書いていただければアップしようと思います。

 

続きを読む

【D3DX】D3DX備忘録

今現在DirectX9時代のD3DXライブラリの調査をしていまして、

その備忘録になります。

 

Living without D3DX | Games for Windows and the DirectX SDK blog

にDirectX10/11におけるD3DXを使用しない代替方法を示しています。

例えばID3DXMeshならば以下のライブラリを代わりに使います。

GitHub - microsoft/DirectXMesh: DirectXMesh geometry processing library

※サイト中央の緑ボタン(Code) → 「Download ZIP」クリックでダウンロード出来る。

 

ID3DXMeshは内部のメッシュ情報にメッシュの隣接性情報(Adjacency)

ポイント表現世情報(PointReps)の2つを用いて管理しています。

そのうちポイント表現情報(PointReps)の仕様について、以下の通りとなります。

・サイトによる図入りの説明です。Googleで翻訳すると良いでしょう。(https://www.gamedev.net/forums/topic/146017-convertadjacencytopointreps/2481968)

・ConvertAdjacencyToPointReps()のソースは先のDirectXMeshライブラリ内にあります。

【imgui】No.3 ボタンを無効化したい場合

imguiでボタンやチェックボックスを無効化したい場合

のソースはこちら。

 

            static bool b_test = false;

    ImGui::BeginDisabled(true);    // この行を記入する。
            ImGui::Checkbox("test", &b_test);

            ImGui::EndDisabled();   // この行を記入する。

【ゲーム制作】No.16 PN-AEN テッセレーション(Direct3D11)

今回は テッセレーションの分割の種類のひとつであるPN-AEN(Crack-Free Point-Normal Triangles using Adjacent Edge Normals)の実装になります。


前回行ったPN Triangles テッセレーションでは、一部のポリゴンの継ぎ目で穴があく「クラック」という現象が起こる欠点がありました。


PN-AENではPN Trianglesを改良し、「クラック」の発生を極力防いだものになります。(完全に防ぐ訳ではないみたいです)

 

PN-AENを実装するにあたり使用した資料は以下です。
PN-AEN-Triangles-Whitepaper.pdf

John_McDonald.pdf

 

PN-AENはPNの改良版であり、コードはPNで利用したものが殆ど使用出来ます。
違う点は以下の2点になります。
①IndexBufferの内容変更
②Hull Shader(Control Point) コントロールポイント3から12(9)への拡張

 ※PN-AEN版としては9への拡張なのですが、①IndexBufferの内容変更が

 12への拡張なので12としています。違いは殆どありません。


①IndexBufferの内容変更

PNでのIndexBufferは通常描画と同じで「(インデックス x 3) x 面数」でしたが、
PN-AENでは「(インデックス x 3 + 拡張コントロールポイント x 6) x 面数」に拡張されます。


これによってIndexBufferは元のサイズの3倍になります。

 

手順としてはまず通常の形でIndexBufferを構築し、
何らかの方法でサイズ3倍のIndexBufferに拡張させます。
方法を調査したところ、meshoptimizerというライブラリに
PN-AEN用に拡張してくれるmeshopt_generateTessellationIndexBuffer()関数が
用意されていた為、今回はこれを使用することにしました。

 

関数の簡単な手順は以下の通りとなります。
1、管理者権限でコマンドプロンプトを実行する。
2、vcpkgのソースコードをダウンロードする。「git clone https://github.com/microsoft/vcpkg.git」を実行
3、vcpkgをインストールする。「bootstrap-vcpkg.bat」を実行
4、vcpkgアプリを立ち上げる。「vcpkg.exe」を実行
5、Visual Studioから利用出来るようにする。「vcpkg integrate install」を実行

6、vcpkgにmeshoptimizerライブラリをインストール「vcpkg install meshoptimizer」を実行
7、ここまでやると後は、普通にVisual Stadioで使用出来ます。
 C++ファイルの先頭に「#include "meshoptimizer.h"」と記述し、
 「meshopt_generate_tessellation_index_buffer(p_index_buffer_dst, NULL, p_index_buffer_src, 6, vb, 6, 12);」と書けばビルド出来ちゃいます。ちょっと凄い!


 「略・・\vcpkg\vcpkg\buildtrees\meshoptimizer\src\v0.18-13698cd784.clean\demo\tests.cpp」の
 tessellation()に使用方法があるので参考にしてください。


 いくらかテストで動かしましたが問題無く動いています。(エラーが出る場合、十中八九引数の誤りだと思います)


 またvcpkgはこのサイトが参考になります。

 

 注意点としてmeshopt_generateTessellationIndexBuffer()で出力されるIndexBufferは
 「(インデックス x 3 + 拡張コントロールポイント x 6) x 面数」では無く
 「(インデックス x 3 + 拡張コントロールポイント x 6 + 拡張コントロールポイント x 3) x 面数」となります。
 そしてIndexBufferは元のサイズの4倍になります。
 ただし対するコードの変更は数字の変更のみで殆ど必要ありません。

 

②Hull Shader(Control Point) コントロールポイント3から12(9)への拡張

 

Hull Shader(Control Point)のコードをPNからPN-AEN用に切り替えます。
PN用ではコントロールポイントは3つでしたが、PN-AEN用はコントロールポイントは12になります。


これは「PN-AEN-Triangles-Whitepaper.pdf」のサンプルコードそのままで問題ありませんでした。
PN用はHS_PNTriangles()、PN-AEN用はHS_PNAENTriangles()です。

 

この様にコンパイルで使い分けします。
HullShader        g_pmx_object__tessellation__HS_main_PNTriangles    = CompileShader(hs_5_0, pmx_object__tessellation__HS_main_PNTriangles());
HullShader        g_pmx_object__tessellation__HS_main_PNAENTriangles    = CompileShader(hs_5_0, pmx_object__tessellation__HS_main_PNAENTriangles());

 

PN-AEN用のHS_PNAENTriangles()では引数「InputPatch<HS_RenderSceneInput, 9> I,」
数字をその時の値に書き換えましょう。( 3 or 9 or 12)

 

CPU側では「D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST」
こちらの数字も同じように書き換えます。( 3 or 9 or 12)

これでうまくいきました。

 

PN用の画像では右目の上辺りにクラックが発生していますが、

 

PN-AEN用では発生していないのがわかります。



 

【ゲーム制作】No.15 PNTriangles11サンプル(Direct3D11)

今回は DirectXSDK(June 2010)のサンプルのひとつであるPNTriangles11サンプルの移植になります。
Direct3Dテッセレーション機能を使う最もシンプルなデモみたいです。
PNTriangles11自体は以前から実装されていたみたいなんですが、
私の中で存在を忘れてまして、「なんだこれ?」と内容確認から始まる羽目になってしまいました。

 

四面体(GIFアニメーション

 

PNTriangles11サンプルはDirectXSDKの持つサンプルのひとつで
directx_sdk.chmDirectX ソフトウェア開発キット)としっかりとした
日本語ドキュメントも用意されていたのですが、今は無くなったみたいでダウンロード出来ないみたいです。
過去のファイルを消すなんて、最もやってはいけない行為だと私は思うんですが
認識間違ってますかねえ・・

今回も大きな問題は無かったのですが、ID3DX11EffectPass::Apply()を実行すると
以下の例外が発生することがありました。

D3D11 CORRUPTION: ID3D11DeviceContext::VSSetShader: Second parameter (ppClassInstances), array index 0 corrupt or unexpectedly NULL. [ MISCELLANEOUS CORRUPTION #14: CORRUPTED_PARAMETER2]

原因は、そのシェーダでは動的シェーダー リンクを使用していたのですが、
ID3DX11EffectInterfaceVariable::SetClassInstance()による初期化をしていなかった為でした。

 

20面体

 

【ゲーム制作】No.14: ハーフランバートライティング(Direct3D11)

今回はライティングエフェクトのひとつであるハーフランバートライティングの実装になります。
ハーフライフで使われていたことで有名ですね。

 

ハーフランバートライティングで検索すれば多数ヒットしますので説明は省略します。
簡単に言うと、このライティングを使うと暗い部分が明るくなるとのことです。

 

以下MMDモデルのハーフランバートライティング画像の比較になります。

MMD側は「自作」

モデルは「初音ミク イミテーションv1ミニマム2.pmx」

 

ハーフランバートライティング=ON(スポットライトx1)

 

ハーフランバートライティング=OFF(スポットライトx1)

 

ハーフランバートライティング=ON(ポイントライトx1)

 

ハーフランバートライティング=OFF(ポイントライトx1)