Texture Model Picker

Windows10 でハードディスク上の X-FILE(頂点座標+法線+Texture)を入力して描画します。

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

プログラムの説明

  1. File Picker を使うと Win10 Picker Color のように、直接ハードディスクからモデルを選択することが出来ます。
    テクスチャ画像のファイル名はモデルの中で定義されているのですが、一般のファイルに対するアクセスは禁止されています。
    幸いにして、モデルとテクスチャは独立していて Win10 Texture Picker のように自由にテクスチャを切り替えることが出来ます。
    そこでテクスチャ画像も File Picker を使って入力することにしました。
    最初に File Picker を使ってモデルを入力すると、仮のテクスチャ画像(star.jpg)を張り付けて描画します。
    次に File Picker を使ってテクスチャ画像を入力すると本来のテクスチャ画像が張り付けられます。
    テクスチャが設定されていないモデルも描画出来ますが、カラーモデルには対応していません。
    法線が設定されていないモデルでは、プログラムで法線ベクトルを計算します。
  2. File Picker を使うプロジェクトは DirectX 11 および XAML アプリ(ユニバーサル Windows)から生成します。
    Win10 Texture Picker を参照してプロジェクトを作成して下さい。
    (CreateCube で生成したモデルの Texture を Picker で選択するプロジェクトです。)
  3. File Picker でモデルとテクスチャを選択する二個のボタンを貼り付けます。
    DirectXPage.xaml のデザイン画面を表示して、ボタンを二個貼り付けて下さい。
    モデル選択ボタンには "Model" と、テクスチャを選択するボタンには "Texture" と表示します。
  4. モデルを入力する Button_Click() 関数です。
    Button_Click() ⇒ m_main->SetString() ⇒ loader->SetString() とリレーします。
    void App1::DirectXPage::Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        auto open = ref new FileOpenPicker();
        open->SuggestedStartLocation = PickerLocationId::Desktop;
        open->FileTypeFilter->Clear();
        open->FileTypeFilter->Append(".x");
        create_task(open->PickSingleFileAsync()).then([this](StorageFile^ file)
        {   return FileIO::ReadTextAsync(file);
        }).then([this](task<String^> previousTask)
        {   try
        {
            m_text = previousTask.get();
            //OutputDebugString(m_text->Data());
            m_main->SetString(m_text);
        }
        catch (...)
        {
            OutputDebugString(L"X-FILE not found\n");
        }
        });
    
        // ページ ライフサイクルのイベント ハンドラーを登録します。
        CoreWindow^ window = Window::Current->CoreWindow;
    
        window->VisibilityChanged +=
            ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &DirectXPage::OnVisibilityChanged);
    
        DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();
    
        currentDisplayInformation->DpiChanged +=
            ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnDpiChanged);
    
        currentDisplayInformation->OrientationChanged +=
            ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnOrientationChanged);
    
        DisplayInformation::DisplayContentsInvalidated +=
            ref new TypedEventHandler<DisplayInformation^, Object^>(this, &DirectXPage::OnDisplayContentsInvalidated);
    
        swapChainPanel->CompositionScaleChanged +=
            ref new TypedEventHandler<SwapChainPanel^, Object^>(this, &DirectXPage::OnCompositionScaleChanged);
    
        swapChainPanel->SizeChanged +=
            ref new SizeChangedEventHandler(this, &DirectXPage::OnSwapChainPanelSizeChanged);
    
        // タッチ時のパフォーマンスが向上するように、ポインターのビジュアル フィードバックをすべて無効にします。
        auto pointerVisualizationSettings = PointerVisualizationSettings::GetForCurrentView();
        pointerVisualizationSettings->IsContactFeedbackEnabled = false;
        pointerVisualizationSettings->IsBarrelButtonFeedbackEnabled = false;
    
        // この時点では、デバイスにアクセスできます。
        // デバイスに依存するリソースを作成できます。
        m_deviceResources = std::make_shared<DX::DeviceResources>();
        m_deviceResources->SetSwapChainPanel(swapChainPanel);
    
        // 独立した入力ポインター イベントを取得するために、SwapChainPanel を登録します
        auto workItemHandler = ref new WorkItemHandler([this](IAsyncAction ^)
        {
            // CoreIndependentInputSource はポインター イベントを発生させます。
            m_coreInput = swapChainPanel->CreateCoreIndependentInputSource(
                Windows::UI::Core::CoreInputDeviceTypes::Mouse |
                Windows::UI::Core::CoreInputDeviceTypes::Touch |
                Windows::UI::Core::CoreInputDeviceTypes::Pen
                );
    
            // バックグラウンド スレッドでポインター イベントが発生したときに、そのイベントを登録します。
            m_coreInput->PointerPressed += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerPressed);
            m_coreInput->PointerMoved += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerMoved);
            m_coreInput->PointerReleased += ref new TypedEventHandler<Object^, PointerEventArgs^>(this, &DirectXPage::OnPointerReleased);
    
            // 入力メッセージが配信された時点で、そのメッセージの処理を開始します。
            m_coreInput->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
        });
    
        // 優先順の高い専用のバックグラウンド スレッドでタスクを実行します。
        m_inputLoopWorker = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::TimeSliced);
    
        m_main = std::unique_ptr<App1Main>(new App1Main(m_deviceResources));
        m_main->StartRenderLoop();
    }
    
  5. テクスチャを入力する Button_Click2() 関数です。
    Button_Click2() ⇒ m_main->GetTexture() ⇒ loader->LoadTexture() とリレーします。
    void App1::DirectXPage::Button_Click2(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
    {
        //OutputDebugString(L"Texture Button\n");
        auto open = ref new FileOpenPicker();
        open->SuggestedStartLocation = PickerLocationId::Desktop;
        open->FileTypeFilter->Clear();
        open->FileTypeFilter->Append(".bmp");
        open->FileTypeFilter->Append(".png");
        open->FileTypeFilter->Append(".jpeg");
        open->FileTypeFilter->Append(".jpg");
    
        create_task(open->PickSingleFileAsync()).then([this](StorageFile^ file)
        {   return FileIO::ReadBufferAsync(file);
        }).then([=](IBuffer^ buffer) 
        {
            auto fileData = ref new Platform::Array<byte>(buffer->Length);
            DataReader::FromBuffer(buffer)->ReadBytes(fileData);
            m_main->GetTexture(fileData, L"Texture");
        });
    }
    
  6. App1Main.cpp から Class X_Class の関数を呼び出します。
    App1Main::SetString() から実際にモデルを入力する m_sceneRenderer->SetString() を呼び出します。
        m_sceneRenderer = std::unique_ptr<X_Class>(new X_Class(m_deviceResources));
    
    void App1Main::SetString(Platform::String^ text)
    {
        m_sceneRenderer->SetString(text); 
    }
    
    App1Main::GetTexture() から実際にテクスチャを入力する m_sceneRenderer->SetTexture() を呼び出します。
    void App1Main::GetTexture(Platform::Array<byte>^ fileData, Platform::String^ fileName)
    {
        m_sceneRenderer->SetTexture(fileData, fileName);
    }
    
  7. モデルやテクスチャを入力して描画する Class X_Class のヘッダーファイル X_Class.h です。
    SetString() が入力したモデルを描画する関数です。
    SetTexture() がテクスチャを入力する関数です。
    #pragma once
    
    #include "..\Common\DeviceResources.h"
    #include "ShaderStructures.h"
    #include "..\Common\StepTimer.h"
    #include <vector>
    using namespace std;
    using namespace Platform;
    
    namespace App1
    {
        class X_Class
        {
        public:
            X_Class(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; }
            void SetString(Platform::String^ text);
            void SetTexture(Platform::Array<byte>^ tex, Platform::String^ filename);
    
        private:
            void Rotate(float radians);
    
        private:
            std::shared_ptr<DX::DeviceResources> m_Resources;
            ID3D11Device1           *m_Device;
            ID3D11DeviceContext1    *m_Context;
    
            // キューブ ジオメトリの Direct3D リソース。
            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;
    
            Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_textureSRV;
            Microsoft::WRL::ComPtr<ID3D11SamplerState>  m_sampler;
    
            ID3D11RasterizerState  *m_RasterizerState;
    
            // モデル ジオメトリのシステム リソース。
            ModelViewProjectionConstantBuffer   m_constantBufferData;
            uint32  m_indexCount;
    
            // レンダリング ループで使用する変数。
            bool    m_loadingComplete;
            float   m_degreesPerSecond;
            bool    m_tracking;
    
            //★ X-FILE の領域
            std::wstring    m_x;                //X-FILE TEXT
            vector<wstring> VT;                 //X-FILE を行で切り分け
            int             VT_size;            //VT の大きさ
    
            vector<DirectX::XMFLOAT3> m_pos;    // 頂点座標
            vector<DirectX::XMFLOAT3> m_norm;   // 法線ベクトル
            vector<DirectX::XMFLOAT2> m_tex;    // テクスチャ
            vector<unsigned short> m_idxP;      // 頂点 Index の並び(角付)
            vector<unsigned short> m_idxN;      // 法線 Index の並び(角付)
            vector<unsigned short> m_idxP3;     // 頂点 Index(3P) の並び
            vector<unsigned short> m_idxN3;     // 法線 Index(3P) の並び
    
            int     m_Line, m_Col, m_Top;
            wstring Word;
            bool    m_normflag;     // 法線の取得フラグ
    
            bool Search(String^ key);
            void Token();
            bool LineToken();
            bool SetXMFLOAT3(String^ key, vector<DirectX::XMFLOAT3> *f3);
            bool SetXMFLOAT2(String^ key, vector<DirectX::XMFLOAT2> *f2);
            void ComputeNorm(VertexPosition *Vertices, int siz);
            bool SetShort(vector<unsigned short> *val);
            void PX_P3(vector<unsigned short> PX, vector<unsigned short> *P3);
        };
    }
    
  8. モデルやテクスチャを入力して描画する X_Class.cpp です。
    処理が簡単な SetTexture() 関数から説明します。
    TextureLoader で画像を入力して m_textureSRV に設定します。
    void X_Class::SetTexture(Platform::Array<byte>^ tex, Platform::String^ filename)
    {
        TextureLoader^ loader = ref new TextureLoader(m_Resources->GetD3DDevice());
        loader->LoadTexture(tex, filename, nullptr, &m_textureSRV);
    }
    
  9. Render() 関数では m_textureSRV を設定して描画します。
    // 頂点とピクセル シェーダーを使用して、1 つのフレームを描画します。
    void X_Class::Render()
    {
        if (!m_loadingComplete) return;
    
        // レンダー ターゲットを画面に設定します。
        ID3D11RenderTargetView *const targets[1] = { m_Resources->GetBackBufferRenderTargetView() };
        m_Context->OMSetRenderTargets(1, targets, m_Resources->GetDepthStencilView());
    
        // 定数バッファーを準備して、グラフィックス デバイスに送信します。
        m_Context->UpdateSubresource(
            m_constantBuffer.Get(), 0, NULL,
            &m_constantBufferData, 0, 0);
    
        UINT stride = sizeof(VertexPosition);
        UINT offset = 0;
        m_Context->IASetVertexBuffers(
            0, 1,
            m_vertexBuffer.GetAddressOf(), &stride, &offset);
    
        m_Context->IASetIndexBuffer(
            m_indexBuffer.Get(),
            DXGI_FORMAT_R16_UINT, 0);
    
        m_Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        m_Context->IASetInputLayout(m_inputLayout.Get());
        m_Context->RSSetState(m_RasterizerState);
    
        // 頂点シェーダーをアタッチします。
        m_Context->VSSetShader(
            m_vertexShader.Get(), nullptr, 0);
    
        // 定数バッファーをグラフィックス デバイスに送信します。
        m_Context->VSSetConstantBuffers(
            0, 1, m_constantBuffer.GetAddressOf());
    
        // ピクセル シェーダーをアタッチします。
        m_Context->PSSetShader(
            m_pixelShader.Get(), nullptr, 0);
    
        m_Context->PSSetShaderResources(0, 1, m_textureSRV.GetAddressOf());
        m_Context->PSSetSamplers(0, 1, m_sampler.GetAddressOf());
    
        // オブジェクトを描画します。
        m_Context->DrawIndexed(m_indexCount, 0, 0);
    }
    
  10. モデルを入力する SetString() 関数です。
    //★ 入力した X-FILE TEXT(モデルデータ)を受け取って解析
    void X_Class::SetString(Platform::String^ text)
    {
        int num, pt, pw;
        m_x = text->Data();
    
        // String を行で切り分ける(m_x⇒VT)
        VT.clear();
        num = m_x.size();
        for (pt = 0; pt<num;)
        {
            pw = pt;
            for (pt++; pt<num && m_x[pt] != L'\n'; pt++);
            if (pt >= num)  break;
            pt++;
            Word = Word.assign(m_x, pw, pt - pw);
            VT.push_back(Word);
        }
        VT.push_back(L"}\r\n");
        VT_size = VT.size();
    
        // Header Check("xof 0303txt 0032")
        m_Col= VT[0].find(L"xof");
        if (m_Col==-1)
        {   return;
        }
        m_Col= VT[0].find(L"txt");
        if (m_Col==-1)
        {   return;
        }
    
        // template をスキップ
        m_Line= 1;
        m_Col= 0;
        while(true)
        {   Token();
            if (Word!=L"template")  break;
            Search("}");
            m_Line++;
        }
        m_Top= m_Line;
    
        // 頂点座標を取得
        m_pos.clear();
        m_Line = m_Top;
        if (SetXMFLOAT3(L"Mesh ", &m_pos))
        {
            SetShort(&m_idxP);
            Debug(m_idxP);
        }
        else
        {   OutputDebugString(L"Mesh File  Format Error\n");
            return;
        }
    
        // 法線を取得
        m_norm.clear();
        m_Line = m_Top;
        m_normflag = false;
        if (SetXMFLOAT3(L"MeshNormals ", &m_norm))
        {
            SetShort(&m_idxN);
            Debug(m_idxN);
        }
    
        // Texture 座標を取得
        m_Line = m_Top;
        if (SetXMFLOAT2(L"MeshTextureCoords ", &m_tex))
        {
            if (m_pos.size() != m_tex.size())
            {   OutputDebugString(L"頂点座標の数 ≠ テクスチャ座標の数\n");  }
        }
        // Texture 座標が無い時
        else
        {   OutputDebugString(L"MeshTextureCoords None\n");  }
    
        // N角ポリゴン ⇒ 3角ポリゴン
        PX_P3(m_idxP, &m_idxP3);
        PX_P3(m_idxN, &m_idxN3);
        Debug(m_idxP3);
        Debug(m_idxN3);
    
        //★ 頂点データとインデックスデータを作成
        m_indexCount = m_idxP3.size();
        VertexPosition *Vertices = new VertexPosition[m_indexCount];
        unsigned short *Indices = new unsigned short[m_indexCount];
    
        for (unsigned i = 0; i<m_indexCount; i++)
        {   // 頂点座標
            Vertices[i].pos = m_pos[m_idxP3[i]];
            // 法線ベクトル
            if (m_idxN3.size()>i)   Vertices[i].norm = m_norm[m_idxN3[i]];
            // テクスチャ座標
            if (m_idxP3[i]<m_tex.size())    Vertices[i].tex = m_tex[m_idxP3[i]];
            else    Vertices[i].tex = XMFLOAT2(0.0f, 0.0f);
            Indices[i] = i;
        }
        // 法線ベクトルの計算
        if (m_normflag==false)
            ComputeNorm(Vertices, m_indexCount);
    
        // メッシュを生成
        D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
        vertexBufferData.pSysMem = Vertices;
        vertexBufferData.SysMemPitch = 0;
        vertexBufferData.SysMemSlicePitch = 0;
        CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(VertexPosition)*m_indexCount, D3D11_BIND_VERTEX_BUFFER);
        DX::ThrowIfFailed(
            m_Device->CreateBuffer(
                &vertexBufferDesc, &vertexBufferData, &m_vertexBuffer));
    
        // インデックスを生成
        D3D11_SUBRESOURCE_DATA indexBufferData = {0};
        indexBufferData.pSysMem = Indices;
        indexBufferData.SysMemPitch = 0;
        indexBufferData.SysMemSlicePitch = 0;
        CD3D11_BUFFER_DESC indexBufferDesc(sizeof(unsigned short)*m_indexCount, D3D11_BIND_INDEX_BUFFER);
        DX::ThrowIfFailed(
            m_Device->CreateBuffer(
                &indexBufferDesc, &indexBufferData, &m_indexBuffer));
    
        // 領域の解放
        if (Vertices)   delete Vertices;
        if (Indices)    delete Indices;
    
        m_loadingComplete = true;   // 描画準備完了
    }
    
  11. シェーダーは[頂点座標+法線+テクスチャ]のものを使用して下さい。
    File Picker から選択した X-FILE(テクスチャモデル)が描画出来ることを確かめて下さい。
    法線が設定されていない tiger.x も描画できることを確認しています。
    char.x も描画出来ますが、星型のポリゴンの色が黄色になりません。
    このモデルはちょっと変わっていて、星型のポリゴンだけはテクスチャが設定されていないのです。
    char.x を正常に描画するプログラムは XLoader Texture3 を参照して下さい。
    X-FILE を解析する全ソースコードは Windows10 X_FILE や Windows10 Load_X Class を参照して下さい。

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