Search code examples
c++directxmeshindicesfbx

How to read in FBX 2014 indices properly for DirectX?


I've been having this very annoying problem for the past weekend. I have to be able to to read in a triangulated 2014 FBX file that contains a mesh exported from Maya and read in it's vertices and indices to be passed to the renderer (DirectX) for the DrawIndexed call.

So the way I have it setup is that there is a TUniqueVertex structure

struct TUniqueVertex
{
    XMFLOAT3 m_vPosition,   // Vertex positions
    XMFLOAT3 m_vNormal,     // Vertex normals
    XMFLOAT2 m_UVs          // Vertex UVs
    int m_nControlPointIndex, // Control Point Index
 }

This code here, will load in the FbxMesh into a CMesh class. Which holds the mesh's vertices and indices.

 class CMesh
 {
     ...
    vector<TUniqueVertex> m_vVertices;
    vector<unsigned int> m_vIndices;
 }

The code to load into the CMesh:

bool LoadMesh(FbxMesh* pMesh, CMesh cMesh)
{
    int polygonCount = pMesh->GetPolygonCount();        
    FbxVector4* controlPoints = pMesh->GetControlPoints();
    int controlPointCount = pMesh->GetControlPointsCount();

    int vertexID = 0;

    for (int polygon = 0; polygon < polygonCount; polygon++)
    {
        int polyVertCount = pMesh->GetPolygonSize(polygon);

    for (int polyVert = 0; polyVert < polyVertCount; polyVert++)
    {
        CMesh::TUniqueVertex uniqueVert;

        int cpIndex = pMesh->GetPolygonVertex(polygon, polyVert);

        // Grab our CP index as well our position information
        uniqueVert.m_nControlPointIndex = cpIndex;
        uniqueVert.m_vPosition = 
        XMFLOAT3((float)controlPoints[cpIndex].mData[0],
                 (float)controlPoints[cpIndex].mData[1],
                 (float)controlPoints[cpIndex].mData[2]);

        // Grab UVs
        int uvElementCount = pMesh->GetElementUVCount();

        for (int uvElement = 0; uvElement < uvElementCount; uvElement++)
        {
            FbxGeometryElementUV* geomElementUV = pMesh>GetElementUV(uvElement);

            FbxLayerElement::EMappingMode mapMode = geomElementUV->GetMappingMode();
            FbxLayerElement::EReferenceMode refMode = geomElementUV->GetReferenceMode();

            int directIndex = -1;

            if (FbxGeometryElement::eByControlPoint == mapMode)
            {
                if (FbxGeometryElement::eDirect == refMode)
                {
                    directIndex = cpIndex;
                }
                else if (FbxGeometryElement::eIndexToDirect == refMode)
                {
                    directIndex = geomElementUV->GetIndexArray().GetAt(cpIndex);
                }
            }
            else if (FbxGeometryElement::eByPolygonVertex == mapMode)
            {
                if (FbxGeometryElement::eDirect == refMode || FbxGeometryElement::eIndexToDirect == refMode)
                {
                    directIndex = pMesh->GetTextureUVIndex(polygon, polyVert);
                }

            }

            // If we got a UV index
            if (directIndex != -1)
            {
                FbxVector2 uv = geomElementUV->GetDirectArray().GetAt(directIndex);

                uniqueVert.m_UVs = XMFLOAT2( (float)uv.mData[0],
                                             (float)uv.mData[1]);
            }
        }

        // Grab normals
        int normElementCount = pMesh->GetElementNormalCount();

        for (int normElement = 0; normElement < normElementCount; normElement++)
        {
            FbxGeometryElementNormal* geomElementNormal = pMesh->GetElementNormal(normElement);

            FbxLayerElement::EMappingMode mapMode = geomElementNormal->GetMappingMode();
            FbxLayerElement::EReferenceMode refMode = geomElementNormal->GetReferenceMode();

            int directIndex = -1;

            if (FbxGeometryElement::eByPolygonVertex == mapMode)
            {
                if (FbxGeometryElement::eDirect == refMode)
                {
                    directIndex = vertexID;
                }
                else if (FbxGeometryElement::eIndexToDirect == refMode)
                {
                    directIndex = geomElementNormal->GetIndexArray().GetAt(vertexID);
                }
            }

            // If we got an index
            if (directIndex != 1)
            {
                FbxVector4 norm = geomElementNormal->GetDirectArray().GetAt(directIndex);

                uniqueVert.m_vNormal = XMFLOAT3((float)norm.mData[0],
                                                (float)norm.mData[1],
                                                (float)norm.mData[2]);

            }
        }

        // Unique Vertices stuff
        vector<CMesh::TUniqueVertex>& uniqueVerts = cMesh.GetVertices();

        size_t size = uniqueVerts.size();
        size_t i;

        for (i = 0; i < size; i++)
        {
            if (uniqueVert == uniqueVerts[i])
            {
                break;
            }
        }

        if (i == size)
        {
            uniqueVerts.push_back(uniqueVert);
        }

        cMesh.GetIndices().push_back(i);
        ++vertexID;
    }
}

return true;
}

Doing this method loads in the vertices, normals, and UVs correctly. However, when loading in the indices and passing the cMesh.GetIndices() count to the DrawIndexed call, the indices are completely out of order and looks like this in the graphic debugger:

Graphic Debugger

Incase anyone is wondering, the indices are as follows:

012, 023, 456, 657, 891, 081, 011, 121, 314, 121, 415, 161, 718, 171, 918, 202, 122, 212, 023

And here is the test .fbx that I'm trying to load the information from:

Cube.fbx direct download

If anyone can help me out with this, I'd be extremely grateful as this issue has caused me so much stress :D


Solution

  • Are you using left-handed view coordinates or right-handed view coordinates in your application (see link)? If you are using left-handed view coordinates, you need to flip the winding of each triangle in the index buffer.

        assert( (indices.size() % 3) == 0 );
        for( auto it = indices.begin(); it != indices.end(); it += 3 )
        {
            std::swap( *it, *(it+2) );
        }
    

    You may also need to flip the texture coordinates as well:

        for( auto it = vertices.begin(); it != vertices.end(); ++it )
        {
            it->textureCoordinate.x = ( 1.f - it->textureCoordinate.x );
        }
    

    Historically most DirectX applications use left-handed view coordinates. XNA Game Studio and most OpenGL applications use right-handed view coordinates.

    BTW, this is why both -fliptriangles+ and -invertvtexcoord+ are enabled by default in the Samples Content Exporter for SDKMESH.