فهرست منبع

Added FBX import for bones and bone influences (skin)

Marko Pintera 10 سال پیش
والد
کامیت
120caac66e

+ 1 - 1
BansheeFBXImporter/BansheeFBXImporter.vcxproj

@@ -245,7 +245,7 @@
     <ClInclude Include="Include\BsFBXUtility.h" />
   </ItemGroup>
   <ItemGroup>
-    <ClCompile Include="Source\BsFBXImportdata.cpp" />
+    <ClCompile Include="Source\BsFBXImportData.cpp" />
     <ClCompile Include="Source\BsFBXImporter.cpp" />
     <ClCompile Include="Source\BsFBXPlugin.cpp" />
     <ClCompile Include="Source\BsFBXUtility.cpp" />

+ 2 - 2
BansheeFBXImporter/BansheeFBXImporter.vcxproj.filters

@@ -35,10 +35,10 @@
     <ClCompile Include="Source\BsFBXImporter.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\BsFBXImportdata.cpp">
+    <ClCompile Include="Source\BsFBXUtility.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\BsFBXUtility.cpp">
+    <ClCompile Include="Source\BsFBXImportData.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>

+ 32 - 0
BansheeFBXImporter/Include/BsFBXImportData.h

@@ -31,6 +31,7 @@ namespace BansheeEngine
 
 		Matrix4 localTransform;
 		Matrix4 worldTransform;
+		FbxNode* fbxNode;
 
 		Vector<FBXImportNode*> children;
 	};
@@ -57,6 +58,34 @@ namespace BansheeEngine
 		Vector<FBXBlendShapeFrame> frames;
 	};
 
+	/**
+	 * @brief	Contains data about a single bone in a skinned mesh.
+	 */
+	struct FBXBone
+	{
+		FBXImportNode* node;
+		Matrix4 bindPose;
+	};
+
+	/**
+	 * @brief	Contains a set of bone weights and indices for a single
+	 *			vertex, used in a skinned mesh.
+	 */
+	struct FBXBoneInfluence
+	{
+		FBXBoneInfluence()
+		{
+			for (UINT32 i = 0; i < FBX_IMPORT_MAX_BONE_INFLUENCES; i++)
+			{
+				weights[i] = 0.0f;
+				indices[i] = -1;
+			}
+		}
+
+		float weights[FBX_IMPORT_MAX_BONE_INFLUENCES];
+		INT32 indices[FBX_IMPORT_MAX_BONE_INFLUENCES];
+	};
+
 	/**
 	 * @brief	Imported mesh data.
 	 */
@@ -76,6 +105,9 @@ namespace BansheeEngine
 		Vector<int> smoothingGroups;
 		Vector<FBXBlendShape> blendShapes;
 
+		Vector<FBXBoneInfluence> boneInfluences;
+		Vector<FBXBone> bones;
+
 		MeshDataPtr meshData;
 		Vector<SubMesh> subMeshes;
 

+ 10 - 0
BansheeFBXImporter/Include/BsFBXImporter.h

@@ -82,6 +82,16 @@ namespace BansheeEngine
 		 */
 		void importBlendShapeFrame(FbxShape* shape, const FBXImportMesh& mesh, const FBXImportOptions& options, FBXBlendShapeFrame& outFrame);
 
+		/**
+		 * @brief	Imports skinning information and bones for all meshes.
+		 */
+		void importSkin(FBXImportScene& scene);
+
+		/**
+		 * @brief	Imports skinning information and bones for the specified mesh.
+		 */
+		void importSkin(FBXImportScene& scene, FbxSkin* skin, FBXImportMesh& mesh);
+
 		/**
 		 * @brief	Converts all the meshes from per-index attributes to per-vertex attributes.
 		 *

+ 2 - 1
BansheeFBXImporter/Include/BsFBXPrerequisites.h

@@ -21,4 +21,5 @@
 #define FBXSDK_NEW_API
 #include <fbxsdk.h>
 
-#define FBX_IMPORT_MAX_UV_LAYERS 2
+#define FBX_IMPORT_MAX_UV_LAYERS 2
+#define FBX_IMPORT_MAX_BONE_INFLUENCES 4

+ 138 - 2
BansheeFBXImporter/Source/BsFBXImporter.cpp

@@ -15,6 +15,16 @@
 
 namespace BansheeEngine
 {
+	Matrix4 FBXToNativeType(const FbxAMatrix& value)
+	{
+		Matrix4 native;
+		for (UINT32 row = 0; row < 4; row++)
+			for (UINT32 col = 0; col < 4; col++)
+			native[row][col] = (float)value[col][row];
+
+		return native;
+	}
+
 	Vector4 FBXToNativeType(const FbxVector4& value)
 	{
 		Vector4 native;
@@ -116,7 +126,13 @@ namespace BansheeEngine
 
 		FBXImportScene importedScene;
 		parseScene(fbxScene, fbxImportOptions, importedScene);
-		importBlendShapes(importedScene, fbxImportOptions);
+
+		if (fbxImportOptions.importBlendShapes)
+			importBlendShapes(importedScene, fbxImportOptions);
+
+		if (fbxImportOptions.importSkin)
+			importSkin(importedScene);
+
 		splitMeshVertices(importedScene);
 		
 		Vector<SubMesh> subMeshes;
@@ -273,7 +289,6 @@ namespace BansheeEngine
 			}
 		}
 
-		// TODO - Parse skin
 		// TODO - Parse animation
 	}
 
@@ -288,6 +303,7 @@ namespace BansheeEngine
 		Quaternion rotation((Radian)rotationEuler.x, (Radian)rotationEuler.y, (Radian)rotationEuler.z);
 
 		node->localTransform.setTRS(translation, rotation, scale);
+		node->fbxNode = fbxNode;
 
 		if (parent != nullptr)
 		{
@@ -312,6 +328,7 @@ namespace BansheeEngine
 			FBXImportMesh* splitMesh = bs_new<FBXImportMesh>();
 			splitMesh->fbxMesh = mesh->fbxMesh;
 			splitMesh->referencedBy = mesh->referencedBy;
+			splitMesh->bones = mesh->bones;
 
 			FBXUtility::splitVertices(*mesh, *splitMesh);
 			FBXUtility::flipWindingOrder(*splitMesh);
@@ -914,4 +931,123 @@ namespace BansheeEngine
 			}
 		}
 	}
+
+	void FBXImporter::importSkin(FBXImportScene& scene)
+	{
+		for (auto& mesh : scene.meshes)
+		{
+			FbxMesh* fbxMesh = mesh->fbxMesh;
+
+			UINT32 deformerCount = (UINT32)fbxMesh->GetDeformerCount(FbxDeformer::eSkin);
+			if (deformerCount > 0)
+			{
+				// We ignore other deformers if there's more than one
+				FbxSkin* deformer = static_cast<FbxSkin*>(fbxMesh->GetDeformer(0, FbxDeformer::eSkin));
+				UINT32 boneCount = (UINT32)deformer->GetClusterCount();
+
+				if (boneCount == 0)
+					continue;
+
+				// If only one bone and it links to itself, ignore the bone
+				if (boneCount == 1)
+				{
+					FbxCluster* cluster = deformer->GetCluster(0);
+					if (mesh->referencedBy.size() == 1 && mesh->referencedBy[0]->fbxNode == cluster->GetLink())
+						continue;
+				}
+
+				importSkin(scene, deformer, *mesh);
+			}
+		}
+	}
+
+	void FBXImporter::importSkin(FBXImportScene& scene, FbxSkin* skin, FBXImportMesh& mesh)
+	{
+		Vector<FBXBoneInfluence>& influences = mesh.boneInfluences;
+		influences.resize(mesh.positions.size());
+
+		UnorderedSet<FbxNode*> existingBones;
+		UINT32 boneCount = (UINT32)skin->GetClusterCount();
+		for (UINT32 i = 0; i < boneCount; i++)
+		{
+			FbxCluster* cluster = skin->GetCluster(i);
+			FbxNode* link = cluster->GetLink();
+
+			// The bone node doesn't exist, skip it
+			auto iterFind = scene.nodeMap.find(link);
+			if (iterFind == scene.nodeMap.end())
+				continue;
+
+			mesh.bones.push_back(FBXBone());
+
+			FBXBone& bone = mesh.bones.back();
+			bone.node = iterFind->second;
+
+			FbxAMatrix clusterTransform;
+			cluster->GetTransformMatrix(clusterTransform);
+
+			FbxAMatrix linkTransform;
+			cluster->GetTransformLinkMatrix(linkTransform);
+
+			FbxAMatrix bindPose = linkTransform.Inverse() * clusterTransform;
+			bone.bindPose = FBXToNativeType(bindPose);
+
+			bool isDuplicate = existingBones.insert(link).second;
+			bool isAdditive = cluster->GetLinkMode() == FbxCluster::eAdditive;
+
+			// We avoid importing weights twice for duplicate bones and we don't
+			// support additive link mode.
+			bool importWeights = !isDuplicate && !isAdditive;
+			if (!importWeights)
+				continue;
+
+			double* weights = cluster->GetControlPointWeights();
+			INT32* indices = cluster->GetControlPointIndices();
+			UINT32 numIndices = (UINT32)cluster->GetControlPointIndicesCount();
+			INT32 numVertices = (INT32)influences.size();
+
+			// Add new weights while keeping them in order and removing the smallest ones
+			// if number of influences exceeds the set maximum value
+			for (UINT32 j = 0; j < numIndices; j++)
+			{
+				INT32 vertexIndex = indices[j];
+				float weight = (float)weights[j];
+
+				for (UINT32 k = 0; k < FBX_IMPORT_MAX_BONE_INFLUENCES; k++)
+				{
+					if (vertexIndex < 0 || vertexIndex >= numVertices)
+						continue;
+
+					if (weight >= influences[vertexIndex].weights[k])
+					{
+						for (UINT32 l = FBX_IMPORT_MAX_BONE_INFLUENCES - 2; l >= k; l--)
+						{
+							influences[vertexIndex].weights[l + 1] = influences[vertexIndex].weights[l];
+							influences[vertexIndex].indices[l + 1] = influences[vertexIndex].indices[l];
+						}
+
+						influences[vertexIndex].weights[k] = weight;
+						influences[vertexIndex].indices[k] = i;
+						break;
+					}
+				}
+			}
+		}
+
+		if (mesh.bones.empty())
+			mesh.boneInfluences.clear();
+
+		// Normalize weights
+		UINT32 numInfluences = (UINT32)mesh.boneInfluences.size();
+		for (UINT32 i = 0; i < numInfluences; i++)
+		{
+			float sum = 0.0f;
+			for (UINT32 j = 0; j < FBX_IMPORT_MAX_BONE_INFLUENCES; j++)
+				sum += influences[i].weights[j];
+
+			float invSum = 1.0f / sum;
+			for (UINT32 j = 0; j < FBX_IMPORT_MAX_BONE_INFLUENCES; j++)
+				influences[i].weights[j] *= invSum;
+		}
+	}
 }

+ 1 - 0
BansheeFBXImporter/Source/BsFBXUtility.cpp

@@ -147,6 +147,7 @@ namespace BansheeEngine
 		dest.indices = source.indices;
 		dest.materials = source.materials;
 
+		dest.boneInfluences = source.boneInfluences;
 		dest.positions = source.positions;
 
 		// Make room for minimal set of vertices

+ 0 - 6
TODOExperimentation.txt

@@ -5,14 +5,8 @@ Next:
   - Therefore I am not using it in FBXImporter but I should
 
 Later:
- - Import skeleton
-  - Unity just maps nodes referenced by FbxSkin clusters to bones, it do no separate parsing
  - Import keyframes
   - ?? TODO
- - Import blend weights
-  - FbxSkin
-    -> FbxCluster - One cluster per bone, contains weights for each influenced control point in destination geometry
-	  - Its transform is also the bind pose transfrom for the bone
  - Add methods for calculating normals and tangent frame. This is especially needed for blend shapes as I believe they
    come with tangent frame or normal information. I've marked points in code where this is needed with a TODO.