You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FBX SDK 2020导入蒙皮动画网格法线与Maya显示不一致

FBX SDK 2020 导入蒙皮网格法线偏差问题

问题背景

目前基于FBX SDK 2020开发FBX导入器,校验导入数据准确性的方式为:将FBX文件拖入Maya,对比Maya组件编辑器(Component Editor)中显示的数据与导入器读取结果。
当前仅针对**绑定到骨骼、带动画数据的蒙皮网格(Skinned Mesh)**出现异常:FBX SDK导入的法线数值和Maya显示值存在偏差,顶点位置(Position)数值完全匹配。

验证流程

将FBX拖入Maya后将模型调整至绑定姿势(Bind Pose),在组件编辑器中查看对应数据做对比:

  • 自研FBX导入器读取的法线数据截图:
    自研导入器读取的法线结果
  • Maya组件编辑器中同一顶点的法线数据截图:
    Maya组件编辑器显示的法线数据
    对比可见法线值在百分位小数位存在误差,位置值基本一致。
  • 删除骨骼后从Maya重新导出的新FBX文件同项数据截图:
    删骨后重导出FBX的读取结果

问题复现

使用带骨骼的单位球体模型测试,可稳定复现相同问题,且该模型的法线偏差幅度更大。
以下为基于FBX SDK 2020编写的可复现问题的代码:

namespace test
{
  FbxSample::FbxSample()
  {

  }

  void FbxSample::ImportMesh(FbxNode& node, int depth)
  {
    auto log = std::ofstream("C:\\Projects\\FbxSamples\\log.txt");

    auto& globalTransform = node.EvaluateGlobalTransform(FBXSDK_TIME_INFINITE);

    FbxMesh* fbxMesh = (FbxMesh*)node.GetNodeAttribute();

    int triangleCount = fbxMesh->GetPolygonCount();
    auto meshControlPoints = fbxMesh->GetControlPoints();
    int meshControlPointsCount = fbxMesh->GetControlPointsCount();

    auto normals = fbxMesh->GetElementNormal();

    int vertexCount = 0;
    for (int polygonId = 0; polygonId < triangleCount; ++polygonId)
    {
      int polygonSize = fbxMesh->GetPolygonSize(polygonId);

      for (int j = 0; j < polygonSize; ++j, ++vertexCount)
      {
        int vertexIndex = fbxMesh->GetPolygonVertex(polygonId, j);

        auto position = meshControlPoints[vertexIndex];

        FbxVector4 normal;

        switch (normals->GetMappingMode())
        {
        case FbxGeometryElement::eByControlPoint:
          switch (normals->GetReferenceMode())
          {
          case FbxGeometryElement::eDirect:
            normal = normals->GetDirectArray().GetAt(vertexIndex);
            break;
          case FbxGeometryElement::eIndexToDirect:
          {
            int id = normals->GetIndexArray().GetAt(vertexIndex);
            normal = normals->GetDirectArray().GetAt(id);
          }
          break;
          }
          break;
        case FbxGeometryElement::eByPolygonVertex:
          switch (normals->GetReferenceMode())
          {
          case FbxGeometryElement::eDirect:
            normal = normals->GetDirectArray().GetAt(vertexCount);
            break;
          case FbxGeometryElement::eIndexToDirect:
          {
            int id = normals->GetIndexArray().GetAt(vertexCount);
            normal = normals->GetDirectArray().GetAt(id);
          }
          break;
          }
          break;
        }

        FbxVector4 normal2;
        fbxMesh->GetPolygonVertexNormal(polygonId, j, normal2);

        log << "Vertex Count: " << vertexCount << " Vertex Index: " << vertexIndex << std::endl;
        log << "Position: X: " << position[0] << " Y: " << position[1] << " Z: " << position[2] << " W: " << position[3] << std::endl;
        log << "Normal: X: " << normal[0] << " Y: " << normal[1] << " Z: " << normal[2] << " W: " << normal[3] << std::endl;
        log << "Normal2: X: " << normal2[0] << " Y: " << normal2[1] << " Z: " << normal2[2] << " W: " << normal2[3] << std::endl;
        log << std::endl;
      }
    }

    log.flush();
    log.close();
  }

  void FbxSample::Walk(FbxNode& node, int depth)
  {
    auto nodeAttribute = node.GetNodeAttribute();

    if (nodeAttribute)
    {
      FbxNodeAttribute::EType attributeType = nodeAttribute->GetAttributeType();

      switch (attributeType)
      {
      case fbxsdk::FbxNodeAttribute::eMesh:
        ImportMesh(node, depth);
        break;
      }
    }

    for (int i = 0; i < node.GetChildCount(); i++)
    {
      Walk(*node.GetChild(i), depth + 1);
    }
  }

  void FbxSample::Walk(FbxScene& fbxScene)
  {
    FbxNode* node = fbxScene.GetRootNode();

    if (node)
    {
      for (int i = 0; i < node->GetChildCount(); ++i)
      {
        Walk(*node->GetChild(i), 1);
      }
    }
  }

  void FbxSample::Test()
  {
    FbxManager* sdkManager = FbxManager::Create();
    if (!sdkManager)
    {
      FBXSDK_printf("Error: Unable to create FBX Manager!\n");
      exit(1);
    }
    else
    {
      FBXSDK_printf("Autodesk FBX SDK version %s\n", sdkManager->GetVersion());
    }

    FbxIOSettings* ios = FbxIOSettings::Create(sdkManager, IOSROOT);
    ios->SetBoolProp(IMP_FBX_MATERIAL, true);
    ios->SetBoolProp(IMP_FBX_TEXTURE, true);
    ios->SetBoolProp(IMP_GEOMETRY, true);
    ios->SetBoolProp(IMP_FBX_MODEL, true);
    ios->SetBoolProp(IMP_SKINS, true);
    ios->SetBoolProp(IMP_FBX_ANIMATION, true);
    ios->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true);

    sdkManager->SetIOSettings(ios);

    auto importer = FbxImporter::Create(sdkManager, "");
    importer->Initialize("C:\\Projects\\FbxSamples\\SphereWithBones.fbx", -1, sdkManager->GetIOSettings());
    auto fbxScene = FbxScene::Create(sdkManager, "Scene");

    importer->Import(fbxScene);

    Walk(*fbxScene);
  }
}

代码运行输出如下:

Vertex Count: 0 Vertex Index: 20
Position: X: 0.293893 Y: -0.951057 Z: -0.0954916 W: 0
Normal: X: 0.298177 Y: 0.267987 Z: -0.916119 W: 1
Normal2: X: 0.298177 Y: 0.267987 Z: -0.916119 W: 1

Vertex Count: 1 Vertex Index: 0
Position: X: 0.148778 Y: -0.987688 Z: -0.0483409 W: 0
Normal: X: 0.18352 Y: 0.228479 Z: -0.956095 W: 1
Normal2: X: 0.18352 Y: 0.228479 Z: -0.956095 W: 1

Vertex Count: 2 Vertex Index: 21
Position: X: 0.25 Y: -0.951057 Z: -0.181636 W: 0
Normal: X: 0.246448 Y: 0.378525 Z: -0.892179 W: 1
Normal2: X: 0.246448 Y: 0.378525 Z: -0.892179 W: 1

从输出结果可见,绑定姿势下的单位球体法线本应与顶点位置的X/Y/Z值接近,但当前读取到的法线值完全错误。如果删除同一FBX文件中的骨骼后重新运行上述代码,读取到的法线可与Maya中的数值正确匹配。


内容的提问来源于stack exchange,提问作者Ilan Keshet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:48:15