Chapter1 レンダリングパイプライン入門
レンダリングパイプライン
描画する手順のこと。
必要最低限の手順
- 入力アセンブラー
頂点バッファー、インデックスバッファーなどをグラフィックスメモリからロード - 頂点シェーダー
頂点データをスクリーン空間に変換 - ラスタライザー
塗りつぶすピクセルを決定 - ピクセルシェーダー
ピクセルカラーを決定
この後にドローコールをすれば描画できる。
頂点シェーダー、ピクセルシェーダーは自由なプログラミングが可能。
ラスタライザーはカリング設定の選択程度。
Chapter2 はじめてのシェーダー
VisualStudio2022でビルドする際のエラー解消
VisualStudio2022でビルドする場合、altbase.hが見つからない。
(本書はVisualStudio2019が前提)
ATLをインストールすれば解決する。

頂点シェーダー
// HLSL
VSOutput VSMain(VSInput In)
{
VSOutput vsOut = (VSOutput)0;
// step-1 入力された頂点座標を出力データに代入する
vsOut.pos = In.pos;
// step-2 入力された頂点座標を2倍に拡大する
vsOut.pos.x *= 2.0f;
vsOut.pos.y *= 2.0f;
// step-3 入力されたX座標を1.5倍、Y座標を0.5倍にして出力
vsOut.pos.x *= 1.5f;
vsOut.pos.y *= 0.5f;
return vsOut;
}
代入した出力用データは演算していじれる。
セマンティクス
// HLSL
// 頂点シェーダーへの入力頂点構造体
struct VSInput
{
float4 pos : POSITION;
};
// 頂点シェーダーの出力
struct VSOutput
{
float4 pos : SV_POSITION;
};
: の後はセマンティクス
頂点が持つデータの中で、どのデータを扱う変数なのかを決める
頂点データは座標、色、法線、UVなどを持つ
これを引数で渡されたデータからどう取るかを指示するのがセマンティクス
以下代表例(他にもregisterとかある)
名前 | 説明 |
POSITION | 座標 |
COLOR | 色(頂点カラー) |
NORMAL | 法線ベクトル |
TANGENT | 接ベクトル(法線マップを利用した法線の算出に使用される) |
TEXCOORD | UV座標 |
BLENDWEIGHT | ボーンへのウェイト(ウェイトは頂点毎に決まるからこのデータがある) |
BLENDINDICES | ボーン番号 |
やっててよかったBlender。
出力の時はSV_が付く。多分DirectXの決まり事?
→ 今の所POSITONのみSV_POSITIONでなければならず、他はそのままでよさそう。
// cpp
// パイプラインステートの初期化
void InitPipelineState(PipelineState& pipelineState, RootSignature& rs, Shader& vs, Shader& ps)
{
// 頂点レイアウトを定義する
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
ここでやってるからセマンティクスを定義してる?っぽいからもしかしたらパイプライン毎かも。分からない。
定義してるのか、HLSLが定義しているセマンティクスを、セマンティクスとして扱うための処理なのか、多分後者かな?
シェーダーメソッドのエントリー
// HLSL
// 頂点シェーダー
VSOutput VSMain(VSInput In)
// ピクセルシェーダー
float4 PSMain(VSOutput vsOut) : SV_Target0
こいつらのコールはどこから?
// cpp
// 2. シェーダーをロード
Shader vs, ps;
vs.LoadVS("Assets/shader/sample.fx", "VSMain");
ps.LoadPS("Assets/shader/sample.fx", "PSMain");
ここ!!!
パイプライン(エンジン?アプリ?)毎に設定されるシェーダーロードの第一引数でファイル、第二引数でメソッド名が指定される。
→ Shaderクラスが今回用のエンジンのクラスのためエンジン毎って理解でいいはず。
逆にいうとパイプラインが隠蔽されている場合はエントリーポイントとなるメソッドは必ず一定の名称になるということ。
→ Unityとかがそう。
引数、戻り値で使ってる構造体(VSInputとか)はユーザー設定だから名称は自由かも?まだ分からない。
コール順はパイプラインが決めてる(DirectXかも?)から、頂点→ピクセルの順で呼ばれるのはそういうものと思った方がよさそう
まぁそういうステージ順になるよねって理解でいいと思う。
コール回数
頂点シェーダー、ピクセルシェーダー共に一回のドローコールで頂点数と同じ回数だけコールされる。
↑間違い。
頂点シェーダーは頂点数、ピクセルシェーダーはピクセル数に応じる。
3頂点ならそれぞれ3回ずつという理解でいいらしい。
↑間違い。
3頂点なら頂点シェーダーは3回、描画が640*480ならピクセルシェーダーは307200回呼ばれる。
頂点シェーダーの引数には、おそらく頂点バッファー配列の要素を一つずつ入れてるんだと思う。
だから頂点シェーダーは↓みたいに単に入力座標を返すだけでも異なった座標を出力できるんだと思う。
// hlsl
// 頂点シェーダー
// 1. 引数は変換前の頂点情報
// 2. 戻り値は変換後の頂点情報
VSOutput VSMain(VSInput In)
{
VSOutput vsOut = (VSOutput)0;
vsOut.pos = In.pos;
vsOut.color = In.color; // カラーの情報を出力する
return vsOut;
}
ピクセルシェーダー
ピクセルシェーダーが色を決定する場所。
典型例は陰影。
シェーダーと言われてぱっと思いつくのはここ。
頂点カラーとピクセルカラー
頂点カラーとピクセルカラーは別物。
特に、頂点シェーダーで設定した色はあくまで頂点毎の色。
各頂点違う色が設定されていれば、頂点間のピクセル色はラスタライザーによって補完される。
// hlsl
float4 PSMain(VSOutput vsOut) : SV_Target0
{
// 赤色を出力している
return float4(1.0f, 0.0f , 0.0f, 1.0f);
}
つまり↑みたいな処理はせっかく入ってきた頂点情報 vsOut(厳密にはラスタライザーによって補完された各ピクセル情報のはず?)を使わず赤色に上書きしているという解釈で良い。
だから↓みたいに入力情報をそのまま出力すればラスタライザーによって補完された色を見ることができる
// hlsl
// ピクセルシェーダー
float4 PSMain(VSOutput vsOut) : SV_Target0
{
// step-4 頂点シェーダーから受け取ったカラーを出力する
float4 color;
color.x = vsOut.color.x;
color.y = vsOut.color.y;
color.z = vsOut.color.z;
color.w = 1.0f; // α値は固定
return color;
}
ピクセルシェーダーに渡るデータ
じゃあVSOutPutとして入ってきたposには・・・?
// hlsl
// ピクセルシェーダー
float4 PSMain(VSOutput vsOut) : SV_Target0
{
// test 頂点座標のデータを色にするとどうなる?
float4 color;
color.x = vsOut.pos.x;
color.y = vsOut.pos.y;
color.z = vsOut.pos.z;
color.w = 1.0f; // α値は固定
return color;
}

黄色(1, 1, 0)?!
一定の値が入ってきているってことっぽい?
いや、色は0.0~1.0で正規化されてるからそれ以上の値が入ってきたときに1.0になるからだ。
最大値分かんないけど適当に1000で割っておけばいい色になりそう。
// hlsl
// ピクセルシェーダー
float4 PSMain(VSOutput vsOut) : SV_Target0
{
// test 頂点座標のデータを色にするとどうなる?
float4 color;
color.x = vsOut.pos.x / 1000;
color.y = vsOut.pos.y / 1000;
color.z = vsOut.pos.z / 1000;
color.w = 1.0f; // α値は固定
return color;
}

できた!
上左右の順に(0.5, 0)(0, 1)(1, 1)のイメージっぽい?
やっぱり各ピクセルごとの座標が入ってくるみたい。
Chapter3 シェーダープログラミングの基本
座標の求め方
座標は ワールド行列、カメラ行列、透視変換行列 の乗算で求めることができる。
変換順序
- モデル空間(モデル毎が基準の空間)
↓ 全頂点にワールド行列を乗算 - ワールド空間(ワールド基準点からの位置)
↓ 全頂点にカメラ行列を乗算 - カメラ空間(カメラからの位置)
↓ 全頂点に透視変換行列を乗算 - スクリーン空間(2次元平面に変換)
頂点数が多く計算量も多いため乗算はGPUが担当する。
ただし、行列の作成はCPUが担当する。
行列作成がCPUの理由
- 行列作成のための情報がメインメモリにあるから
- 1フレーム当たりの計算量が少ないから
定数バッファー
CPUで作った行列をGPUに渡すシステム(定数バッファー)を使用する。
定数バッファーに関連するAPIをDirectXが提供している
定数バッファーを使用するための仕組み → ディスクリプタヒープ
イメージ
- 行列:ご飯
- 定数バッファー:お椀
- ディスクリプタヒープ:お盆
サンプルコードのエンジン
サンプルコード中のMainEngineは著者さんが作成したエンジンっぽい。
実際に一から作るのであればDirectXのAPIをラップするようなエンジンが必要なことは覚えておいた方が事故らなさそう。
セマンティクスが使用できるカ所
セマンティクスは構造体宣言時以外でも使える!!
// hlsl
float4x4 g_worldMatrix : register(b0);
↑ がメソッド外にあっても大丈夫。
この場合、float4x4(float行列)のg_worldMatrix変数をbレジスタ0番目(CPU側で設定した定数バッファー)へのアクセス変数として定義する という意味
テクスチャロード
テクスチャは本体の画像データをCPUで読みこんでグラフィックスメモリに入れる。
定数バッファーでbレジスタに入れた時と同様に、tレジスタに入れる。
bはバッファのb、tはテクスチャのtらしい。
定数バッファ同様、テクスチャはディスクリプタヒープに入れる
// cpp
// ディスクリプタヒープに定数バッファを登録
ds.RegistConstantBuffer(0, cb);
// step-3 テクスチャをディスクリプタヒープに登録
ds.RegistShaderResource(0, tex);
つまり正確には以下の関係性
- データ:ご飯(行列、テクスチャなど)
- レジスタ:お椀(bレジスタ、tレジスタ)
- ディスクリプタヒープ:お盆
テクスチャマッピング

// hlsl
// ピクセルシェーダー
float4 PSMain(VSOutput vsOut) : SV_Target0
{
// step-5 テクスチャカラーをサンプリングして返す
float4 texColor = g_texture.Sample(g_sampler, vsOut.uv);
return texColor;
}
各ピクセルごとにテクスチャからUV座標と一致するテクセルの色を取得して返却することでマッピングできる。
3Dモデルのデータ構造
3Dモデルの描画も結局は三角形の集合で可能
→三角形の描画フロー(頂点バッファー、インデックスバッファー、テクスチャ)で行える
1頂点当たりのマテリアル情報、頂点バッファー、インデックスバッファーが全頂点分あれば3Dモデルデータとして役割を果たす
コメント