406.インスタンス描画

 この項ではインスタンス描画について説明します。
 インスタンス描画とは一度の描画で複数のオブジェクトを描画する方法で、ワールド行列だけが違う大量のオブジェクトを同時に描画することを可能にするテクニックです。
 BaseCrossではシンプルなシェーダセットおいてのみインスタンス描画が可能です。(リアルシェーダセットでは現時点では対応してません)。
 FullSample406を実行すると以下のような画面が現れます。
 このサンプルはリリースモードで実行してください。

 

図0406a

 実行すると、ステージ上には大量のボックスが回転しています。プレイヤーを動かすとそれぞれと衝突判定をして、上に乗ることができます。上に乗ると親子関係になり、ボックスの回転に合わせてプレイヤーが台の上で回転します。
 インスタンス描画を実装するためにPNTStaticInstanceDraw描画コンポーネントを使用しています。
 この○○StaticInstanceDrawPNT(位置、法線、テクスチャ)のほかにPC(位置、カラー)、PT(位置、テクスチャ)、PC(位置、カラー)があり、それぞれの頂点フォーマットに対応したものになります。
 インスタンス描画コンポーネントの実装方法はいくつかありますが、このサンプルでは描画するためだけのゲームオブジェクトを作成し、衝突判定や親子関係など(シャドウマップも)はそれぞれのオブジェクトが行います。
 衝突判定をしないのであればもっと最適化ができると思います。

描画を行うオブジェクト

 まずインスタンス描画をするオブジェクトですがCharacter.h/cppRollingBoxGroupクラスがあります。
 このクラスではPNTStaticInstanceDraw描画コンポーネントを実装します。以下はRollingBoxGroup::OnCreate()関数です。
//初期化
void RollingBoxGroup::OnCreate() {
    auto PtrTrans = GetComponent<Transform>();
    PtrTrans->SetScale(Vec3(1.0f, 1.0f, 1.0f));
    Quat Qt;
    Qt.identity();
    PtrTrans->SetQuaternion(Qt);
    PtrTrans->SetPosition(Vec3(0, 0, 0));
    //影をつける(自己影用のシェーダを使うための理由)
    auto ShadowPtr = AddComponent<Shadowmap>();
    //描画コンポーネント
    auto PtrDraw = AddComponent<PNTStaticInstanceDraw>();
    PtrDraw->SetOwnShadowActive(true);
    PtrDraw->SetMeshResource(L"DEFAULT_CUBE");
    PtrDraw->SetTextureResource(L"SKY_TX");
    //毎ターン毎に行列をクリアする
    PtrDraw->SetAutoClearMatrixVec(true);
}
 インスタンス描画のメカニズムはワールド行列コンスタントバッファとは別のスロットに入れてパイプラインを形成することです。具体的には○○StaticInstanceDrawは内部にワールド行列の配列を持ち、をの配列をスロットに流します。
 クリエイト時のワールド行列のままで良ければ赤くなっている部分のフラグは必要ありません。

おのおののオブジェクト

 続いて、RollingBoxクラスですが、以下はRollingBox::OnCreate()関数です。
void RollingBox::OnCreate() {
    auto PtrTransform = GetComponent<Transform>();

    PtrTransform->SetScale(m_Scale);
    PtrTransform->SetRotation(m_Rotation);
    PtrTransform->SetPosition(m_Position);

    //衝突判定
    auto PtrObb = AddComponent<CollisionObb>();
    PtrObb->SetFixed(true);

    //影をつける
    auto ShadowPtr = AddComponent<Shadowmap>();
    ShadowPtr->SetMeshResource(L"DEFAULT_CUBE");

    auto GroupPtr = GetStage()->GetSharedGameObject<RollingBoxGroup>(L"RollingBoxGroup");
    auto DrawPtr = GroupPtr->GetComponent<PNTStaticInstanceDraw>();
    DrawPtr->AddMatrix(PtrTransform->GetWorldMatrix());

    AddTag(L"MoveBox");

}
 見てわかるとおりこちらは描画コンポーネントは実装されていません。赤くなっているところはグループオブジェクトを取り出して、そのPNTStaticInstanceDrawコンポーネントに自分の行列を追加しています。
 またAddTag(L"MoveBox");によりプレイヤーが上に乗ったときに親子関係を設定できるようにしています。

更新処理

 更新処理はRollingBoxクラスのみ記述があります。
void RollingBox::OnUpdate() {
    float ElapsedTime = App::GetApp()->GetElapsedTime();

    auto PtrTransform = GetComponent<Transform>();
    auto Qt = PtrTransform->GetQuaternion();
    Quat QtSpan(Vec3(0, 1, 0), ElapsedTime * m_RotParam);
    Qt *= QtSpan;
    PtrTransform->SetQuaternion(Qt);
    auto GroupPtr = GetStage()->GetSharedGameObject<RollingBoxGroup>(L"RollingBoxGroup");
    auto DrawPtr = GroupPtr->GetComponent<PNTStaticInstanceDraw>();
    DrawPtr->AddMatrix(PtrTransform->GetWorldMatrix());
}
 ここでは回転させて結果の行列をグループオブジェクトに渡しています。

ステージでのオブジェクトの追加

 最後に配置方法ですが、GameStage.cppGameStage::CreateRollingBox()で配置します。
void GameStage::CreateRollingBox() {
    auto GroupPtr = AddGameObject<RollingBoxGroup>();
    //シェアオブジェクトに設定
    SetSharedGameObject(L"RollingBoxGroup", GroupPtr);
    Vec3 Scale(1.25f, 0.5f, 1.25f);
    Vec3 Rot(0.0f, 0.0f, 0.0f);
    Vec3 Pos;

    //配列の初期化
    vector< vector<Vec3> > Vec;
    //奥側のオブジェクト
    for (int x = -20; x < 20; x+= 2) {
        for (int z = 5; z < 20; z+= 2) {
            Pos = Vec3((float)x, 0.25f, (float)z);
            vector<Vec3> temp = { Scale, Rot, Pos };
            Vec.push_back(temp);
        }
    }
    //手前側のオブジェクト
    for (int x = -20; x < 20; x += 2) {
        for (int z = -5; z > -20; z -= 2) {
            Pos = Vec3((float)x, 0.25f, (float)z);
            vector<Vec3> temp = { Scale, Rot, Pos };
            Vec.push_back(temp);
        }
    }
    //オブジェクトの作成
    float RotParam = 1.0f;
    for (auto v : Vec) {
        //乱数で回転係数を0.5から1.5の間に設定
        RotParam = Util::RandZeroToOne(true) + 0.5f;
        AddGameObject<RollingBox>(v[0], v[1], v[2], RotParam);
    }
}
 ここではまずグループオブジェクトを作成し、そのあと大量のRollingBoxクラスを作成しています。乱数により回転速度にばらつきを設けています。

 このようにシンプルシェーダのみという制限付きではありますがインスタンス描画により描画速度は劇的に速くなります。リアルシェーダの場合は、ライティングにワールド行列以外も必要とするので、なかなかこの手法は使えませんが、シェーダでライティングを細かく記述するなど、独自の手法を使えば可能と思います。