【DirectX12】ピクセルシェーダーと頂点シェーダー間のやりとり

【DirectX12】ピクセルシェーダーと頂点シェーダー間のやりとり

今回確認する物の見た目

こんな感じに表示されるものです。

alt text

DirectX12に振れたことのある方なら一度くらいは見たことがあるかもしれません。UV値をカラーとして出力することでグラデーションのような見た目になっています。具体的には画像左上が0,0、画像右下が1,1となっています。つまり右上は1,0、左下は0,1になっています。ややこしいと思いますが画像にして表示するとこのようになっています。単純明快でわかりやすいですね。

alt text

では具体的にどのような順序でUV値をカラー情報とし、表示しているのでしょうか。詳しく見ていきます。

シェーダーの流れ

実際には頂点シェーダーとピクセルシェーダーの間に多くシェーダーが入ってくるのですが、今回はいったん除外して考えます、そもそもここまでは頂点シェーダーとピクセルシェーダーのみで再現可能な内容です。

DierectX12では頂点シェーダーからピクセルシェーダーへ情報が渡されます。常に頂点シェーダーが頭でピクセルシェーダーが最後になります。最低限これだけ覚えておけばこれから要素が増えてもある程度理解しやすくなるでしょう。

.hlsli

今回は.hlsliというファイルを使い、シェーダー共通で使用する構造体を定義しています。.hlsliを使用することで各シェーダーで共通の定義を使用することができます。例えば私の場合は

struct BasicType
{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
};

のように定義しており、これは頂点座標とUV座標をまとめて構造体にしたものです。この構造体を使用し、任意のデータを頂点シェーダーからピクセルシェーダーへ送っています。

定義方法について

上記の構造体でいろいろ見慣れないものがあると思いますが、簡単に説明しておきます。まず、

float4
float2

がイマイチしっくりこない方もいるのではないでしょうか。これはfloatとその数を示しています。実際の中身は

float2 = (x, y)
float3 = (x, y, z)
float4 = (x, y, z, w)

のようになっています。

次にそれぞれ変数の定義の後ろについている

    : SV_POSITION;
    : TEXCOORD;

これらはセマンティクスと呼ばれるもので、変数に対し「その値が何の情報か」を指定しています。例えば

	: SV_POSITION

の場合は座標を意味し

	: TEXCOORD;

はその値がUV値であることを示しています。

ちなみにプレフィックスにSV_とついているものはシステムで定義されているセマンティクスになり、こちらで変更することはできません。

処理の流れ

実際にグラデーション処理の流れを簡単にですが追ってみます。 流れとしては

GPUヒープの作成(CPU)
↓
CPUからGPUへ情報伝達(CPU)
↓
ドローコールを発行する(CPU)
↓
頂点シェーダーが呼ばれる(GPU)
↓
ラスタライザにより頂点データが分解される(GPU)
↓
ピクセルシェーダーにより色が決定される(GPU)
↓
出力

という流れになります。ここに出てきたラスタライザはピクセルシェーダーへ送る前にいろんなことをしてくれるものですが、現時点では「頂点シェーダーから入ってきた三角形がどのピクセルに掛かっているか判定し、ピクセルシェーダーに送る前の準備を行う、また、座標以外の値を線形補完する」程度の認識で問題ありません。深度計算やカリングなんかも行ってくれますが、現時点では必要ないかと思います。

ここで大事になってくるのが「座標以外の値を線形補完する」という部分で、今回私は

struct Vertex
	{
		DirectX::XMFLOAT3 pos;
		DirectX::XMFLOAT2 uv;
	};

という構造体を作成し、この構造体を

// 頂点定義
Vertex vertices[] =
{
	{{-0.4f,-0.7f, 0.0f}, {0.0f, 1.0f}} , //左下
	{{-0.4f, 0.7f, 0.0f}, {0.0f, 0.0f}} , //左上
	{{ 0.4f,-0.7f, 0.0f}, {1.0f, 1.0f}} , //右下
	{{ 0.4f, 0.7f, 0.0f}, {1.0f, 0.0f}} , //右上
};

のように定義しています。また、セマンティクスは

D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
	{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
	{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};

のようにCPU側で定義しており、これをラスタライザがそれぞれのPositionに対応するUV(TEXCOORD)の間を線形補完してピクセル単位の値を出力してくれています。

以上を踏まえたうえでPixelShaderを確認してみるとこのようになっています。

float4 BasicPS(BasicType input) : SV_TARGET
{
    return float4(input.uv, 1.0f, 1.0f);
}

となっており、ここには各ピクセルのラスタライザにより保管された値がinputされ、最終的に画面へ出力するような形になっています。input.uvがx,y(r,b)で1.0がz(b)となっています。いったん最後の1.0fは気にしなくて問題ありません。

これにより画面全体の青は1で固定で、赤と緑がUV値に合わせて変動、そしてラスタライザによってピクセル単位で各頂点からの線形補完が行われ出力されています。画像にするとわかりやすいかもしれませんが、下記のようになっています。かなりわかりやすいくなっていると思いますがどうでしょうか。

alt text

今回は以上になります。お疲れ様でした。