ことれいのもり

0から学ぶHLSLシェーダーの読み方 第1回~パイプラインと頂点シェーダー~

はじめに

この記事はDirectX11で使っている、シェーダー言語のHLSLを読めるようになるために私が学んだことの備忘録です。

実際のHLSLコードを書いて表現するといった内容ではなく、HLSLという言語の見方がさっぱり分からない人がある程度読めるようになることを目的としています。

第一回はパイプラインの基本的な流れと頂点シェーダーのコードを説明します。

前提

言語

レンダリングパイプラインとは

レンダリングパイプラインとは、描画するための機械 のようなものです。

三角形の頂点といったデータを2Dの画面に表示するために計算などを行ないます。

DirectX11の描画のパイプラインを図にすると以下の通りです。

パイプラインの流れの画像

赤色で示した部分が頂点シェーダーです。

頂点のデータを受け取り、変換された情報をラスタライザーに渡します。

入力:頂点データ 出力:変換された頂点の位置などのデータ

頂点シェーダーとピクセルシェーダーの間にラスタライザーというものがあり、映すピクセルを決める処理(深度情報やカリングなど)をしています。

さきほど頂点シェーダーから出力されたデータを受け取り、ピクセルごとにデータを分割し、ピクセルシェーダーへ渡します。

自動で行なわれるので、今回はあるんだなー程度で話を進めていきます。

入力:変換された頂点の位置などのデータ 出力:ピクセルごとに分けられたデータ

青色で示した部分はピクセルシェーダーです。

ラスタライザーから出力されたピクセルごとのデータを受け取り、色を計算し決定します。

入力:ピクセルごとに分けられたデータ 出力:ピクセルの色情報

頂点シェーダーでは赤色で書かれた入力と出力を、ピクセルシェーダーでは青色で書かれた入力と出力をHLSLという言語を使って表します。

実際のコードを見ていきましょう。

頂点シェーダー Vertex Shader

頂点シェーダーの入力と出力のおさらいです。

入力:頂点データ

出力:変換された頂点の位置などのデータ

出力は本来はラスタライザーに渡されますが、自動なのでピクセルシェーダーにデータを渡すとイメージします。

ここまで理解できた上でHLSLコードを見ていきます。

今回のコードは、頂点に変な加工をせずにそのまま出力するだけです。

// 入力部分の構造体定義
struct VertexInput
{
    float3 position : POSITION;  // 頂点の座標
    float4 color : COLOR;        // 頂点の色
};

// 出力部分の構造体定義
struct PixelInput
{
    float4 position : SV_POSITION; // 「ラスタライザーに渡す」頂点の座標
    float4 color : COLOR;          // 頂点の色
};

// 実際の処理
// 出力する構造体 main (入力する構造体) { } というふうに記述
PixelInput main(VertexInput input)
{
    // 出力する構造体を宣言する
    PixelInput output;

    // 座標も色もそのまま渡す
    output.position = float4(input.position, 1.0f);
    output.color = input.color;

    return output;
}

一つずつ説明します。

入力部分の構造体定義

// 入力部分の構造体定義
struct VertexInput
{
    float3 position : POSITION;  // 頂点の座標
    float4 color : COLOR;        // 頂点の色
};

頂点シェーダーの入力部分を定義する構造体で、頂点の座標と色の情報を持っています。

普段のプログラミング言語とは違い、変わった書き方をしています。

これはHLSLの言語でセマンティクスと呼ばれる意味づけをする書き方だからです。

誤解を恐れずに言うと、定数のようなものです。

// セマンティクスの例

POSITION     // 頂点の位置
COLOR        // 頂点の色
NORMAL       // 頂点の法線
TEXCOORD     // テクスチャ座標
SV_POSITION  // ラスタライザーに渡す座標

今回の入力部分では、頂点の座標と色を使います。

position という変数にはPOSITIONを(x, y, z の3つの値を持つので float3 型)、

color という変数にはCOLORを指定しています。(r, g, b, α の4つの値を持つので float4 型)

これで入力部分の構造体定義ができました。

出力部分の構造体定義

// 出力部分の構造体定義
struct PixelInput
{
    float4 position : SV_POSITION; // 「ラスタライザーに渡す」頂点の座標
    float4 color : COLOR;          // 頂点の色
};

続いて頂点シェーダーの出力部分の構造体です。

入力した座標と色をそのまま出力するのでほとんど同じですが、座標のセマンティクスに注目してください。

座標部分が POSITION から SV_POSITION に変わっています。

この SV_POSITION は、ラスタライザに渡すという意味を含んでいるセマンティクスです。

パイプラインの画像をもう一度見てください。

入力されたデータは頂点シェーダーを通り、ラスタライザーへ渡されます。

ラスタライザーは自動で処理を行ないますが、「どこの座標に処理をすれば良いか?」を知る必要があります。 そのため、POSITION ではなく、 SV_POSITIONという値を使っています。

ちなみに、SVというのは System Value (システム値)というDirectXが特別に認識する値という略です。

main部分

// 実際の処理
// 出力する構造体 main (入力する構造体) { } というふうに記述
PixelInput main(VertexInput input)
{
    // 出力する構造体を宣言する
    PixelInput output;

    // 座標も色もそのまま渡す
    output.position = float4(input.position, 1.0f);
    output.color = input.color;

    return output;
}

main部分で頂点シェーダーの処理内容を記述します。

コメントで詳しく書いたので見れば分かると思いますが、入力した座標と色を出力先の変数に代入しているだけです。

他に頂点シェーダーで行ないたい処理がある場合はここに記述していくことになります。

これで頂点シェーダーの説明は終わりです。

まとめると、

頂点シェーダーの入力と出力の構造体を記述し、main部分に処理内容を記述する というものになります。 少しは読めるようになったのではないでしょうか。

おわりに

今回はパイプラインの説明と頂点シェーダーの解説を行ないました。

長くなったのでピクセルシェーダーは次回にします。