Skip to content

Latest commit

 

History

History
353 lines (274 loc) · 11.9 KB

vertex_input_data_processing.adoc

File metadata and controls

353 lines (274 loc) · 11.9 KB

頂点入力データ処理

本章では、グラフィックスパイプラインを使用する際に、アプリケーションがどのようにデータを頂点シェーダにマッピングするかを理解するために、仕様書の固定機能頂点処理の章の概要を説明します。

また、Vulkan はさまざまな使い方ができるツールであることも忘れてはいけません。以下は、教育目的のための、頂点データをどのようにレイアウトできるかの例です。

バインディングとロケーション

binding は、頂点シェーダが vkCmdDraw* 呼び出し中にデータの読み取りを開始する頂点バッファ内の位置に関連付けられています。binding を変更しても、アプリの頂点シェーダのソースコードを変更する必要は ありません

以下のコード例は、bindings の挙動の図と一致しています。

// この例では、両方のバインディングに同じバッファを使用する
VkBuffer buffers[] = { vertex_buffer, vertex_buffer };
VkDeviceSize offsets[] = { 8, 0 };

vkCmdBindVertexBuffers(
                        my_command_buffer, // commandBuffer
                        0,                 // firstBinding
                        2,                 // bindingCount
                        buffers,           // pBuffers
                        offsets,           // pOffsets
                      );
vertex_input_data_processing_binding

以下の例では、入力データに応じて、bindinglocation の値を設定するさまざまな方法を示しています。

例 A - パックされたデータ

最初の例では、頂点ごとの属性データは次のようになります。

struct Vertex {
    float   x, y, z;
    uint8_t u, v;
};
vertex_input_data_processing_example_a

パイプラインの作成情報は、おおよそ次のようになります。

const VkVertexInputBindingDescription binding = {
    0,                          // binding
    sizeof(Vertex),             // stride
    VK_VERTEX_INPUT_RATE_VERTEX // inputRate
};

const VkVertexInputAttributeDescription attributes[] = {
    {
        0,                          // location
        binding.binding,            // binding
        VK_FORMAT_R32G32B32_SFLOAT, // format
        0                           // offset
    },
    {
        1,                          // location
        binding.binding,            // binding
        VK_FORMAT_R8G8_UNORM,       // format
        3 * sizeof(float)           // offset
    }
};

const VkPipelineVertexInputStateCreateInfo info = {
    1,             // vertexBindingDescriptionCount
    &binding,      // pVertexBindingDescriptions
    2,             // vertexAttributeDescriptionCount
    &attributes[0] // pVertexAttributeDescriptions
};

これを利用した GLSL コードは以下のようになります。

layout(location = 0) in vec3 inPos;
layout(location = 1) in uvec2 inUV;

例 B - パディングとオフセットの調整

この例では、頂点データが密に詰まっておらず、余分なパディングがある場合を検討します。

struct Vertex {
    float   x, y, z, pad;
    uint8_t u, v;
};

唯一の変更点は、パイプライン作成時のオフセットを調整することです。

        1,                          // location
        binding.binding,            // binding
        VK_FORMAT_R8G8_UNORM,       // format
-        3 * sizeof(float)           // offset
+        4 * sizeof(float)           // offset

これで、uv が読み込まれる場所に正しいオフセットが設定されます。

vertex_input_data_processing_example_b_offset

例 C - 非インターリーブ

データがインターリーブされていない場合は以下のようになります。

float position_data[] = { /*....*/ };
uint8_t uv_data[] = { /*....*/ };
vertex_input_data_processing_example_c

この場合、バインディングは2つになりますが、ロケーションは2つのままです。

const VkVertexInputBindingDescription bindings[] = {
    {
        0,                          // binding
        3 * sizeof(float),          // stride
        VK_VERTEX_INPUT_RATE_VERTEX // inputRate
    },
    {
        1,                          // binding
        2 * sizeof(uint8_t),        // stride
        VK_VERTEX_INPUT_RATE_VERTEX // inputRate
    }
};

const VkVertexInputAttributeDescription attributes[] = {
    {
        0,                          // location
        bindings[0].binding,        // binding
        VK_FORMAT_R32G32B32_SFLOAT, // format
        0                           // offset
    },
    {
        1,                          // location
        bindings[1].binding,        // binding
        VK_FORMAT_R8G8_UNORM,       // format
        0                           // offset
    }
};

const VkPipelineVertexInputStateCreateInfo info = {
    2,             // vertexBindingDescriptionCount
    &bindings[0],  // pVertexBindingDescriptions
    2,             // vertexAttributeDescriptionCount
    &attributes[0] // pVertexAttributeDescriptions
};

GLSL コードは例 A と変わりません。

layout(location = 0) in vec3 inPos;
layout(location = 1) in uvec2 inUV;

例 D - 2つのバインディングと3つのロケーション

この例は、bindinglocation が互いに独立していることを説明するものです。

この例では、頂点データは次のようなフォーマットで提供される2つのバッファにレイアウトされています。

struct typeA {
    float   x, y, z; // position
    uint8_t u, v;    // UV
};

struct typeB {
    float x, y, z; // normal
};

typeA a[] = { /*....*/ };
typeB b[] = { /*....*/ };

シェーダのインターフェイスは次のようになります。

layout(location = 0) in vec3 inPos;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in uvec2 inUV;

VkVertexInputBindingDescriptionVkVertexInputAttributeDescription を適宜設定することで、以下のように適切にマッピングされます。

vertex_input_data_processing_example_d
const VkVertexInputBindingDescription bindings[] = {
    {
        0,                          // binding
        sizeof(typeA),              // stride
        VK_VERTEX_INPUT_RATE_VERTEX // inputRate
    },
    {
        1,                          // binding
        sizeof(typeB),              // stride
        VK_VERTEX_INPUT_RATE_VERTEX // inputRate
    }
};

const VkVertexInputAttributeDescription attributes[] = {
    {
        0,                          // location
        bindings[0].binding,        // binding
        VK_FORMAT_R32G32B32_SFLOAT, // format
        0                           // offset
    },
    {
        1,                          // location
        bindings[1].binding,        // binding
        VK_FORMAT_R32G32B32_SFLOAT, // format
        0                           // offset
    },
    {
        2,                          // location
        bindings[0].binding,        // binding
        VK_FORMAT_R8G8_UNORM,       // format
        3 * sizeof(float)           // offset
    }
};
vertex_input_data_processing_example_d_vertex

例 E - 入力属性フォーマットの理解

VkVertxInputAttributeDescription::format が混乱の原因になることがあります。format は、シェーダが読み込むべきデータのサイズタイプを記述するだけです。

VkFormat の値を使う理由は、明確に定義されていて、頂点シェーダの入力レイアウトと一致するためです。

この例では、頂点データは4つの浮動小数点数だけです。

struct Vertex {
    float a, b, c, d;
};

formatoffset の設定により、読み込まれるデータが重なります。

const VkVertexInputBindingDescription binding = {
    0,                          // binding
    sizeof(Vertex),             // stride
    VK_VERTEX_INPUT_RATE_VERTEX // inputRate
};

const VkVertexInputAttributeDescription attributes[] = {
    {
        0,                          // location
        binding.binding,            // binding
        VK_FORMAT_R32G32_SFLOAT,    // format - Reads in two 32-bit signed floats ('a' and 'b')
        0                           // offset
    },
    {
        1,                          // location
        binding.binding,            // binding
        VK_FORMAT_R32G32B32_SFLOAT, // format - Reads in three 32-bit signed floats ('b', 'c', and 'd')
        1 * sizeof(float)           // offset
    }
};

シェーダでデータを読み込むと、重なっている部分の値は同じになります。

layout(location = 0) in vec2 in0;
layout(location = 1) in vec2 in1;

// in0.y == in1.x
vertex_input_data_processing_understanding_format

重要なのは、in1vec2 であるのに対して、入力属性は VK_FORMAT_R32G32B32_SFLOAT であり、完全には一致していないことです。仕様書によると

頂点シェーダのコンポーネントが少ない場合、余分なコンポーネントは破棄されます。

つまりこの場合、ロケーション1の最後の成分(d)は破棄され、シェーダに読み込まれることはありません。

コンポーネント割り当て

仕様書では、Component 割り当てについてさらに詳しく説明されています。以下にその概要をご紹介します。

コンポーネントの埋められ方

VkVertxInputAttributeDescription の各 location は4つのコンポーネントを持っています。上の例では、シェーダ入力のコンポーネント数が少ない場合、format からの余分なコンポーネントが破棄されることを示しました。

Note
Example

VK_FORMAT_R32G32B32_SFLOAT は3つのコンポーネントを持ちますが、vec2 は2つしかありません。

その逆のケースでは、仕様書には次のように書かれています。

フォーマットにG、B、Aのコンポーネントが含まれていない場合、64ビットのデータ型ではない属性は、必要に応じて(0,0,1)で埋められます(フォーマットに応じて1.0f または整数の1を使用)。

例としてはこのようになります。

layout(location = 0) in vec3 inPos;
layout(location = 1) in uvec2 inUV;
vertex_input_data_processing_fill_0

この場合、次のように埋められます。

layout(location = 0) in vec4 inPos;
layout(location = 1) in uvec4 inUV;
vertex_input_data_processing_fill_1