代码之家  ›  专栏  ›  技术社区  ›  Mich

顶点和索引缓冲区如何在带有顶点、法线和texcoords的Directx11中工作?

  •  0
  • Mich  · 技术社区  · 6 年前

    我不知道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个texcoords,而不是2个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(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的字段。为什么它会影响顶点。为什么顶点和法向坐标/值有效,但texcoorder无效。

    directx如何知道indexbuffer中的哪些不雅引用顶点,哪些引用法线,哪些引用texcoords

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

    DirectX是否自动将步幅内的前3个值指定给顶点,将next3指定给正常值,将next 2指定给texcoordinates?这就是它的工作原理吗?

    谢谢,

    1 回复  |  直到 6 年前
        1
  •  3
  •   Chuck Walbourn    6 年前

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

    这对应于顶点结构,如:

    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的步幅 绑定顶点缓冲区。仍然只有一个 单一的 索引缓冲区,因此特定顶点的所有数据必须在所有三个VBS的同一索引中。

    它将使用类似于以下伪代码的代码:

    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-gons转换为三角形等。