ことれいのもり

DirectX11でImGuiを使ってみた

はじめに

以前DirectX11 でImGuiを使うために設定をしました。

今回は実際にメッセージを表示するところまで作ってみます。

前回の記事はこちらから

前提

言語

  • Visual Studio 2022
  • C++
  • DirectX11
  • ImGui

参考リンク

設計

ImGuiを使ってDirectX11のCameraオブジェクトの座標を表示してみました。

今回はDebuggerクラスを作成し、ImGuiの機能を持たせたいと思います。

ImGuiをDirectX11で使うために必要なことは以下の通りです。

  • HWND, ID3D11Device, ID3D11DeviceContextで初期化する
  • 描画前に ImGui::NewFrame(); を呼び出す
  • 描画後に ImGui::Render();ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); を呼び出す
  • 終了するときに破棄処理を呼び出す

それぞれの処理をクラスのメソッドとして実装します。

ちなみにシングルトンにしています。

Debugger.h

#pragma once

#include <Windows.h>
#include <d3d11.h>
#include <wrl/client.h>
#include <DirectXMath.h>
#include "imgui.h"
#include "imgui_impl_win32.h"
#include "imgui_impl_dx11.h"

using Microsoft::WRL::ComPtr;

class Debugger
{
public:
    static Debugger& GetInstance()
    {
        static Debugger instance;
        return instance;
    }

    // 初期化
    void Initialize(HWND hWnd, ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> context);

    // 描画開始前の処理
    void BeginFrame();

    // 描画終了後の処理
    void EndFrame();

    // デバッグ情報の描画
    void ShowDebugInfo(const DirectX::XMVECTOR& cameraPos);

private:
    Debugger();
    ~Debugger();

    // 初期化しているかどうかのフラグ
    bool isInitialized;
};

Debugger.cpp

#include "Debugger.h"

Debugger::Debugger() : isInitialized(false)
{

}

Debugger::~Debugger()
{
    if (isInitialized) {
        ImGui_ImplDX11_Shutdown();
        ImGui_ImplWin32_Shutdown();
        ImGui::DestroyContext();
    }
}

void Debugger::Initialize(HWND hWnd, ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> context)
{
    if (isInitialized)
    {
        return;
    }

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;   // キーボードの入力を有効化

    ImGui::StyleColorsDark();

    // ImGuiのWindows用バックエンドとDirectX11用バックエンドの初期化をする
    ImGui_ImplWin32_Init(hWnd);
    ImGui_ImplDX11_Init(device.Get(), context.Get());

    isInitialized = true;
}

void Debugger::BeginFrame()
{
    if (!isInitialized)
    {
        return;
    }

    ImGui_ImplDX11_NewFrame();
    ImGui_ImplWin32_NewFrame();
    ImGui::NewFrame();
}

void Debugger::EndFrame()
{
    if (!isInitialized)
    {
        return;
    }

    ImGui::Render();
    ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}

void Debugger::ShowDebugInfo(const DirectX::XMVECTOR& cameraPos)
{
    if (!isInitialized)
    {
        return;
    }

    ImGui::Begin("Debug Infomation");

    // カメラの位置を取得
    DirectX::XMFLOAT3 cameraPosition;
    DirectX::XMStoreFloat3(&cameraPosition, cameraPos);

    // 表示
    ImGui::Text("CameraPosition: X: %.2f, Y: %.2f, Z: %.2f", cameraPosition.x, cameraPosition.y, cameraPosition.z);

    ImGui::End();
}

初期化処理は Debugger::Initialize()に、描画前の処理はDebugger::BeginFrame()に、描画後の処理はDebugger::EndFrame()に、終了するときの処理はDebugger::~Debugger()のデストラクタに記述しています。

そして、表示させたい情報をDebugger::ShowDebugInfo()の中に書いておきます。

後はこれを適切な場所で呼び出すだけです。

シングルトンになっているので簡単に呼び出すことができます。

呼び出すとき

作ったクラスを呼び出すときにどうするかですが、初期化はDirectX11でデバイスとデバイスコンテキストを作成した後が良いと思います。

私の場合だとGraphics.cppでデバイスとデバイスコンテキストを呼び出しているので、

Graphics.cppの中に書きました。

DirectX11は勉強中なのでコメントに間違いがあるかも知れませんがご容赦ください。

bool Graphics::InitDirectX(HWND hWnd)
{
    D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0 };
    D3D_FEATURE_LEVEL selectedFeatureLevel;
    // DirectX11のデバイスとデバイスコンテキストを作成する
    HRESULT hr = D3D11CreateDevice(
        nullptr,                    // 指定なし->ハードウェアが選ぶ
        D3D_DRIVER_TYPE_HARDWARE,   // レンダリングドライバの指定
        nullptr,                    // ソフトウェアラスタライザを使用しない
        D3D11_CREATE_DEVICE_DEBUG,  // デバッグフラグ
        featureLevels,              // 特徴レベルは11を明示的に指定
        _countof(featureLevels),    // DirectXのバージョンの数
        D3D11_SDK_VERSION,          // DirectXのバージョンの指定
        d3dDevice.GetAddressOf(),   // DirectX11デバイスを格納するポインタ
        &selectedFeatureLevel,      // 特徴レベルを格納
        d3dContext.GetAddressOf()   // デバイスコンテキストを格納
    );

    if (FAILED(hr)) {
        return false;   // デバイスの作成失敗
    }

    // SwapChainの設定
    DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
    swapChainDesc.BufferCount = 2;                                  // バッファの数(2はダブルバッファリング)
    swapChainDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;   // バックバッファのフォーマット
    swapChainDesc.BufferDesc.Width = 800;                           // 幅
    swapChainDesc.BufferDesc.Height = 600;                          // 高さ
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;    // バッファの用途(レンダリングターゲットに使う)
    swapChainDesc.OutputWindow = hWnd;                              // 描画先のウィンドウの指定
    swapChainDesc.SampleDesc.Count = 1;                             // マルチサンプリング(アンチエイリアスなしの1)
    swapChainDesc.Windowed = TRUE;                                  // ウィンドウモードで起動
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;            // スワップチェインの交換方法(フレームごとに新しいバックバッファを使う)
    swapChainDesc.Flags = 0;                                        // フラグはなし

    // SwapChainを作成
    // IDXGIFactory はGPUの情報、スワップチェインの作成などのDirectXのディスプレイ関連の操作を管理するインターフェース
    // ->ここでは pFactory に IDXGIFactoryのインスタンスを作成して格納する、そして”後で”スワップチェインを作成する
    ComPtr<IDXGIFactory1> pFactory;
    // CreateIDXGIFactoryは、DirectXのGPU関連の情報を取得するファクトリーオブジェクトを作成する
    // _uuidof(IDXGIFactory) はインターフェースの GUID を取得
    hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(pFactory.GetAddressOf()));
    if (FAILED(hr)) {
        return false;
    }

    // さっきの”後で”はここ!
    // スワップチェインを作成する
    hr = pFactory->CreateSwapChain(d3dDevice.Get(), &swapChainDesc, swapChain.GetAddressOf());
    if (FAILED(hr) || !swapChain) {
        logger->Log("スワップチェインの作成に失敗しました", Logger::Error);
        return false;
    }

    // Debuggerの初期化
    InitDebugger(hWnd, d3dDevice, d3dContext);

    return true;
}

void Graphics::InitDebugger(HWND hWnd, ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> context)
{
    Debugger::GetInstance().Initialize(hWnd, device, context);
}

そして、描画処理の前にBeginFrame, 後にEndFrameを記述します。

挟み込む感じです。

私の場合はRenderer.cppに描画処理を任せているので、以下のようにしました。

void Renderer::Render()
{
    // キーボードの入力チェック
    float moveX = 0.0f;
    float moveY = 0.0f;
    float moveZ = 0.0f;

    // 前
    if (GetAsyncKeyState(VK_UP)) {
        moveZ += 0.1f;
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        moveZ -= 0.1f;
    }
    if (GetAsyncKeyState(VK_LEFT)) {
        moveX -= 0.1f;
    }
    if (GetAsyncKeyState(VK_RIGHT)) {
        moveX += 0.1f;
    }

    // カメラ移動
    camera->Move(moveX, moveY, moveZ);

    // ビュー・プロジェクション行列
    DirectX::XMMATRIX viewMatrix = camera->GetViewMatrix();
    DirectX::XMMATRIX projectionMatrix = camera->GetProjectionMatrix();
    DirectX::XMMATRIX viewProjectionMatrix = DirectX::XMMatrixMultiply(viewMatrix, projectionMatrix);

    // 行列を転置して、(DirectXのHLSLは順番が違うので転置する必要がある)
    // 定数バッファを作成
    MatrixBufferType matrixData;
    matrixData.world = DirectX::XMMatrixTranspose(DirectX::XMMatrixIdentity());
    matrixData.viewProjection = DirectX::XMMatrixTranspose(viewProjectionMatrix);

    // 定数バッファを更新
    constantBuffer.Update(d3dContext, matrixData);

    // シェーダーに定数バッファをセット
    d3dContext->VSSetConstantBuffers(0, 1, constantBuffer.GetBufferAddress());

    // ビューポートの設定
    SetViewPort(800, 600);

    // 入力レイアウトをセット
    d3dContext->IASetInputLayout(shaderManager->GetInputLayout());

    // 頂点バッファをセット
    ID3D11Buffer* vb = vertexBuffer->GetVertexBuffer();
    UINT stride = sizeof(Vertex);
    UINT offset = 0;
    d3dContext->IASetVertexBuffers(0, 1, &vb, &stride, &offset);

    // プリミティブ(線画形状)を設定
    d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // シェーダーをセット
    d3dContext->VSSetShader(shaderManager->GetVertexShader(), nullptr, 0);
    d3dContext->PSSetShader(shaderManager->GetPixelShader(), nullptr, 0);

    d3dContext->OMSetRenderTargets(1, renderTargetView.GetAddressOf(), nullptr);

    Debugger::GetInstance().BeginFrame();

    // 描画
    d3dContext->Draw(3, 0);

    Debugger::GetInstance().ShowDebugInfo(camera->GetPosition());

    Debugger::GetInstance().EndFrame();
}

Debuggerクラスをどこで呼び出すかは人によると思うので参考にならないと思いますが、使い方を紹介しておきました。

この処理を含めて実行すると、

ImGuiを使って描画した画像

カメラの座標が表示されました!

良い感じです。

おわりに

最初はDirectX11 のコードを全部載せようと思ったのですが、長くなりすぎるので一部分だけを載せました。

ImGuiの使い方は伝わると思います。