顶点和索引缓冲区如何在 DirectX11 中与顶点、法线和 Texcoords 一起工作

How do Vertex and Index buffers work in DirectX11 with Vertices, Normals, and Texcoords

我无法弄清楚 DirectX11 如何理解顶点缓冲区中的哪些值是顶点,哪些是法线,哪些是 Texcoords

例如:

以下代码有效,但将模型绘制为全白。但是法线和顶点绘制正确

std::vector<float> vertex_buffer;
for (int i = 0, j = 0; i < num_vertices; i+=3, j+=2)
{
    vertex_buffer.push_back(attrib.vertices[i + 0]);
    vertex_buffer.push_back(attrib.vertices[i + 1]);
    vertex_buffer.push_back(attrib.vertices[i + 2]);
    vertex_buffer.push_back(attrib.normals[i + 0]);
    vertex_buffer.push_back(attrib.normals[i + 1]);
    vertex_buffer.push_back(attrib.normals[i + 2]);
    vertex_buffer.push_back(0.0F);
    vertex_buffer.push_back(0.0F);
    vertex_buffer.push_back(0.0F);
}

std::vector<UINT> index_buffer;
for (int i = 0, j = 0; i < num_indices; i+=3, j+=2)
{
    index_buffer.push_back(shapes[0].mesh.indices[i + 0].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 1].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 2].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 0].normal_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 1].normal_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 2].normal_index);
    index_buffer.push_back(0);
    index_buffer.push_back(0);
    index_buffer.push_back(0);
}

例如,上面的代码产生了这个:

但是如果我开始更改索引缓冲区中 9 个值的最后 3 个值中的任何内容,模型将错误地绘制顶点。

这里我修改为使用texcoords (我使用 TinyObjLoader 导入 o​​bj 文件,我不知道为什么每个顶点有 3 个纹理坐标,而不是 2 个)

std::vector<float> vertex_buffer;
for (int i = 0, j = 0; i < num_vertices; i += 3, j += 2)
{
    vertex_buffer.push_back(attrib.vertices[i + 0]);
    vertex_buffer.push_back(attrib.vertices[i + 1]);
    vertex_buffer.push_back(attrib.vertices[i + 2]);
    vertex_buffer.push_back(attrib.normals[i + 0]);
    vertex_buffer.push_back(attrib.normals[i + 1]);
    vertex_buffer.push_back(attrib.normals[i + 2]);
    vertex_buffer.push_back(attrib.texcoords[i + 0]);
    vertex_buffer.push_back(attrib.texcoords[i + 1]);
    vertex_buffer.push_back(attrib.texcoords[i + 2]);
}

std::vector<UINT> index_buffer;
for (int i = 0, j = 0; i < num_indices; i += 3, j += 2)
{
    index_buffer.push_back(shapes[0].mesh.indices[i + 0].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 1].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 2].vertex_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 0].normal_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 1].normal_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 2].normal_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 0].texcoord_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 1].texcoord_index);
    index_buffer.push_back(shapes[0].mesh.indices[i + 2].texcoord_index);
}

我得到这个结果:

显然,这里不仅仅是受影响的纹理,顶点顺序也被打乱了。但我只更改缓冲区中应该用于 texcoords 的字段。为什么它会影响顶点。为什么顶点和法线 coordinates/values 工作,但 texcoords 不。

DirectX如何知道IndexBuffer中哪些索引是指Vertices,哪些是Normals,哪些是Texcoords

此外,对于这个模型,我必须使用 36 的顶点步幅,当我将条目从 9 移动到 8 并将顶点步幅更改为 32 时,情况更糟。

DirectX 是否会自动将步幅内的前 3 个值分配给顶点,将 next3 分配给法线,然后将下 2 个值分配给 texcoordinates?这是它的工作原理吗?

谢谢,

Direct3D 输入汇编器并不像您假设的那样灵活。它从索引缓冲区中获取 单个 索引,并使用该值从 1 个或多个绑定顶点缓冲区中查找 same 顶点。然后将整个顶点发送到顶点着色器的单次调用。

输入布局告诉你你需要知道的一切。例如,这是一个非常简单的输入布局:

{ "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL",      0, DXGI_FORMAT_R32G32B32_FLOAT,    0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD",    0, DXGI_FORMAT_R32G32_FLOAT,       0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },

这对应于像这样的顶点结构:

struct Vertex
{
    XMFLOAT3 position;
    XMFLOAT3 normal;
    XMFLOAT2 textureCoordinate;
};

在这种情况下,您将绑定一个单个顶点缓冲区到系统,当然还有一个单个索引缓冲区。 VB 的步幅为 sizeof(Vertex) 或 32 字节,这被认为是大多数硬件的最佳大小。

它会使用类似这样的伪代码:

// StartIndexLocation, BaseVertexLocation, IndexCount are DrawIndexed parameters
// stride and offset are IASetVertexBuffers parameters
for(I = 0; I < IndexCount; I++)
{
    uint16_t/uint32_t index = IndexBuffer[I + StartIndexLocation];

    Vertex v = VertexBuffer_Bytes[((index + BaseVertexLocation) * stride) + offset];

    VertexShader(v);
}

您还可以创建一个需要多个 VB 的多流输入布局。这是 3 流输入布局的示例:

{ "SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL",      0, DXGI_FORMAT_R32G32B32_FLOAT,    1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD",    0, DXGI_FORMAT_R32G32_FLOAT,       2, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },

这里对应三个顶点结构:

struct Vertex1
{
    XMFLOAT3 position;
};

struct Vertex2
{
    XMFLOAT3 normal;
};

struct Vertex3
{
    XMFLOAT2 textureCoordinate;
};

对于三个 绑定的顶点缓冲区,您将使用 12、12 和 8 的步长。仍然只有一个 单个 索引缓冲区,因此特定顶点的所有数据必须在所有三个 VB 的相同索引中。

它会使用类似这样的伪代码:

for(I = 0; I < IndexCount; I++)
{
    uint16_t/uint32_t index = IndexBuffer[I + StartIndexLocation];

    Vertex1 v1 = VertexBuffer0_Bytes[((index + BaseVertexLocation) * stride0) + offset0];
    Vertex2 v2 = VertexBuffer1_Bytes[((index + BaseVertexLocation) * stride1) + offset1];
    Vertex3 v3 = VertexBuffer2_Bytes[((index + BaseVertexLocation) * stride2) + offset2];

    VertexShader(v1, v2, v3);
}

虽然 WaveFront OBJ 等几何文件格式和 CAD/3D 艺术程序的内部数据结构通常为每个顶点使用多个索引以获得更紧凑的内存结构,但您不能直接 使用 Direct3D 或 OpenGL 渲染 此类数据。您必须通过重复数据.

将其转换为交错形式
std::vector<XMFLOAT3> positions;
std::vector<XMFLOAT3> normals;
std::vector<XMFLOAT2> texcoords;
// Load these three from the file

std::vector<Vertex> vertexBuffer;
std::vector<uint32_t> indexBuffer;

for each face in WaveFront OBJ:
    for each vertex in the face:
        Vertex v;
        v.position = positions[vertexIndex];
        v.normal = normal[normalIndex];
        v.textureCoordinate = texcoords[textureIndex];

        uint32_t index = AddVertex(vertexIndex, &vertex, vertexCache);
        indexBuffer.push_back(index);

// Helper function to try to minimize vertex duplication        
typedef std::unordered_multimap<UINT, UINT> VertexCache;

uint32_t AddVertex(UINT hash, const Vertex* pVertex, VertexCache& cache)
{
    auto f = cache.equal_range(hash);

    for (auto it = f.first; it != f.second; ++it)
    {
        auto& tv = vertexBuffer[it->second];

        if (0 == memcmp(pVertex, &tv, sizeof(Vertex)))
        {
            return it->second;
        }
    }

    uint32_t index = static_cast<uint32_t>(vertices.size());
    vertexBuffer.emplace_back(*pVertex);

    VertexCache::value_type entry(hash, index);
    cache.insert(entry);
    return index;
}

参见WaveFrontReader.h。虽然我的 reader 实现并不完美,但它确实处理了您的代码忽略的许多问题,例如负索引值、将 n 边形转换为三角形等。