Matrix Camera

Matrix Camera を使って、2体のモデル(Bone)を関節のように回転します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. Windows10 で CreateModel と MatCamera を組み込んで、2体の Bone を組み合わせて関節のように回転します。
    DirectX9(DirectX Store 以前) では WORLD Matrix を使ってモデルの回転や移動を行っていました。
    そこで変換行列 Matrix を利用して回転や移動を行う方法を試してみました。
    Win10 Model 2 を参照して2体のモデルを描画するプロジェクトを作成して下さい。
    MatCamera のソースコードは、このページの後半に掲載します。
  2. Sample3DSceneRenderer.h にモデル1, モデル2と振り子運動の領域を定義して下さい。
    m_mat[2] はモデルの姿勢を設定する変換行列(Matrix)です。
    #include "CreateModel.h"
    #include "MatCamera.h"
    
        Microsoft::WRL::ComPtr<ID3D11Buffer>        m_vertexBuffer1;
        Microsoft::WRL::ComPtr<ID3D11Buffer>        m_vertexBuffer2;
        Microsoft::WRL::ComPtr<ID3D11Buffer>        m_indexBuffer1;
        Microsoft::WRL::ComPtr<ID3D11Buffer>        m_indexBuffer2;
    
        uint32  m_indexCount1;
        uint32  m_indexCount2;
        CreateModel     *m_CreateModel;
        MatCamera       *camera;
    
        // レンダリング ループで使用する変数。
        DirectX::XMMATRIX   m_mat[2];
        DirectX::XMMATRIX   r_mat;
        float       dt = 0.05f;
        float       rt = 0.0f;
    
  3. Sample3DSceneRenderer.cpp の CreateDeviceDependentResources() 関数の中で2体のモデルを生成します。
    CreateBone() は Version_2 で追加された Bone のモデルです。
    多角錐の回転の中心はモデルの中央に設定されていますが、Bone では関節に相当する蓋の部分に設定されています。
    一番目のパラメータは Bone の長さで、Bone を骨に見立てて組合せて回転することを想定しています。
        // 両方のシェーダーの読み込みが完了したら、メッシュを作成します。
        auto createCubeTask = (createPSTask && createVSTask).then([this] () {
            m_CreateModel = new CreateModel(m_deviceResources);
            m_CreateModel->CreateBone(5.0f, &m_vertexBuffer1, &m_indexBuffer1, &m_indexCount1);
            m_CreateModel->CreateBone(3.0f, &m_vertexBuffer2, &m_indexBuffer2, &m_indexCount2);
        });
    
  4. Sample3DSceneRenderer の Constructor で MatCamera を生成して、モデルの姿勢(m_mat[2])を初期化します。
    Sample3DSceneRenderer::Sample3DSceneRenderer(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
        m_loadingComplete(false),
        m_degreesPerSecond(45),
        m_indexCount1(0),
        m_indexCount2(0),
        m_tracking(false),
        m_deviceResources(deviceResources)
    {
        CreateDeviceDependentResources();
        CreateWindowSizeDependentResources();
        camera = new MatCamera(m_deviceResources);
        m_mat[0] = XMMatrixIdentity();
        m_mat[1] = XMMatrixIdentity();
        r_mat = XMMatrixIdentity();
    }
    
  5. Rotate() 関数ではモデル1を XMMatrixTranslation() で振り子運動をさせてみました。
    モデル2はY軸で自動的に回転します。
    * XMMatrixTranslation(0.0f, -5.0f, 0.0f); でモデル2のY座標を -5.0 移動します。
    これによりモデル1の先端でモデル2が回転するようになります。
    void Sample3DSceneRenderer::Rotate(float radians)
    {
        if (rt > 5.0f)  dt = -0.05f;
        if (rt < -5.0f) dt = 0.05f;
        rt += dt;
        m_mat[0] = XMMatrixTranslation(rt, 0.0f, 0.0f);
        //m_mat[0] = XMMatrixTranslation(0.0f, rt, 0.0f);
        //m_mat[0] = XMMatrixTranslation(0.0f, 0.0f, rt);
        m_mat[1] = XMMatrixRotationY(radians) * XMMatrixTranslation(0.0f, -5.0f, 0.0f);
    }
    
  6. Render() 関数でモデル1に続いてモデル2を描画します。
    モデル2では、モデル2とモデル1の変換行列の積をパラメータとして渡します。
    void Sample3DSceneRenderer::Render()
    {
        if (!m_loadingComplete) {   return;  }
        auto context = m_deviceResources->GetD3DDeviceContext();
        context->VSSetShader(m_vertexShader.Get(), nullptr, 0);
        context->PSSetShader(m_pixelShader.Get(), nullptr, 0);
        context->VSSetConstantBuffers(0, 1, m_constantBuffer.GetAddressOf());
        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        context->IASetInputLayout(m_inputLayout.Get());
    
        UINT stride = sizeof(VertexPositionColor);
        UINT offset = 0;
    
        //☆モデル1を描画
        camera->SetEnv(&m_mat[0]);
        m_constantBufferData.projection = camera->m_projection;
        m_constantBufferData.view = camera->m_view;
        m_constantBufferData.model = camera->m_model;
    
        context->UpdateSubresource(m_constantBuffer.Get(), 0, NULL, &m_constantBufferData, 0, 0);
        context->IASetVertexBuffers(0, 1, m_vertexBuffer1.GetAddressOf(), &stride, &offset);
        context->IASetIndexBuffer(m_indexBuffer1.Get(), DXGI_FORMAT_R16_UINT, 0);
        context->DrawIndexed(m_indexCount1, 0, 0);
    
        //☆モデル2を描画
        DirectX::XMMATRIX   w;
        w = m_mat[1] * m_mat[0];
        camera->SetEnv(&w);
        m_constantBufferData.projection = camera->m_projection;
        m_constantBufferData.view = camera->m_view;
        m_constantBufferData.model = camera->m_model;
    
        context->UpdateSubresource(m_constantBuffer.Get(), 0, NULL, &m_constantBufferData, 0, 0);
        context->IASetVertexBuffers(0, 1, m_vertexBuffer2.GetAddressOf(), &stride, &offset);
        context->IASetIndexBuffer(m_indexBuffer2.Get(), DXGI_FORMAT_R16_UINT, 0);
        context->DrawIndexed(m_indexCount2, 0, 0);
    }
    
  7. プログラムを実行するとモデル1の回転に合わせてモデル2が追随します。
    他の組み合わせも試してみて下さい。

Rotation で回転

  1. モデル1とモデル2の両方を Rotation で回転します。
    当初この組み合わせがうまく動かず「試行錯誤」の連続でしたが、ようやく目途が立ちました。
  2. Rotate() 関数ではモデル1をX軸で自動的に回転します。
    同時に逆回転した変換行列を r_mat に保存します。
    モデル2はY軸で自動的に回転します。
    * XMMatrixTranslation(0.0f, -5.0f, 0.0f); でモデル2のY座標を -5.0 移動します。
    void Sample3DSceneRenderer::Rotate(float radians)
    {
        m_mat[0] = XMMatrixRotationX(radians);
        r_mat = XMMatrixRotationX(-radians);
        m_mat[1] = XMMatrixRotationY(radians) * XMMatrixTranslation(0.0f, -5.0f, 0.0f);
    }
    
  3. Render() 関数のモデル2描画では、モデル2変換行列にモデル1の逆回転を掛け合わせます。
    非常に不可解なのですが、これでモデル1に追随してモデル2が回転するので試してみて下さい。 (^_^;)
    どこかミスっているのでしょうか? それとも DirectX のバグでしょうか?(ゝω・)v
    void Sample3DSceneRenderer::Render()
    {
        ・・・
    
        //☆モデル2を描画
        DirectX::XMMATRIX   w;
        //w = m_mat[1] * m_mat[0];
        w = m_mat[1] * r_mat;
        camera->SetEnv(&w);
        m_constantBufferData.projection = camera->m_projection;
        m_constantBufferData.view = camera->m_view;
        m_constantBufferData.model = camera->m_model;
    
        context->UpdateSubresource(m_constantBuffer.Get(), 0, NULL, &m_constantBufferData, 0, 0);
        context->IASetVertexBuffers(0, 1, m_vertexBuffer2.GetAddressOf(), &stride, &offset);
        context->IASetIndexBuffer(m_indexBuffer2.Get(), DXGI_FORMAT_R16_UINT, 0);
        context->DrawIndexed(m_indexCount2, 0, 0);
    
  4. MatCamera では SetEnv() でモデルの姿勢を計算します。
    XMMATRIX *m_mat で指定されたモデルの姿勢データを m_projection, m_view, m_model に設定します。
    移動情報を含んだ Matrix をそのまま m_model に代入出来れば苦労は無いのですが、移動情報を含めるとモデルが大きく変形してしまいます。
    そこで回転情報と移動情報を分けて処理します。
    移動情報は XMMATRIX の4行目の1列目から3列目に格納されています。
    モデルの移動は m_view のカメラの座標と向きを変更して対応します。
    モデルの回転は、移動情報を外して m_model に代入します。
    void MatCamera::SetEnv(DirectX::XMMATRIX *mat)
    {
        DirectX::XMMATRIX   w;
        w = mat[0];
        Camera(w.r->m128_f32[12], w.r->m128_f32[13], w.r->m128_f32[14]);
        w.r->m128_f32[12] = 0.0f;
        w.r->m128_f32[13] = 0.0f;
        w.r->m128_f32[14] = 0.0f;
        XMStoreFloat4x4(&m_model, w);
    }
    
  5. プログラムを実行するとモデル1の回転に合わせてモデル2が追随します。
    他の組み合わせも試してみて下さい。
    モデルが2体のときはうまく追随出来たのですが、3体になると思うように動きません。
    またカメラが回転するためか、光源の方向がめちゃくちゃになります。

MatCamera のソースコード

  1. MatCamera.h のソースコードです。
    // Matrix Camera Header File  前田 稔
    #pragma once
    #include "..\Common\DeviceResources.h"
    struct float3
    {
        float   x;
        float   y;
        float   z;
    };
    
    class MatCamera
    { public:
        float3 f3(float x, float y, float z);
        MatCamera(std::shared_ptr<DX::DeviceResources>& deviceResources);
        void Projection();
        void Camera(DirectX::XMVECTOR eye, DirectX::XMVECTOR at, DirectX::XMVECTOR up);
        void Camera(float dx, float dy, float dz);
        void SetEnv(DirectX::XMMATRIX *mat);
    
        // 3Dモデルの描画環境
        DirectX::XMFLOAT4X4 m_projection;
        DirectX::XMFLOAT4X4 m_view;
        DirectX::XMFLOAT4X4 m_model;
    
        // カメラの姿勢(SetCamera で規定値から修正)
        DirectX::XMVECTOR   m_eye = { 0.0f, 0.7f, 20.0f, 0.0f };
        DirectX::XMVECTOR   m_at = { 0.0f, -0.1f, 0.0f, 0.0f };
        DirectX::XMVECTOR   m_up = { 0.0f, 1.0f, 0.0f, 0.0f };
    
    private:
        std::shared_ptr<DX::DeviceResources> m_deviceResources;
    };
    
  2. MatCamera.cpp のソースコードです。
    /***************************************/
    /* Matrix Camera Program File  前田 稔 */
    /***************************************/
    #pragma once
    
    #include "pch.h"
    #include "..\Common\DirectXHelper.h"
    #include "MatCamera.h"
    
    using namespace DirectX;
    using namespace Windows::Foundation;
    
    float3 MatCamera::f3(float x, float y, float z)
    {
        float3 wf3;
        wf3.x = x;
        wf3.y = y;
        wf3.z = z;
        return wf3;
    }
    
    // Constructor で規定値に設定
    MatCamera::MatCamera(std::shared_ptr<DX::DeviceResources>& deviceResources)
    {
        m_deviceResources = deviceResources;
        Projection();
        XMStoreFloat4x4(&m_view, XMMatrixTranspose(XMMatrixLookAtRH(m_eye, m_at, m_up)));
        XMStoreFloat4x4(&m_model, XMMatrixIdentity());
    }
    
    // カメラの Projection を設定
    void MatCamera::Projection()
    {
        Size outputSize = m_deviceResources->GetOutputSize();
        float aspectRatio = outputSize.Width / outputSize.Height;
        float fovAngleY = 70.0f * XM_PI / 180.0f;
        if (aspectRatio < 1.0f)
        {   fovAngleY *= 2.0f;  }
        XMMATRIX perspectiveMatrix = XMMatrixPerspectiveFovRH(
        fovAngleY, aspectRatio, 0.01f, 100.0f);
    
        XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();
        XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);
        XMStoreFloat4x4(&m_projection, XMMatrixTranspose(perspectiveMatrix * orientationMatrix));
    }
    
    // m_eye, m_at, m_up を規定値から変更
    void MatCamera::Camera(DirectX::XMVECTOR eye, DirectX::XMVECTOR at, DirectX::XMVECTOR up)
    {
        m_eye = eye;
        m_at = at;
        m_up = up;
        XMStoreFloat4x4(&m_view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, m_up)));
    }
    
    // 規定値からカメラを操作してモデルを平行移動
    void MatCamera::Camera(float dx, float dy, float dz)
    {
        DirectX::XMVECTOR   eye;
        DirectX::XMVECTOR   at;
        eye = m_eye;
        at = m_at;
        eye.m128_f32[0] += dx;
        eye.m128_f32[1] += dy;
        eye.m128_f32[2] += dz;
        at.m128_f32[0] += dx;
        at.m128_f32[1] += dy;
        at.m128_f32[2] += dz;
        XMStoreFloat4x4(&m_view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, m_up)));
    }
    
    // モデルの描画環境を設定
    void MatCamera::SetEnv(DirectX::XMMATRIX *mat)
    {
        DirectX::XMMATRIX   w;
        w = mat[0];
        Camera(w.r->m128_f32[12], w.r->m128_f32[13], w.r->m128_f32[14]);
        w.r->m128_f32[12] = 0.0f;
        w.r->m128_f32[13] = 0.0f;
        w.r->m128_f32[14] = 0.0f;
        XMStoreFloat4x4(&m_model, w);
    }
    

超初心者のプログラム入門(DirectX Store)