LoginSignup
13

More than 3 years have passed since last update.

DirectX11 プログラミング事始め

Last updated at Posted at 2019-09-23

DirectX11 の初歩的なプログラミングを紹介します。
グラフィックス API の入門でおなじみの三角形を映すだけのプログラムを作成します。
メインウィンドウの生成処理と DirectX が行う 3D 描画の処理を明確に分けることを意識して進めます。
サンプルコードは GitHubのリポジトリ に置きました。

メインウィンドウの生成に関して

GUI アプリケーションを作成する際に重要なことの一つが使用するフレームワークの選択です。
今回は三角形を映すだけのシンプルな機能しか持たせないので、ウィンドウがメインのものしかない簡素な GUI を作成します。Win32API を使いますが一から作ることはせずに、Visual Studio のプロジェクトテンプレートから作成して手間を省きます。

プロジェクトの作成

開発環境は Visual Studio 2019 Preview を使用していますが、DirectX11 の SDK があれば特にバージョンにこだわる必要はありません。
Visual Studio を起動し新しいプロジェクトの作成を選択し、プロジェクトテンプレートを選ぶ画面で下記画像の様に「Windows デスクトップアプリケーション」を選択し[次へ]をクリックします。
今回のサンプルではプロジェクト名・ソリューション名を DrawPrimitive と付けました。

makeproject.png

クラスの作成

DirectX による描画処理を担うクラスを作成します。クラス名は CDxGraphic とし、メンバー変数とメソッドの定義を DxGraphic.h に、メソッドの実装を DxGraphic.cpp に記述します。
ヘッダーファイルを下記に記載します。C++ ファイルに関しては GitHubのページ を参照してください。

DxGraphic.h
#pragma once
// M_PI
#define _USE_MATH_DEFINES
#include <math.h>
#include <vector>
#include <fstream>
#include <Shlwapi.h>
#pragma comment(lib, "Shlwapi.lib")

// D3D
#include <d3d11.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "D3DCompiler.lib")

// Comptr
#include <atlcomcli.h>

class CDxGraphic
{
private:
    HWND m_WindowHandle = NULL;
    // 機能レベル, フォーマット
    D3D_FEATURE_LEVEL featurelevel = D3D_FEATURE_LEVEL_11_0;
    UINT swapchaincount = 1;
    DXGI_FORMAT swapchainformat = DXGI_FORMAT_B8G8R8A8_UNORM;
    DXGI_FORMAT depthstencilformat = DXGI_FORMAT_D24_UNORM_S8_UINT;
    DXGI_SAMPLE_DESC sampledesc = { 1, 0 };

    // コアとなる処理を行うための変数
    CComPtr<ID3D11Device> device;
    CComPtr<ID3D11DeviceContext> context;
    CComPtr<IDXGISwapChain> swapchain;
    CComPtr<ID3D11Texture2D> backbuffer;
    CComPtr<ID3D11RenderTargetView> rtv;
    CComPtr<ID3D11Texture2D> depthtex;
    CComPtr<ID3D11DepthStencilView> dsv;
    CComPtr<ID3D11RasterizerState> rs;
    CComPtr<ID3D11DepthStencilState> dss;
    CComPtr<ID3D11VertexShader> vertexshader;
    CComPtr<ID3D11GeometryShader> geometryshader;
    CComPtr<ID3D11PixelShader> pixelshader;
    CComPtr<ID3D11InputLayout> inputlayout;

    // 定数バッファ
    CComPtr<ID3D11Buffer> matrixbuffer;
    CComPtr<ID3D11Buffer> vertexbuffer;
    UINT numindices = 0;

    // DirectX算術用マトリックス
    DirectX::XMMATRIX d3dworldmatrix = DirectX::XMMatrixIdentity();
    DirectX::XMMATRIX d3dviewmatrix = DirectX::XMMatrixIdentity();
    DirectX::XMMATRIX d3dprojmatrix = DirectX::XMMatrixIdentity();

    struct Vertex
    {
        float position[3];  // (x, y, z)
        float color[4];     // (r, g, b, a)
    };

    std::vector<Vertex> InputData =
    {
        {{ 0.0f, 0.0f, 1.732051f}, {1.0f, 0.0f, 0.0f, 1.0f}},
        {{-1.0f, 0.0f,   0.0f}, {0.0f, 1.0f, 0.0f, 1.0f}},
        {{ 1.0f, 0.0f,   0.0f}, {0.0f, 0.0f, 1.0f, 1.0f}}
    };

    struct CoordColor
    {
        DirectX::XMFLOAT3 coord;
        DirectX::XMFLOAT4 color;
    };

    struct MatrixBuffer
    {
        DirectX::XMMATRIX matproj;
        DirectX::XMMATRIX matview;
        DirectX::XMMATRIX matworld;
    };

    bool CreateDeviceAndSwapChain(int w, int h);

    bool CreateRenderTarget();

    bool CreateDefaultRasterizerState();

    bool CreateDepthStencilState();

    bool CreateStencilBuffer(int w, int h);

    bool CreateShaderFromCompiledFiles();

    bool CreateConstantBuffer();

    void ReleaseComPtr();

public:
    CDxGraphic();
    ~CDxGraphic();

    void SetWindowHandle(HWND hWnd);

    bool InitD3D(int w, int h);

    void Render();

    void LoadSampleData(int w, int h);

    void UpdateMatrices(int w, int h);

    bool ResizeView(int w, int h);
};

InputData が今回表示させる三角形のデータです。各頂点は位置座標 (x, y, z) と色情報 (red, green, blue, alpha) を持っています。

シェーダーファイルの作成

GPU で行う処理をシェーダーファイル(HLSL)に記述します。
[新しいファイルの追加]ウィンドウで[Visual C++]の欄にある[HLSL]をクリックし、頂点シェーダーファイルを選び VertexShader.hlsl を追加します。
ジオメトリーシェーダーファイル、ピクセルシェーダーファイルも同様の手順で追加します。お好みでフィルターとフォルダを作成してファイルを整理するのもありです。
シェーダーファイルは Shader Model 5.0 でコンパイルします。
この設定を行うにはソリューションエクスプローラーで各シェーダーファイルを右クリックしプロパティページを開き、HLSL コンパイラ欄の全般をクリックし、シェーダーモデルの項目からShader Model 5.0 (/5_0) を選択します。
各シェーダーファイルの記述は GitHubのページ を参考にしてください。

メインウィンドウ側での呼び出し

エントリーポイント及びメインウィンドウの生成は自動生成された DrawPrimitive.cpp に記述されています。
このファイルに DxGraphic.h をインクルードして、処理を呼び出します。
(2020.9.26追記 GetWindowRect 関数を呼び出していた箇所を GetClientRect 関数に修正しました。)

DrawPrimitive.cpp
// DrawPrimitive.cpp : アプリケーションのエントリ ポイントを定義します。

//

#include "framework.h"
#include "DrawPrimitive.h"
#include "DxGraphic.h"
#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名
CDxGraphic g_dxgra;                             // DirectX 描画処理

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。

    // グローバル文字列を初期化する
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_DRAWPRIMITIVE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーション初期化の実行:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DRAWPRIMITIVE));

    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します
//
//   コメント:
//
//        この関数で、グローバル変数でインスタンス ハンドルを保存し、
//        メイン プログラム ウィンドウを作成および表示します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // グローバル変数にインスタンス ハンドルを格納する

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   g_dxgra.SetWindowHandle(hWnd);
   RECT rc;
   GetClientRect(hWnd, &rc);
   int w = rc.right - rc.left;
   int h = rc.bottom - rc.top;
   if (g_dxgra.InitD3D(w, h))
   {
       g_dxgra.LoadSampleData(w, h);
   }

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的: メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウを描画する
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    RECT rc;
    GetClientRect(hWnd, &rc);
    int w = rc.right - rc.left;
    int h = rc.bottom - rc.top;
    switch (message)
    {
    case WM_SIZE:
        if (g_dxgra.ResizeView(w, h))
        {
            g_dxgra.UpdateMatrices(w, h);
            g_dxgra.Render();
        }
        break;
    case WM_COMMAND:
=== 中略 ===
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
=== 以下省略 ===

プログラムの実行

drawprimitive.png

参考

グラフィックパイプラインの概要(マイクロソフト公式)
DirectX11シェーダー入門 | ZeroGram
☆PROJECT ASURA☆ [Direct3D 11] 『Direct3D 11 再入門』

更新履歴

2019/09/24 : 記事投稿 & 編集
2019/12/29 : 解説記事を投稿
2020/09/26 : 微修正

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
13