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

 

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

 

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

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

 

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

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

 

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

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

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

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

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

 

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


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

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

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

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


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

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

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


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

 

 

// 関数名    draw_to_uniformshadowmap_as_directionallight
// 戻り値: HOUZMETHOD_VD                  実装は「void __fastcall」です。
// 引数:  p_draw_pkg_conter_pack_in_view シャドウマップに影を落とすボリュームボックスオブジェクトのコンテナです。
//                                            オブジェクトはビュー視錐台内にいることが条件になります。
//            p_light                            ディレクショナルライトのオブジェクト。ライトの方向を使用します。
HOUZMETHOD_VD shadowmap_effect_t::draw_to_uniformshadowmap_as_directionallight(

    const draw_object_pkg_conter_pack_t&    p_draw_pkg_conter_pack_in_view,    // テスト用。後で消すこと。
    const dirlight_ptr_t&                    p_light,
    const ID3D11DeviceContextPtr&            device_context) const
{
    // 使用するシャドウマップの番号を、
    // 初期値である無効(-1)に設定します。
    p_light->set_shadowmap_index(-1);

    // ビュー視錐台内にボリュームがいない場合、
    // 影は発生しないので、これで終了となります。
    if( p_draw_pkg_conter_pack_in_view.empty())
        return;

    // 使用するシャドウマップの番号を取得します。
    // シャドウマップは現在8枚まで用意されています。
    // 全て使用済みであり取得出来なかった場合、これで終了となります。
    const int_t    shadowmap_index    = m_p_shadowmap_resource->release_index();
    if( shadowmap_index < 0)
        return;

    // ビュー ボリュームを決定する2つのベクトル(minベクトルとmaxベクトル)の変数です。
    // この値を決定するのが目的になります。
    cvector3_t        v_shadow_volume_min_all    = zero();
    cvector3_t        v_shadow_volume_max_all    = zero();

    // ライトの姿勢角度を求めます。
    // これはデバックで使用する変数ですので、無くてもかまいません。
    extern int    gui_f_dlight_axis_x;
    extern int    gui_f_dlight_axis_y;
    extern int    gui_f_dlight_axis_z;
    float_t    dlight_axis_x    = gui_f_dlight_axis_x - 360.0;
    float_t    dlight_axis_y    = gui_f_dlight_axis_y - 360.0;
    float_t    dlight_axis_z    = gui_f_dlight_axis_z - 360.0;
    const rmatrix4x4pf_t    dlight_rot_x    = rotate_x( to_radian(dlight_axis_x));
    const rmatrix4x4pf_t    dlight_rot_y    = rotate_y( to_radian(dlight_axis_y));
    const rmatrix4x4pf_t    dlight_rot_z    = rotate_z( to_radian(dlight_axis_z));
    const rmatrix4x4pf_t    dlight_rot_zxy    = mul( mul( dlight_rot_z, dlight_rot_x), dlight_rot_y);

    const cvector3_t        v_light_direction    = mul_normal( dlight_rot_zxy, p_light->get_direction());    // ライトの方向。
    //const cvector3_t        v_light_direction    = p_light->get_direction();    // ライトの方向。
    const cvector3_t        v_light_position    = zero();                    // ディレクショナルライトの為、位置はありません。
    //const rmatrix4x4pf_t    m_light_basis        = mul( dlight_rot_zxy, to_orthonormal_basis( p_light->get_direction()));    // ライトの姿勢。Z軸のみOK
    //const rmatrix4x4pf_t    m_light_basis        = mul( to_orthonormal_basis( p_light->get_direction()), dlight_rot_zxy);    // ライトの姿勢。Y軸のみOK
    const rmatrix4x4pf_t    m_light_basis        = to_orthonormal_basis( p_light->get_direction());    // ライトの姿勢。

    const rmatrix4x4pf_t    m_light_view        = to_view( v_light_position,  m_light_basis);

    // ビュー ボリュームを決定する2つのベクトル(minベクトルとmaxベクトル)を求めます。
    // コンテナであるp_draw_pkg_conter_pack_in_viewに
    // 登録されている全てのボリュームに対して行います。
    for( size_t p = 0; p < draw_object_pkg_conter_pack_t::num_conter_types; p++)
    {
    for (draw_object_pkg_ptr_set_t::const_iterator i_draw_pkg = p_draw_pkg_conter_pack_in_view[p].begin(); i_draw_pkg != p_draw_pkg_conter_pack_in_view[p].end(); ++i_draw_pkg)
    {
        if( (*i_draw_pkg)->p_volume == NULL)
            continue;

        // コンテナよりボリュームオブジェクトを取得します。
        const bounding_volume_t&    volume    = *(*i_draw_pkg)->p_volume;

        // ボリュームの最適な半径(effective_radius)を求めます。
        // ボリュームの形状(球、ボックス、楕円体など)によって
        // 最適な半径が計算され出力されます。
        const cvector3_t    v_volume_radius    = initv(
            get_effective_radius( transpose(m_light_basis(0)), volume),
            get_effective_radius( transpose(m_light_basis(1)), volume),
            get_effective_radius( transpose(m_light_basis(2)), volume));

        // ボリュームのライト空間における位置を求めます。
        const cvector3_t    v_volume_position    = mul_coord( m_light_view, volume.get_position());

        // ボリュームの調整値を求めます。
        // これはデバックで使用する変数ですので、無くてもかまいません。
        extern int    gui_f_orthogonal_matrix_min_x;
        extern int    gui_f_orthogonal_matrix_max_x;
        extern int    gui_f_orthogonal_matrix_min_y;
        extern int    gui_f_orthogonal_matrix_max_y;
        extern int    gui_f_orthogonal_matrix_min_z;
        extern int    gui_f_orthogonal_matrix_max_z;

        //const cvector3_t    v_volume_adjust_min    = cvector3_t( initv(
        //    gui_f_orthogonal_matrix_min_x,
        //    gui_f_orthogonal_matrix_min_y,
        //    gui_f_orthogonal_matrix_min_z)) * rcp( 100.0);

        //const cvector3_t    v_volume_adjust_max    = cvector3_t( initv(
        //    gui_f_orthogonal_matrix_max_x,
        //    gui_f_orthogonal_matrix_max_y,
        //    gui_f_orthogonal_matrix_max_z)) * rcp( 100.0);

        const cvector3_t    v_volume_adjust_min    = cvector3_t( initv(
            gui_f_orthogonal_matrix_min_x,
            gui_f_orthogonal_matrix_min_y,
            gui_f_orthogonal_matrix_min_z));

        const cvector3_t    v_volume_adjust_max    = cvector3_t( initv(
            gui_f_orthogonal_matrix_max_x,
            gui_f_orthogonal_matrix_max_y,
            gui_f_orthogonal_matrix_max_z));

        // ボリュームのバイアス値を求めます。
        const float_t    v_volume_bias    = 1.0 / 2048.0;

        // ビュー ボリュームを決定する2つのベクトル(minベクトルとmaxベクトル)を求めます。
        const cvector3_t    v_shadow_volume_min_any    = v_volume_position - v_volume_bias - v_volume_radius + v_volume_adjust_min;
        const cvector3_t    v_shadow_volume_max_any    = v_volume_position + v_volume_bias + v_volume_radius + v_volume_adjust_max;

        // 全てのボリュームのminベクトルとmaxベクトルを求める場合のコードです。
        // 通常はこちらを使用してください。
        v_shadow_volume_min_all    = min( v_shadow_volume_min_all, min( v_shadow_volume_min_any, v_shadow_volume_max_any));
        v_shadow_volume_max_all    = max( v_shadow_volume_max_all, max( v_shadow_volume_min_any, v_shadow_volume_max_any));

        //// ボリュームが複数あった場合に最適化する場合のコードです。
        //// テストケースです。
        //// コードに問題がある為、通常は使用しません。
        //const bool    b_receive_shadow    = volume.get_radius().x >= 10.0;
        //if(!b_receive_shadow)
        //{
        //    v_shadow_volume_min_all    = min( v_shadow_volume_min_all, min( v_shadow_volume_min_any, v_shadow_volume_max_any));
        //    v_shadow_volume_max_all    = max( v_shadow_volume_max_all, max( v_shadow_volume_min_any, v_shadow_volume_max_any));
        //}
        //else
        //{
        //    v_shadow_volume_min_all    = max( v_shadow_volume_min_all, max( v_shadow_volume_min_any, v_shadow_volume_max_any));
        //    v_shadow_volume_max_all    = min( v_shadow_volume_max_all, min( v_shadow_volume_min_any, v_shadow_volume_max_any));
        //}
    }
    }

    // ビュー ボリュームを決定する2つのベクトル(minベクトルとmaxベクトル)から
    // 射影行列を作成します。

    // パースペクティブ射影行列を作成する場合のコードです。
    // テストケースです。
    //perspective_view_t light_viewobj_test    = perspective_view_t(
    //    m_light_basis,
    //    initv(0, 5, 5),
    //    zero(),
    //    float_t(pi()) * 0.625f,    // 角度 = 112.500000
    //    1.0f,
    //    1.0f,
    //    100.0f);

    // 正射法の射影行列を作成する場合のコードです。
    // 通常はこちらを使用してください。
    orthogonal_view_t    light_viewobj_test    = orthogonal_view_t(
    m_light_basis,
    zero(),
    zero(),
    v_shadow_volume_min_all,
    v_shadow_volume_max_all);

    //// 以降は視錐台(light_frustum)を3Dで表示させる場合のサンプルコードです。
    //// テストケースです。ここから

    // 視錐台(light_frustum)表示がONでなければキャンセル
    extern bool    gui_b__bounding_volumes;
    extern bool    gui_b__light_frustum;
    if( !gui_b__bounding_volumes && gui_b__light_frustum)
    {
        //// 最大頂点と最小頂点の2頂点から視錐台オブジェクトを作成します。
        frustum_t    viewfrustum_test(    light_viewobj_test.get_world_transform_matrix(),
                                        light_viewobj_test.get_min_corner(),
                                        light_viewobj_test.get_max_corner());

        // 視錐台オブジェクトから視錐台の頂点(8頂点)を作成します。
        cvector3_collection frustum_vertices;    // 前左下,前左上,前右下,前右上,後左下,後左上,後右下,後右上
        frustum_vertices.resize(8);
        houz::frustum_t::compute_vertices( &*frustum_vertices.begin(),    viewfrustum_test.face);

        //////// 視錐台の頂点(8頂点)から射影行列管理用のオブジェクトを作成します。
        //////// この処理が重すぎるかも。。リアルタイムではきついか?
        //////const perspective_view_t    light_viewobj_test2    = perspective_view_t(
        //////    frustum_vertices,
        //////    zero(),                // apex_pos
        //////    v_light_direction,    // front
        //////    to_radian( 30.0f),    // fov_lower_limit
        //////    to_radian( 90.0f),    // fov_upper_limit
        //////    0.0f,                // near_limit ※現在作成するか検討中です。(いらないような・・)
        //////    100.0f,                // far_limit ※現在作成するか検討中です。(いらないような・・)
        //////    true);                // move_backward
        //////        

        //////// 射影行列管理用のオブジェクトから改めて視錐台オブジェクトを作成し、視錐台の頂点(8頂点)を作成します。
        //////frustum_t    viewfrustum_test2(    light_viewobj_test2.get_world_transform_matrix(),
        //////                                light_viewobj_test2.get_fov_y(),
        //////                                light_viewobj_test2.get_aspect(),
        //////                                light_viewobj_test2.get_near_range(),
        //////                                light_viewobj_test2.get_far_range());

        //////houz::frustum_t::compute_vertices( &*frustum_vertices.begin(),    viewfrustum_test2.face);

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

        //// 視錐台の頂点(8頂点)をバーテックスバッファにマップします。
        get_D3D11_geometric_shape_effect_ptr()->set_viewfrustum_vertices( &*frustum_vertices.begin());

        // 視錐台の表示を要求します。

        color_t    color(1,0,0,1);    // 視錐台の頂点色を赤に設定します。
        color.a    = 0.5;            // 視錐台を半透明に設定します。

        get_D3D11_geometric_shape_effect_ptr()->push_draw_shape_order( shape_order_type_viewfrustum, identity(), color, false);

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

    // 視錐台(light_frustum)を3Dで表示させる場合のサンプルコードです。
    // テストケースです。ここまで
    }

    //// シャドウマップ行列を作成します。
    const rmatrix4x4pf_t    light_view        = light_viewobj_test.get_view_matrix();
    const rmatrix4x4pf_t    light_proj        = light_viewobj_test.get_proj_matrix();
    const rmatrix4x4pf_t    light_viewproj    = mul( light_proj, light_view);

    // シャドウマップにボリュームを描画し、シャドウマップを完成させます。
    m_h_view_proj->SetMatrixTranspose( (const float32_t*)light_viewproj.get_block_ptr(0));
    m_h_shadowmap_index->SetInt(shadowmap_index);

    draw_to_lightspace_shadowmap( p_draw_pkg_conter_pack_in_view, shadowmap_index, device_context);

    // シャドウマップ行列用のバイアスを適用させます。
    extern int    gui_f_scale_bias;
    //const float32_t    bias    =  -1.0f / (light_viewobj_test.get_max_corner().z - light_viewobj_test.get_min_corner().z);
    //const float32_t    bias    =  -1.0f / (light_viewobj_test.get_max_corner().z - light_viewobj_test.get_min_corner().z) + float32_t(gui_f_scale_bias) / 100.0f;
    //const float32_t    bias    =  -1.0f / (+100.0f - 5.0f);
    //const float32_t    bias    =  -2.0f / 65536.0f;
    const float32_t    bias    =  -2.0f / 2048.0f;

    const rmatrix4x4pf_t    scale_bias    = initm(    0.5f,    0,        0,    0.5f,
                                                    0,        -0.5f,    0,    0.5f,
                                                    0,        0,        1,    bias,
                                                    0,        0,        0,    1);
    const rmatrix4x4pf_t to_shadowmap_space    = mul( scale_bias, light_viewproj);

    // シャドウマップ行列とシャドウマップ番号を保存します。
    // これらは影の描画時に使用されます。
    m_p_shadowmap_resource->m_to_shadowmap_space[shadowmap_index]    = to_shadowmap_space;

    p_light->set_shadowmap_index(shadowmap_index);
}