【DX12を学びたい】2D描画をスクリーン座標で描画できるようにしたい

座標変換の結果 DirextX

前ページの処理を使い、2D表示を修正していきます。

P1:定数バッファーの準備
  定数バッファーの作成
  定数バッファービューの作成
P2:表示クラスの修正
  スクリーン→ウィンドウ座標に変換して描画

スポンサーリンク

表示クラスの修正

テクスチャ表示用のSpriteクラスを修正します。

ヘッダーファイル

定数バッファー関連と位置、拡縮、回転の処理を追加しています。

#pragma once

#include "Texture.h"

// 画像表示のクラス
class Sprite
{
public:
	Sprite(float fAlpha);
	~Sprite();

	// 初期化
	bool Initialize(ID3D12Device* pDevice, const wchar_t* pFile);
	// 更新
	void Update(ID3D12GraphicsCommandList* pCommandList);
	// 描画
	void Draw(ID3D12GraphicsCommandList* pCommandList);

	// 各種設定
	void SetPosition(int x, int y);
	void SetScale(float x, float y);
	void SetRotateZ(float degrees);

private:
	// バッファーの作成
	bool CreateBuffer(ID3D12Device* pDevice);
	// マップ
	bool Map();
	// バッファービューの設定
	void SetBufferView();
	// 定数バッファービューの設定
	void CreateConstantBufferView(ID3D12Device* pDevice);
	// ディスクリプタヒープの作成
	bool CreateDescriptorHeap(ID3D12Device* pDevice);

	// 行列の更新
	void UpdateMatrix();

private:
	// 頂点情報
	struct Vertex
	{
		XMFLOAT3 Pos;
		XMFLOAT4 Color;
		XMFLOAT2 Uv;
	};
	Vertex		m_Vertices[4];
	unsigned short	m_IndexData[6];

	ID3D12Resource*			m_pVertexBuffer;
	ID3D12Resource*			m_pIndexBuffer;
	ID3D12Resource*			m_pConstantBuffer;

	D3D12_VERTEX_BUFFER_VIEW	m_VertexBufferView;
	D3D12_INDEX_BUFFER_VIEW		m_IndexBufferView;

	ID3D12DescriptorHeap*		m_pDescHeap;
	UINT				m_HandleIncrementSize;

	Texture*		m_pTexture;
	XMMATRIX*		m_pMatrix;

	XMINT2			m_Position;
	XMFLOAT2		m_Scale;
	float			m_RotateZ;
};

 

C++ファイル

ポイントはInitializeで頂点情報を修正している箇所です。テクスチャ幅をセットしておきます。

// コンストラクタ
Sprite::Sprite(float fAlpha)
    : m_IndexData{ 0,1,2,0,2,3 }
    , m_pVertexBuffer(nullptr)
    , m_pIndexBuffer(nullptr)
    , m_pConstantBuffer(nullptr)
    , m_VertexBufferView{}
    , m_IndexBufferView{}
    , m_pDescHeap(nullptr)
    , m_pTexture(nullptr)
    , m_HandleIncrementSize(0)
    , m_pMatrix(nullptr)
    , m_Position{ 0,0 }
    , m_Scale(1.0f, 1.0f)
    , m_RotateZ(0.f)
{
    // 頂点情報
    m_Vertices[0].Pos = XMFLOAT3(-1.0f, 1.0f, 0.0f);
    m_Vertices[1].Pos = XMFLOAT3(1.0f, 1.0f, 0.0f);
    m_Vertices[2].Pos = XMFLOAT3(1.0f, -1.0f, 0.0f);
    m_Vertices[3].Pos = XMFLOAT3(-1.0f, -1.0f, 0.0f);
    m_Vertices[0].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, fAlpha);
    m_Vertices[1].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, fAlpha);
    m_Vertices[2].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, fAlpha);
    m_Vertices[3].Color = XMFLOAT4(1.0f, 1.0f, 1.0f, fAlpha);
    m_Vertices[0].Uv = XMFLOAT2(0.0f, 0.0f);
    m_Vertices[1].Uv = XMFLOAT2(1.0f, 0.0f);
    m_Vertices[2].Uv = XMFLOAT2(1.0f, 1.0f);
    m_Vertices[3].Uv = XMFLOAT2(0.0f, 1.0f);
}

// デストラクタ
Sprite::~Sprite()
{
    SAFE_DELETE(m_pTexture);
    SAFE_RELEASE(m_pConstantBuffer);
    SAFE_RELEASE(m_pIndexBuffer);
    SAFE_RELEASE(m_pVertexBuffer);
    SAFE_RELEASE(m_pDescHeap);
}

// 初期化
bool Sprite::Initialize(ID3D12Device* pDevice, const wchar_t* pFile)
{
    // ディスクリプタヒープの作成
    CreateDescriptorHeap(pDevice);
    
    // テクスチャ(リソース)の作成
    m_pTexture = new Texture();
    if (m_pTexture->CreateTexture(pDevice, pFile) == false) {
        return false;
    }
    // SRVを作成
    m_pTexture->CreateSRV(pDevice, m_pDescHeap->GetCPUDescriptorHandleForHeapStart());

    // 頂点位置をテクスチャサイズに修正
    float width = m_pTexture->GetWidth();
    float height = m_pTexture->GetHeight();
    m_Vertices[0].Pos = XMFLOAT3(-width, height, 0.0f);
    m_Vertices[1].Pos = XMFLOAT3(width, height, 0.0f);
    m_Vertices[2].Pos = XMFLOAT3(width, -height, 0.0f);
    m_Vertices[3].Pos = XMFLOAT3(-width, -height, 0.0f);

    if (CreateBuffer(pDevice) == false) {
        return false;

    }
    if (Map() == false) {
        return false;

    }

    SetBufferView();


    // CBVを作成
    CreateConstantBufferView(pDevice);

    return true;
}

// 更新
void Sprite::Update(ID3D12GraphicsCommandList* pCommandList)
{
    if (m_pTexture) {
        m_pTexture->Update(pCommandList);
    }

    UpdateMatrix();
}

 

ディスクリプタヒープ

SRVとCBVで2つ必要なので、NumDescriptorsを修正します。

// ディスクリプタヒープの作成
bool Sprite::CreateDescriptorHeap(ID3D12Device* pDevice)
{
    D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
    // 2つ
    heapDesc.NumDescriptors = 2;
    // SRVとCBV用の
    heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    // シェーダーから見える
    heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;

    // ディスクリプタヒープを作成
    HRESULT result = pDevice->CreateDescriptorHeap(
        &heapDesc,
        IID_PPV_ARGS(&m_pDescHeap)
    );
    if (result == S_FALSE)  return false;

    return true;
}

 

バッファー関連

バッファー作成

バッファー作成の処理はほとんど同じなので、関数にしてまとめました。定数バッファーのときはサイズを256の倍数に変更します。

// バッファーの作成
bool Sprite::CreateBuffer(ID3D12Device* pDevice)
{
    if (pDevice == nullptr) return false;

    const UINT64 vertexBufferSize = sizeof(m_Vertices);

    // 頂点バッファーを作成
    HRESULT result = CreateDataResource(pDevice, &m_pVertexBuffer, vertexBufferSize, false);
    if(result == S_FALSE) return false;

    // インデックスバッファーを作成
    result = CreateDataResource(pDevice, &m_pIndexBuffer, sizeof(m_IndexData), false);
    if(result == S_FALSE) return false;

    // 定数バッファーを作成
    result = CreateDataResource(pDevice, &m_pConstantBuffer, sizeof(XMMATRIX), true);
    if(result == S_FALSE) return false;

    return true;
}
HRESULT CreateDataResource(ID3D12Device* pDevice, ID3D12Resource** ppResource, UINT64 size, bool bConstant)
{
    HRESULT result;

    // 定数バッファは256でアラインメントする
    if (bConstant){
        size = (size + 0xff) & ~0xff;
    }

    // ヒープのプロパティ
    D3D12_HEAP_PROPERTIES prop;
    // ヒープの種類
    prop.Type = D3D12_HEAP_TYPE_UPLOAD;     // cpuは書き込み、gpuは読み取り
    // CPUページプロパティ
    prop.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; // プロパティ不明
    // メモリプール
    prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;  // プール不明
    // マルチアダプター関連
    prop.CreationNodeMask = 1;
    prop.VisibleNodeMask = 1;

    // リソースの設定
    D3D12_RESOURCE_DESC desc;
    // リソースのディメンション
    desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    // 配置の指定
    desc.Alignment = 0;
    // バッファーサイズ
    desc.Width = size;
    desc.Height = 1;
    // リソース深さ
    desc.DepthOrArraySize = 1;
    // MIPレベル
    desc.MipLevels = 1;
    // リソースデータの形式
    desc.Format = DXGI_FORMAT_UNKNOWN;  // データ関係はこれでいい
    // マルチサンプリングの設定
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    // テクスチャのレイアウト
    desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;   // データを連続して配置する
    // オプション
    desc.Flags = D3D12_RESOURCE_FLAG_NONE;  // なし

    // リソースを作成
    result = pDevice->CreateCommittedResource(
        &prop,
        D3D12_HEAP_FLAG_NONE,               // オプションなし
        &desc,
        D3D12_RESOURCE_STATE_GENERIC_READ,  // 読み取り状態
        nullptr,                            // クリアカラー
        IID_PPV_ARGS(ppResource)
    );

    return result;
}

 

バッファービュー

コンスタントバッファービューの作成時に注意が必要です。

今回はGetCPUDescriptorHandleForHeapStart()の位置でSRVを作成しているので、SRVのサイズ分ポインタ位置をズラしてからCBVを作成しています。

// 頂点バッファービューの設定
void Sprite::SetBufferView()
{
    // バッファーの仮想アドレス
    m_VertexBufferView.BufferLocation = m_pVertexBuffer->GetGPUVirtualAddress();
    // 1頂点のバイトサイズ
    m_VertexBufferView.StrideInBytes = sizeof(m_Vertices[0]);
    // バッファーのバイトサイズ
    m_VertexBufferView.SizeInBytes = sizeof(m_Vertices);

    // インデックス
    // バッファーの仮想アドレス
    m_IndexBufferView.BufferLocation = m_pIndexBuffer->GetGPUVirtualAddress();
    // バッファーのフォーマット
    m_IndexBufferView.Format = DXGI_FORMAT_R16_UINT;
    // バッファーのバイトサイズ
    m_IndexBufferView.SizeInBytes = sizeof(m_IndexData);
}

// 定数バッファービューの作成
void Sprite::CreateConstantBufferView(ID3D12Device* pDevice)
{
    // SRVのサイズを取得
    m_HandleIncrementSize = pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    
    // SRV分をズラす
    D3D12_CPU_DESCRIPTOR_HANDLE handle = m_pDescHeap->GetCPUDescriptorHandleForHeapStart();
    handle.ptr += m_HandleIncrementSize;

    // CBVを作成
    D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
    cbvDesc.BufferLocation = m_pConstantBuffer->GetGPUVirtualAddress();
    cbvDesc.SizeInBytes = static_cast<UINT>(m_pConstantBuffer->GetDesc().Width);
    pDevice->CreateConstantBufferView(&cbvDesc, handle);
}

 

Map

サンプルによると定数バッファーはUnmapしなくてもいいと書かれていたので、Mapしたままにしておきます。

// マップ
bool Sprite::Map()
{
    UINT bufferSize = sizeof(m_Vertices);

    // リソースデータのポインターを取得
    UINT8* pDataBegin;
    HRESULT result = m_pVertexBuffer->Map(
        0,          // インデックス番号
        nullptr,    // リソース全体
        reinterpret_cast<void**>(&pDataBegin)
    );
    IS_S_FALSE(result);
    // バッファーに情報をコピー
    memcpy(pDataBegin, m_Vertices, bufferSize);
    // 取得したポインターを無効にする
    m_pVertexBuffer->Unmap(0, nullptr);

    // インデックス
    bufferSize = sizeof(m_IndexData);
    result = m_pIndexBuffer->Map(
        0,          // インデックス番号
        nullptr,    // リソース全体
        reinterpret_cast<void**>(&pDataBegin)
    );
    IS_S_FALSE(result);
    // バッファーに情報をコピー
    memcpy(pDataBegin, m_IndexData, bufferSize);
    // 取得したポインターを無効にする
    m_pIndexBuffer->Unmap(0, nullptr);

    // コンスタント
    result = m_pConstantBuffer->Map(
        0,
        nullptr,
        reinterpret_cast<void**>(&m_pMatrix)
    );
    *m_pMatrix = XMMatrixIdentity();
    

    return true;
}

 

描画

テクスチャと定数バッファーがあるので、2回SetGraphicsRootDescriptorTableを呼んでいます。ハンドルのポインタ位置に注意です。

// 描画
void Sprite::Draw(ID3D12GraphicsCommandList* pCommandList)
{
    if (pCommandList == nullptr || m_pTexture == nullptr) return;
    if (!m_pTexture->IsCopied())  return;

    // ディスクリプタヒープの設定
    ID3D12DescriptorHeap* ppHeaps[] = { m_pDescHeap };
    pCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
    
    D3D12_GPU_DESCRIPTOR_HANDLE handle = m_pDescHeap->GetGPUDescriptorHandleForHeapStart();

    // t0に設定
    pCommandList->SetGraphicsRootDescriptorTable(0, handle);

    // b0に設定
    handle.ptr += m_HandleIncrementSize;
    pCommandList->SetGraphicsRootDescriptorTable(1, handle);

    // 3点で1面の情報
    pCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    // 頂点バッファービューの設定
    pCommandList->IASetVertexBuffers(
        0,                  // インデックス
        1,                  // ビューの数
        &m_VertexBufferView
    );
    // インデックスバッファーの設定
    pCommandList->IASetIndexBuffer(&m_IndexBufferView);
    // インデックスで描画
    pCommandList->DrawIndexedInstanced(
        _countof(m_IndexData),  // インデックスの数
        1,                      // 描画するインスタンスの数
        0,                      // 最初のインデックス
        0,                      // 各インデックスに追加する頂点の値
        0                       // 一旦0
    );
}

 

スクリーン→ウィンドウ座標に変換して描画

左上右下
ウィンドウ座標x = -1, y = 1x = 1, y = -1
スクリーン座標x = 0, y = 0x = size, y = size
sizeはウィンドウのサイズ

上記に合うように行列を使って変換します。

例)ウィンドウが200×100でテクスチャが50×50の場合で考えます

 

拡縮

まずは大きさからです。

普通に〇倍する行列を用意すればいいだけですが、Initializeで頂点位置をテクスチャの幅にセットしているので、このままだとウィンドウ座標(-50~50)で表示されます。

なので、スクリーン座標で設定されているデータをウィンドウ座標に変換する行列も必要になります。

と言っても、ウィンドウサイズで割るだけです。

x = 1 / ウィンドウ幅
y = 1 / ウィンドウ高さ

例で計算すると
x = 1 / 200 = 0.005
y = 1 / 100 = 0.01

乗算したら
x = 50 × 0.005 = 0.25
y = 50 × 0.01 = 0.5

ウィンドウ座標(-0.25~0.25, 0.5~-0.5)で、正しい大きさになります。

あとは、XMMatrixScalingにセットして行列を作るだけです。

 

位置

次は位置です。

スクリーン座標(0,0)はウィンドウ座標(-1,1)で、スクリーン座標(ウィンドウ幅,ウィンドウ高さ)はウィンドウ座標(1,-1)になるような計算式を考えます。

x = -1.f + (スクリーン座標のx * 2) / ウィンドウ幅
y = 1.f – (スクリーン座標のy * 2) / ウィンドウ高さ

例で計算すると

(0, 0) = (-1, 1)
x = -1.f + 0 / 200
y = 1.f – 0 / 100

(100, 50) = (0, 0)
x = -1.f + 200 / 200
y = 1.f – 100 / 100

(200, 100) = (1, -1)
x = -1.f + 400 / 200
y = 1.f – 200 / 100

 

上記の計算で表示させると、テクスチャの中心が(0,0)に表示されます。テクスチャの左上を(0,0)にしたい場合はテクスチャサイズを加算しましょう。

x = -1.f + (スクリーン座標のx * 2 + テクスチャ幅) / ウィンドウ幅
y = 1.f – (スクリーン座標のy * 2 + テクスチャ高さ) / ウィンドウ高さ

真ん中が中心のサンプル
テクスチャの中心が(0,0)ver
左上が中心のサンプル
テクスチャの左上が(0,0)ver

位置計算は好みの方で良いですが、今回は左上が(0,0)の方で進めます。

XMMatrixTranslationにセットして行列を作りましょう。

 

回転

XMMatrixRotationZを使います。

角度(ラジアン)を入れるだけでOKです。

 

行列をかける

これまでの行列を掛けていきます。

行列は普通の掛け算と違って、掛ける順番が違うと結果が変わるので注意です!

matrix = スケール × 回転 × ウィンドウ座標に戻すスケール × 位置

「スケール」「ウィンドウ座標に戻すスケール」を分けているのは、回転をする前にウィンドウ座標に戻すとウィンドウのサイズで結果が変わってしまうからです。

// 行列の更新
void Sprite::UpdateMatrix()
{
    float width = static_cast<float>(m_pTexture->GetWidth()) * m_Scale.x;
    float height = static_cast<float>(m_pTexture->GetHeight()) * m_Scale.y;

    // 移動
    float x = -1.f + (static_cast<float>(m_Position.x * 2) + width) / static_cast<float>(WINDOW_WIDTH);
    float y = 1.f - (static_cast<float>(m_Position.y * 2) + height) / static_cast<float>(WINDOW_HEIGHT);
    XMMATRIX pos = XMMatrixTranslation(x, y, 0.0f);

    // 拡縮
    XMMATRIX scale = XMMatrixScaling(
        m_Scale.x,
        m_Scale.y,
        1.f
    );
    // ウィンドウ座標にする
    XMMATRIX windowScale = XMMatrixScaling(
        1.0f / static_cast<float>(WINDOW_WIDTH),
        1.0f / static_cast<float>(WINDOW_HEIGHT),
        1.f
    );

    // 回転
    XMMATRIX rot = XMMatrixRotationZ(m_RotateZ);

    // 行列をかけ合わせる
    XMMATRIX mat = scale * rot * windowScale * pos;
    
    // XMMATRIX:行優先(row major)
    // HLSL:列優先(column major)
    // HLSLに合わせるので転置する
    *m_pMatrix = XMMatrixTranspose(mat);
}

// 各種設定
void Sprite::SetPosition(int x, int y)
{
    m_Position.x = x;
    m_Position.y = y;
}
void Sprite::SetScale(float x, float y)
{
    m_Scale.x = x;
    m_Scale.y = y;
}
void Sprite::SetRotateZ(float degrees)
{
    // ラジアンに変換しておく
    m_RotateZ = XMConvertToRadians(degrees);
}

コメントで書いてある通りですが、HLSLでデータを参照するため、XMMatrixTransposeで転置しています。

 

さいごに

位置、スケール、回転がセットできるようになり、少しは使いやすいものになったのではないでしょうか!

次はUVアニメに対応したいと思っています。

かれいど

ゲームをしたり、作ったり
色々な事に挑戦していきたい!

サッカー観戦も趣味で
主にJ1リーグを観ています。

かれいどをフォローする
DirextX
スポンサーリンク
シェアする
かれいどブログ

コメント