顶点和索引缓冲区如何在具有顶点,法线和Texcoords的DirectX11中工作

时间:2018-07-10 22:14:17

标签: c++ windows visual-studio directx direct3d

我无法弄清楚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);
}

例如,上面的代码将产生以下结果: enter image description here

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

在这里我修改它以使用texcoords (我使用TinyObjLoader导入obj文件,但我不知道为什么每个顶点有3个texcoord,而不是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);
}

我得到这个结果:

enter image description here

很明显,这里不仅影响了纹理,而且顶点顺序也被弄乱了。但是我只改变了应该用于texcoords的缓冲区中的字段。为什么会影响顶点。为什么顶点和法线坐标/值有效,但texcoords无效。

DirectX如何知道IndexBuffer中的哪些索引是指顶点,哪些索引是指法线,​​哪些索引是Texcoords

此外,对于该模型,我必须使用36的Vertex Stride,当我将条目从9移到8并将veve stride更改为32时,情况更糟。

DirectX是否自动将跨步的前3个值分配给顶点,将next3分配给法线,然后将2个值分配给texcoordinates?这是怎么工作的?

谢谢

1 个答案:

答案 0 :(得分:3)

Direct3D输入汇编器的灵活性不如您想象的那么灵活。它从索引缓冲区中获取一个单个索引,并使用该值从1个或多个绑定的顶点缓冲区中查找相同顶点。然后将整个顶点发送到顶点着色器的单次调用。

输入布局会告诉您所有您需要了解的内容。例如,这是一个非常简单的输入布局:

{ "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 },

这对应于如下的Vertex结构:

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。尽管我的阅读器实现并不完美,但它确实可以解决代码忽略的许多问题,例如负索引值,将n形转换为三角形等。