Win10 XLoader

Win10 XLoader

コーンの座標をプログラムで計算して描画します。
Windows8 の XLoader をリメークして Windows10 で作成します。

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

Win10 XLoader

  1. DirectX Windows8.1 のプロジェクトは Windows10 で動いたのですが、Windows8 のプロジェクトは動きませんでした。
    XLoader Class を使って X-FILE をプロジェクトに組み込むと、解り易くスマートに組み込むことが出来ます。
    XLoader は頂点データの形式(頂点+色, 頂点+法線, 頂点+法線+テクスチャ など)に合わせて作成しています。
    Windows8 の XLoader には「頂点+法線+テクスチャ+色」のモデルや複数枚のテクスチャを貼り付けるプロジェクトを作成中でした。
    そこで Windows10 で XLoader Class をリメークすることにしました。
  2. Windows8 の XLoader Class と Windows10 のプログラムを比べて最も気になることは float3 でしょうか?
    【Windows10】
    struct VertexPosition
    {
        DirectX::XMFLOAT3 pos;
        DirectX::XMFLOAT3 norm;
    };
    
    【Windows8】
    struct VertexPosition
    {
        float3 pos;   // position
        float3 norm;  // norm
    };
    
    基本的に両者は同じですが Windows10 ではエラーになるようです。
    float3 は BasicMath.h で構造体として定義されています。
    Windows10 DirectX Library に掲載しているので参考にして下さい。
    #include "BasicMath.h"
    
    struct float3
    {
        float  x;
        float  y;
        float  z;
    };
    
  3. Windows10 の自動生成プロジェクトでは App1Main からモデルを描画する Class(Sample3DSceneRenderer) を呼び出しますが、 XLoader Class では Model Class を経由して XLoader Class を呼び出します。
    実質的に Model は Sample3DSceneRenderer の名前が変わったものと思って下さい。
      System⇒App.cpp⇒App1Main⇒Model⇒XLoader
    
    これによりモデルが変更された場合でも Model Class はそのままで XLoader Class の修正だけで済ますことが出来ます。
    Sample3DSceneRenderer をそのまま組み込むより解り易くなりますが、モデルデータの作成(X-FILE の解析)は必要であり初心者には難しいかも知れません。
    但し、カメラの設定は Model Class で行っています。
  4. Sample3DSceneRenderer.h(.cpp) をプロジェクトから削除して、Model.h(.cpp) と XLoader.h(.cpp) をプロジェクトに加えて下さい。
    Model Class の名前に違和感を感じるなら Sample3DSceneRenderer の名前をそのまま使用しても構いません。

XLoader Class

  1. それでは具体的に Cone のメッシュをプログラムで設定するプロジェクトを例にして説明します。
    頂点座標を定義する ShaderStructures.h です。
    この中で BasicMath.h を取り込むのが良いようです。
    シェーダは Win10 Shader から「頂点座標+法線」の物を使って下さい。
    #pragma once
    #include "BasicMath.h"
    
    namespace App1
    {
        // MVP マトリックスを頂点シェーダーに送信するために使用する定数バッファー。
        struct ModelViewProjectionConstantBuffer
        {
            DirectX::XMFLOAT4X4 model;
            DirectX::XMFLOAT4X4 view;
            DirectX::XMFLOAT4X4 projection;
        };
    
        // 頂点シェーダーへの頂点ごとのデータの送信に使用します。
        struct VertexPosition
        {
            float3 pos;   // position
            float3 norm;  // norm
        };
    }
    
  2. App1Main.cpp から Model Class の呼び出し方は Sample3DSceneRenderer の場合と同じです。
    App1Main.h(.cpp) に記述されている "Sample3DSceneRenderer" を "Model" に変更して下さい。
    【Sample3DSceneRenderer Class を呼び出す】
        m_sceneRenderer = std::unique_ptr<Sample3DSceneRenderer>(new Sample3DSceneRenderer(m_deviceResources));
    
    【Model Class を呼び出す】
        m_sceneRenderer = std::unique_ptr<Model>(new Model(m_deviceResources));
    
  3. Model.h のソースコードです。
    モデルの生成(X-FILE の入力)関係は XLoader Class に任せます。
    #pragma once
    
    #include "..\Common\DeviceResources.h"
    #include "..\Common\StepTimer.h"
    #include "ShaderStructures.h"
    #include "XLoader.h"
    
    namespace App1
    {
        // このサンプル レンダリングでは、基本的なレンダリング パイプラインをインスタンス化します。
        class Model
        {
        public:
            Model(const std::shared_ptr<DX::DeviceResources>& deviceResources);
            void CreateDeviceDependentResources();
            void CreateWindowSizeDependentResources();
            void ReleaseDeviceDependentResources();
            void Update(DX::StepTimer const& timer);
            void Render();
            void StartTracking();
            void TrackingUpdate(float positionX);
            void StopTracking();
            bool IsTracking() { return m_tracking; }
    
        private:
            void Rotate(float radians);
            std::unique_ptr<XLoader> xloader;
    
        private:
            // デバイス リソースへのキャッシュされたポインター。
            std::shared_ptr<DX::DeviceResources> m_deviceResources;
    
            ModelViewProjectionConstantBuffer   m_constantBufferData;
            float   m_degreesPerSecond;
            bool    m_tracking;
        };
    }
    
  4. Model.cpp のソースコードです。
    Model Class から XLoader Class を呼び出します。
    xloader = std::unique_ptr<XLoader>(new XLoader(m_deviceResources)); で XLoader を生成します。
    xloader->Load(); でモデルを設定します。
    xloader->Draw(m_constantBufferData); でモデルを描画します。
    void Model::CreateDeviceDependentResources()
    {
        xloader = std::unique_ptr<XLoader>(new XLoader(m_deviceResources));
        xloader->Load();
    }
    
    モデルの生成(X-FILE の入力)と描画は XLoader Class に任せます。

    // コーン(座標と法線)をプログラムで計算して描画
    #include "pch.h"
    #include "Model.h"
    #include "..\Common\DirectXHelper.h"
    
    using namespace App1;
    
    using namespace DirectX;
    using namespace Windows::Foundation;
    
    // ファイルから頂点とピクセル シェーダーを読み込み、キューブのジオメトリをインスタンス化します。
    Model::Model(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
        m_degreesPerSecond(45),
        m_tracking(false),
        m_deviceResources(deviceResources)
    {
        CreateDeviceDependentResources();
        CreateWindowSizeDependentResources();
    }
    
    // ウィンドウのサイズが変更されたときに、ビューのパラメーターを初期化します。
    void Model::CreateWindowSizeDependentResources()
    {
        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_constantBufferData.projection,
            XMMatrixTranspose(perspectiveMatrix * orientationMatrix)
            );
    
        // 視点は (0,0.7,1.5) の位置にあり、y 軸に沿って上方向のポイント (0,-0.1,0) を見ています。
        //static const XMVECTORF32 eye = { 0.0f, 0.7f, 1.5f, 0.0f };
        static const XMVECTORF32 eye = { 0.0f, 0.7f, 4.0f, 0.0f };
        static const XMVECTORF32 at = { 0.0f, -0.1f, 0.0f, 0.0f };
        static const XMVECTORF32 up = { 0.0f, 1.0f, 0.0f, 0.0f };
    
        XMStoreFloat4x4(&m_constantBufferData.view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up)));
    }
    
    // フレームごとに 1 回呼び出し、キューブを回転させてから、モデルおよびビューのマトリックスを計算します。
    void Model::Update(DX::StepTimer const& timer)
    {
        if (!m_tracking)
        {
            // 度をラジアンに変換し、秒を回転角度に変換します
            float radiansPerSecond = XMConvertToRadians(m_degreesPerSecond);
            double totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
            float radians = static_cast<float>(fmod(totalRotation, XM_2PI));
    
            Rotate(radians);
        }
    }
    
    // モデルを、ラジアン単位で設定された大きさだけ回転させます。
    void Model::Rotate(float radians)
    {
        //更新されたモデル マトリックスをシェーダーに渡す準備をします
        XMStoreFloat4x4(&m_constantBufferData.model, XMMatrixTranspose(XMMatrixRotationY(radians)));
    }
    
    void Model::StartTracking()
    {
        m_tracking = true;
    }
    
    // 追跡時に、出力画面の幅方向を基準としてポインターの位置を追跡することにより、3D キューブを Y 軸に沿って回転させることができます。
    void Model::TrackingUpdate(float positionX)
    {
        if (m_tracking)
        {
            float radians = XM_2PI * 2.0f * positionX / m_deviceResources->GetOutputSize().Width;
            Rotate(radians);
        }
    }
    
    void Model::StopTracking()
    {
        m_tracking = false;
    }
    
    // 頂点とピクセル シェーダーを使用して、1 つのフレームを描画します。
    void Model::Render()
    {
        xloader->Draw(m_constantBufferData);
    }
    
    void Model::CreateDeviceDependentResources()
    {
        xloader = std::unique_ptr<XLoader>(new XLoader(m_deviceResources));
        xloader->Load();
    }
    
    void Model::ReleaseDeviceDependentResources()
    {
        xloader->Release();
    }
    
  5. XLoader.h のソースコードです。
    自動生成のプロジェクトでは Sample3DSceneRenderer.h で定義されていたモデル関係の領域です。
    #pragma once
    
    #include "..\Common\DeviceResources.h"
    #include "..\Common\StepTimer.h"
    #include "ShaderStructures.h"
    
    namespace App1
    {
        class XLoader
        {
        public:
            XLoader(const std::shared_ptr<DX::DeviceResources>& deviceResources);
            void Load();
            void Draw(ModelViewProjectionConstantBuffer);
            void Release();
    
        private:
            std::shared_ptr<DX::DeviceResources> m_deviceResources;
    
            Microsoft::WRL::ComPtr<ID3D11InputLayout>   m_inputLayout;
            Microsoft::WRL::ComPtr<ID3D11Buffer>        m_vertexBuffer;
            Microsoft::WRL::ComPtr<ID3D11Buffer>        m_indexBuffer;
            Microsoft::WRL::ComPtr<ID3D11VertexShader>  m_vertexShader;
            Microsoft::WRL::ComPtr<ID3D11PixelShader>   m_pixelShader;
            Microsoft::WRL::ComPtr<ID3D11Buffer>        m_constantBuffer;
            ModelViewProjectionConstantBuffer   m_constantBufferData;
            uint32  m_indexCount;
            bool    m_loadingComplete;
        };
    }
    
  6. XLoader.cpp のソースコードです。
    自動生成のプロジェクトでは Sample3DSceneRenderer.cpp で行っていたモデルの生成と描画を行います。
    今回は X-FILE を使わずに Cone メッシュの座標をプログラムで計算して描画します。
    Cone メッシュの描画は Win10 Normal Cone を参照して下さい。
    // コーン(座標と法線)をプログラムで計算して描画
    #include "pch.h"
    #include "XLoader.h"
    
    #include "..\Common\DirectXHelper.h"
    #define  KAKU  19  // (pyramid の画数+1)
    
    using namespace App1;
    using namespace DirectX;
    using namespace Windows::Foundation;
    
    // ファイルから頂点とピクセル シェーダーを読み込み、キューブのジオメトリをインスタンス化します。
    XLoader::XLoader(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
        m_loadingComplete(false),
        m_indexCount(0),
        m_deviceResources(deviceResources)
    {
    }
    
    // Load X-FILE
    void XLoader::Load()
    {
        // シェーダーを非同期で読み込みます。
        auto loadVSTask = DX::ReadDataAsync(L"SampleVertexShader.cso");
        auto loadPSTask = DX::ReadDataAsync(L"SamplePixelShader.cso");
    
        // 頂点シェーダー ファイルを読み込んだ後、シェーダーと入力レイアウトを作成します。
        auto createVSTask = loadVSTask.then([this](const std::vector<byte>& fileData) {
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreateVertexShader(
                    &fileData[0],
                    fileData.size(),
                    nullptr,
                    &m_vertexShader
                    )
                );
    
            static const D3D11_INPUT_ELEMENT_DESC vertexDesc [] =
            {
                { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
                { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            };
    
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreateInputLayout(
                    vertexDesc,
                    ARRAYSIZE(vertexDesc),
                    &fileData[0],
                    fileData.size(),
                    &m_inputLayout
                    )
                );
        });
    
        // ピクセル シェーダー ファイルを読み込んだ後、シェーダーと定数バッファーを作成します。
        auto createPSTask = loadPSTask.then([this](const std::vector<byte>& fileData) {
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreatePixelShader(
                    &fileData[0],
                    fileData.size(),
                    nullptr,
                    &m_pixelShader
                    )
                );
    
            CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer) , D3D11_BIND_CONSTANT_BUFFER);
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreateBuffer(
                    &constantBufferDesc,
                    nullptr,
                    &m_constantBuffer
                    )
                );
        });
    
        // 両方のシェーダーの読み込みが完了したら、メッシュを作成します。
        auto createCubeTask = (createPSTask && createVSTask).then([this] () {
    
            static VertexPosition modelVertices[KAKU * 2];
            for (int slice = 0; slice<KAKU; slice++)
            {
                float v = (float)slice / (float)(KAKU - 1);
                float theta = v * 3.14f * 2;
                modelVertices[2 * slice + 0].pos.x = sinf(theta);
                modelVertices[2 * slice + 0].pos.y = -1.0f;
                modelVertices[2 * slice + 0].pos.z = cosf(theta);
                modelVertices[2 * slice + 0].norm.x = sinf(theta);
                modelVertices[2 * slice + 0].norm.y = 1.0f;
                modelVertices[2 * slice + 0].norm.z = cosf(theta);
                modelVertices[2 * slice + 1].pos.x = 0.0f;
                modelVertices[2 * slice + 1].pos.y = 1.0f;
                modelVertices[2 * slice + 1].pos.z = 0.0f;
                modelVertices[2 * slice + 1].norm.x = sinf(theta);
                modelVertices[2 * slice + 1].norm.y = 1.0f;
                modelVertices[2 * slice + 1].norm.z = cosf(theta);
            }
    
            D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
            vertexBufferData.pSysMem = modelVertices;
            vertexBufferData.SysMemPitch = 0;
            vertexBufferData.SysMemSlicePitch = 0;
            CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(modelVertices), D3D11_BIND_VERTEX_BUFFER);
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreateBuffer(
                    &vertexBufferDesc,
                    &vertexBufferData,
                    &m_vertexBuffer
                    )
                );
    
            static unsigned short modelIndices[KAKU*6];
            for(int slice=0; slice<(KAKU-1); slice++)
            {   modelIndices[3*slice+0] = slice*2+0;
                modelIndices[3*slice+1] = slice*2+1;
                modelIndices[3*slice+2] = slice*2+2;
                modelIndices[3*(KAKU+slice)+0] = slice*2+3;
                modelIndices[3*(KAKU+slice)+1] = slice*2+2;
                modelIndices[3*(KAKU+slice)+2] = slice*2+1;
            }
    
            m_indexCount = ARRAYSIZE(modelIndices);
    
            D3D11_SUBRESOURCE_DATA indexBufferData = {0};
            indexBufferData.pSysMem = modelIndices;
            indexBufferData.SysMemPitch = 0;
            indexBufferData.SysMemSlicePitch = 0;
            CD3D11_BUFFER_DESC indexBufferDesc(sizeof(modelIndices), D3D11_BIND_INDEX_BUFFER);
            DX::ThrowIfFailed(
                m_deviceResources->GetD3DDevice()->CreateBuffer(
                    &indexBufferDesc,
                    &indexBufferData,
                    &m_indexBuffer
                    )
                );
        });
    
        // モデルが読み込まれたら、オブジェクトを描画する準備が完了します。
        createCubeTask.then([this] () {
            m_loadingComplete = true;
        });
    }
    
    // モデルを描画
    void XLoader::Draw(ModelViewProjectionConstantBuffer BuffData)
    {
        // 読み込みは非同期です。読み込みが完了した後にのみ描画してください。
        if (!m_loadingComplete)
        {   return; }
    
        auto context = m_deviceResources->GetD3DDeviceContext();
    
        // レンダー ターゲットを画面に設定します。
        ID3D11RenderTargetView *const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
        context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
    
        // 定数バッファーを準備して、グラフィックス デバイスに送信します。
        context->UpdateSubresource(
            m_constantBuffer.Get(),
            0, NULL, &BuffData, 0, 0);
    
        // 各頂点は、VertexPositionColor 構造体の 1 つのインスタンスです。
        UINT stride = sizeof(VertexPosition);
        UINT offset = 0;
        context->IASetVertexBuffers(
            0, 1, m_vertexBuffer.GetAddressOf(),
            &stride, &offset);
    
        context->IASetIndexBuffer(
            m_indexBuffer.Get(),
            DXGI_FORMAT_R16_UINT, // 各インデックスは、1 つの 16 ビット符号なし整数 (short) です。
            0);
    
        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    
        context->IASetInputLayout(m_inputLayout.Get());
    
        // 頂点シェーダーをアタッチします。
        context->VSSetShader(
            m_vertexShader.Get(), nullptr, 0);
    
        // 定数バッファーをグラフィックス デバイスに送信します。
        context->VSSetConstantBuffers(
            0, 1, m_constantBuffer.GetAddressOf());
    
        // ピクセル シェーダーをアタッチします。
        context->PSSetShader(
            m_pixelShader.Get(),
            nullptr, 0);
    
        // オブジェクトを描画します。
        context->DrawIndexed(
            m_indexCount, 0, 0);
    }
    
    // 領域の解放
    void XLoader::Release()
    {
        m_loadingComplete = false;
        m_vertexShader.Reset();
        m_inputLayout.Reset();
        m_pixelShader.Reset();
        m_constantBuffer.Reset();
        m_vertexBuffer.Reset();
        m_indexBuffer.Reset();
    }
    

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