【3Dゲーム開発#06】頂点カラーでポリゴンに色をつける

前回までで、ポリゴンを表示して自由に座標変換(2D)できるようになりました。

今後はそれを応用して3D座標変換をして、3Dゲームの絵づくりをしていきます。

なのですが、3D座標変換の前に、これまで表示している三角形に色をのせて、より立体的な構成を把握しやすくしましょう。

そのために、今回は頂点カラーを使って三角形に色付けしていきます。

前回の記事:

【3Dゲーム開発#05】ポリゴンの座標変換② : 移動・回転・スケールの2D変換
前回までで、DirectXMathを使って行列の用意、シェーダーで座標変換、ができました。今回は、「移動」「回転」「スケール」それぞれの2D変換について詳しく見ていきましょう。また、座標変換は、単体で終わるものではなく、移動・回転・スケール...

3Dゲーム開発記事のまとめ:「3Dゲーム開発」

頂点カラーとは

頂点カラーとは、その名の通り頂点単位での色設定です。

頂点(1つの点)に色をつけたところで、ポリゴンの色はどうなるの?

と疑問に思うかもしれませんが、描画処理で頂点間の色は補間され、きれいなグラデーションのような色付けがされます。

いかが各頂点のカラーを、赤、緑、青に設定した三角形の描画結果(本記事の出力結果そのもの)です。

vertex_color

ここで頂点カラーを取り入れる意味

今後3Dモデルの描画をしていくにあたり、頂点データを拡張して扱えることは重要となります。

3Dモデルでは、

  • 頂点座標
  • 頂点ごとの法線
  • テクスチャ座標(UV座標)

といったデータを頂点ごとにもつことが多く、頂点データは単純な座標データではなくなります。

そこで、もっとも出力結果がわかりやすい、頂点カラーを使って、本記事で頂点データの拡張を行った描画・シェーダ処理を行っていきます。

頂点カラーがシェーダー含めて扱えれば、同様の拡張で法線、UVなどもシェーダーで扱うことが可能となるでしょう。

頂点カラー処理

頂点(Vertex)構造体の拡張

まずは頂点に色をもたせられるように、頂点構造体(struct Vertex)を拡張しましょう。

下記のように、ColorプロパティをDirectX::XMFLOAT4型で追加します。

色は、R,G,B,Aの4つの値から構成するため、Float4つで構成できるようにします。

struct Vertex
{
    DirectX::XMFLOAT3 Position = {};
    DirectX::XMFLOAT4 Color = {};

    ...
};

続いて、拡張した頂点データに、前回まで表示していた三角形ポリゴンの各頂点に色を設定します。

冒頭の通り、赤、緑、青を設定、色は[0.0f, 1.0f]でシェーダーで扱うので、この範囲で設定します。

また今回はAlpha値はわかりやすく1.0(不透明)とします。Alphaの適用は[次の記事]()で詳しく扱います。

Triangle::Triangle()
{
    auto sqrt3 = sqrtf(3);
    Vertices[0] = { 0.f, sqrt3 / 3.f, 0.f };
    Vertices[1] = { 0.5f, -sqrt3 / 6.f, 0.f };
    Vertices[2] = { -0.5f, -sqrt3 / 6.f, 0.f };

    constexpr float ALPHA = 1.f;
    Vertices[0].Color = { 1.f, 0.f, 0.f, ALPHA };
    Vertices[1].Color = { 0.f, 1.f, 0.f, ALPHA };
    Vertices[2].Color = { 0.f, 0.f, 1.f, ALPHA };
}

これで頂点データに、カラーをもたせることができました。

頂点バッファへの対応は不要

頂点データは以下の処理で頂点バッファに設定されているのでした。

bool Triangle::CreateVertexBuffer(Renderer& renderer)
{
    D3D11_BUFFER_DESC vertexBufferDesc = {};
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(Vertex) * 3;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

    D3D11_SUBRESOURCE_DATA vertexSubData;
    vertexSubData.pSysMem = Vertices;

    auto hr = renderer.GetDevice()->CreateBuffer(
        &vertexBufferDesc,
        &vertexSubData,
        &VertexBuffer
    );

    ...
}

この処理のとおり、sizeof(Vertex) * 3でサイズを指定してデータを設定しているので、Vertexが拡張されれば自動で頂点バッファにも適切なサイズでデータ設定されます。
ですので頂点バッファに対しては、特に何もいじる必要がありません。

シェーダー向け入力レイアウトの定義の拡張

ここまでで頂点データの拡張準備はできました。

あとはシェーダーでColorを解釈できるようにする必要があります。

シェーダーに渡す頂点データは、入力レイアウト(=どういった頂点データか)を教えて上げる必要がありました(参考:入力レイアウト作成)。

D3D11_INPUT_ELEMENT_DESC構造体は以下の構成(参考:公式ドキュメント)となっています。

typedef struct D3D11_INPUT_ELEMENT_DESC {
  LPCSTR                     SemanticName;
  UINT                       SemanticIndex;
  DXGI_FORMAT                Format;
  UINT                       InputSlot;
  UINT                       AlignedByteOffset;
  D3D11_INPUT_CLASSIFICATION InputSlotClass;
  UINT                       InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;

今回は、頂点バッファには、Position、Colorが1セットとなったVertex構造体単位でデータをセットしているので、上記のAlignedByteOffsetを指定することで、Position、ColorがVertexの中でどこに位置しているかを教えてあげます。

PositionはVertexの先頭に位置するのでOffsetは0、ColorはPositionの後ろにあるので、Positionのサイズ分(=sizeof(XMFLOAT3))である12バイトのオフセットを指定します。

結果以下のような入力レイアウト指定となります。

    D3D11_INPUT_ELEMENT_DESC layout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

シェーダーでの入力・出力構造体の拡張

入力レイアウトの指定が済めば、シェーダーにデータが渡る用意ができたので、あとはシェーダーで使っていきます。

頂点シェーダー

まずは頂点シェーダーです。

これまではPositionのみを入力として、出力もPositionのみだったので、以下のような単純な処理でした。

...

float4 main( float4 pos : POSITION ) : SV_POSITION
{
    return mul(pos, Transform);
}

これが、入力データを拡張して構造体(VS_INPUT)に、そして後工程(ピクセルシェーダー等)にもColorは伝える必要があるので、出力データも同様の構造体(VS_OUTPUT)とします。

頂点シェーダーの出力Positionには「SV_POSITION」というセマンティクスを使うのがルールとなっており必須です。

Colorはそのまま出力すればOKです。ピクセルシェーダーでは頂点間で自動で補間された色が渡ってきます。

結果、頂点シェーダーは以下のような処理となります。

...

struct VS_INPUT {
    float3 Pos : POSITION;   // 頂点座標(モデル座標系)
    float4 Col : COLOR;      // 頂点色
};

struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float4 Col : COLOR;
};

VS_OUTPUT main(VS_INPUT input)
{
    VS_OUTPUT output;

    float4 pos = float4(input.Pos, 1.0);
    output.Pos = mul(pos, Transform);
    output.Col = input.Col;

    return output;
}

ピクセルシェーダー

続いてピクセルシェーダーです。

こちらもこれまではポリゴンを白にしていただけなので、以下のような単純な処理でした。

float4 main() : SV_TARGET
{
    return float4(1.0f, 1.0f, 1.0f, 1.0f);
}

これが、頂点シェーダーから出力結果を受け取るため、VS_OUTPUTと同じ構造体のPS_INPUTとして、入力データを受け取ります。

前述もした通り、ピクセルシェーダーには頂点間で補間されたColor値が渡ってきます。

ですので、渡ってきたColorをそのままピクセルの色として出力します。

struct PS_INPUT {
    float4 Pos : SV_POSITION;
    float4 Col : COLOR;
};

float4 main(PS_INPUT input) : SV_TARGET
{
    return input.Col;
}

結果

以上が頂点カラーの対応でした。

出力結果が以下となります(冒頭でも表示している通りです)。

vertex_color

上述もしたとおり、今後3D描画していくにあたって、頂点データは法線(Normal)やテクスチャ(UV)など、データが拡張されていきます。

ですが、ここでやったカラーと同様に拡張すれば、そういった拡張データも扱えるイメージがつくかと思います。

ソースコード

以下が今回の実装があるコードです。

https://github.com/prog-log/GameDev/blob/master/04-VertexColor/RenderParam.cpp

コメント

タイトルとURLをコピーしました