FBX SDK的使用:读取Mesh

读取顶点数据

要将一个Mesh渲染出来,必须要有顶点的位置,法线,UV等顶点属性,和三角面的顶点索引数组。在提取这些数据之前,先理解FBX SDK里面的几个概念:

  1. Control Point 顶点的位置,就是x,y,z坐标;
  2. Polygon Vertex Control Point的索引,Control Point存在一个数组里面,根据这个索引去获取它的值;
  3. Polygon 表示一个面,可以是三角面,或多边形面,这里只出去三角面,游戏面也都是三角面;

法线,UV等都是FbxMesh的几何元素,可以通过FbxMesh::GetElementNormal(), FbxMesh::GetElementUV()接口去获取,我们知道可能有多层UV,这个接口可以传一个int参数去表示获取的是第几层UV,法线也是同理。这里我们只读取第0层法线和UV。

法线,UV的读取和位置相似,都是先获取一个索引,然后通过索引去数组里面查找对应的值,但是索引和数组要根据映射和引用模式去获取。

MappingMode,对于法线和UV一般使用下面两种模式

  • eByControlPoint:和control point的索引相同,就是上面的Polygon Vertex;
  • eByPolygonVertex:顶点的索引,不好解释,直接看下面的代码

ReferenceMode,数据放在DirectArray里面,IndexArray存储DirectArray的索引

  • eDirect:直接通过DirectArray读取
  • eIndex:兼容旧版本,同eIndexToDirect
  • eIndexToDirect:先通过IndexArray获取索引,再通过DirectArray获取值

这里读取出来的数据,顶点数组的长度和索引数组的长度相同,不做顶点去重。切线和副切线的读取方式和法线相同。顶点索引使用的是polygonIdx * 3 + i,读取来的数据是直接在OpenGL使用的,因此顶点数据不需要做转换,如果是在DX使用,需要做转换,可以参考 FBX SDK的使用:基础知识 这篇文章。

c++ 复制代码
struct Vertex
{
	float position[3];
	float normal[3];
	float texcoord[2];
};

void ReadMesh(FbxMesh* pMesh, Vertex** pVertices, unsigned int** pIndices)
{
    int numTriangles = pMesh->GetPolygonCount();	// 三角面数量
	int numVertices = numTriangles * 3;				
	Vertex* vertices = new Vertex[numVertices];
	unsigned int* indices = new unsigned int[numVertices];
    *pVertices = vertices;
    *pIndices = indices;
    
    int vertexIdx = 0;
    FbxVector4* lCtrPoints = pMesh->GetControlPoints();
	for (int polygonIdx = 0; polygonIdx < numTriangles; polygonIdx++)
	{
		// 只处理三角形
		int lPolygonSize = pMesh->GetPolygonSize(polygonIdx);
		if (lPolygonSize != 3)
		{
			delete[] vertices;
			delete[] indices;
			printf("only process triangle, polygon size is %d \n", lPolygonSize);
			return nullptr;
		}
		
		for (int i = 0; i < lPolygonSize; i++)
		{
			Vertex lVertex;

			// 读取位置
			int controlPointIndex = pMesh->GetPolygonVertex(polygonIdx, i);
			FbxVector4 lCtrPoint = lCtrPoints[controlPointIndex];
			lVertex.position[0] = lCtrPoint.mData[0];
			lVertex.position[1] = lCtrPoint.mData[1];
			lVertex.position[2] = lCtrPoint.mData[2];

			// 读取法线
			ReadNormal(pMesh, controlPointIndex, vertexIdx, lVertex);

			// 读取UV
			ReadUV(pMesh, controlPointIndex, vertexIdx, lVertex);
			
            // 读取材质,下面的SubMesh使用
			ReadMat(pMesh, polygonIdx, lVertex);

			indices[vertexIdx] = vertexIdx;
			vertices[vertexIdx] = lVertex;
			vertexIdx++;
		}
	}
}

// 读取法线
void FbxLoader::ReadNormal(FbxMesh* pMesh, int pCpIdx, int pVertexIdx, Vertex& pVertex)
{
	FbxLayerElementNormal* lLayerNormal = pMesh->GetElementNormal(0);	// 第0层的法线
	FbxLayerElement::EReferenceMode lERefMode = lLayerNormal->GetReferenceMode();
	FbxLayerElement::EMappingMode lEMapMode = lLayerNormal->GetMappingMode();

	int lNormalIdx = 0;
	if (lEMapMode == FbxLayerElement::EMappingMode::eByControlPoint)
	{
		lNormalIdx = pCpIdx;
	}
	else if (lEMapMode == FbxLayerElement::EMappingMode::eByPolygonVertex)
	{
		lNormalIdx = pVertexIdx;
	}

	FbxVector4 lNormal;
	if (lERefMode == FbxLayerElement::EReferenceMode::eDirect)
	{
		lNormal = lLayerNormal->GetDirectArray().GetAt(lNormalIdx);
	}
	else if (lEMapMode == FbxLayerElement::EReferenceMode::eIndexToDirect)
	{
		int lIndex = lLayerNormal->GetIndexArray().GetAt(lNormalIdx);
		lNormal = lLayerNormal->GetDirectArray().GetAt(lIndex);
	}

	pVertex.normal[0] = lNormal.mData[0];
	pVertex.normal[1] = lNormal.mData[1];
	pVertex.normal[2] = lNormal.mData[2];
}

// 读取UV
void ReadUV(FbxMesh* pMesh, int pCpIdx, int pVertexIdx, Vertex& pVertex)
{
	FbxLayerElementUV* lLayerUV = pMesh->GetElementUV(0);	// 第0层的UV

	int lUVIdx = 0;
	switch (lLayerUV->GetMappingMode())
	{
	case FbxLayerElement::EMappingMode::eByControlPoint:
		lUVIdx = pCpIdx;
		break;
	case FbxLayerElement::EMappingMode::eByPolygonVertex:
		lUVIdx = pVertexIdx;
		break;
	default:
		return;
	}

	FbxVector4 lUV;
	int index = 0;
	switch (lLayerUV->GetReferenceMode())
	{
	case FbxLayerElement::EReferenceMode::eDirect:
		lUV = lLayerUV->GetDirectArray().GetAt(lUVIdx);
		break;
	case FbxLayerElement::EReferenceMode::eIndexToDirect:
		index = lLayerUV->GetIndexArray().GetAt(lUVIdx);
		lUV = lLayerUV->GetDirectArray().GetAt(index);
		break;
	default:
		return;
	}

	pVertex.texcoord[0] = lUV.mData[0];
	pVertex.texcoord[1] = lUV.mData[1];
}

SubMesh

首先SubMesh是顶点索引数组的一部分,作用是一个Mesh的不同部分可以使用不同的材质,比如眼镜的镜框和镜片,对应3DMax里面材质ID的功能,给不同的Polygon设置不同的材质ID,通过Multi/Sub-Object材质给不同的材质ID指定不同的材质。因此,处理SubMesh主要是收集具有相同材质ID的顶点索引。

c++ 复制代码
int FbxLoader::ReadMat(FbxMesh* pMesh, int pPolygonIdx, Vertex& pVertex)
{
	FbxLayerElementMaterial* leMat = pMesh->GetElementMaterial();
 	// 材质一般是ePolygon映射,这里默认。  
	int matIdx = leMat->GetIndexArray().GetAt(pPolygonIdx);
    
    int index = 0;
	FbxSurfaceMaterial* surfaceMat = nullptr;
	switch (leMat->GetReferenceMode())
	{
	case FbxLayerElement::EReferenceMode::eDirect:
		surfaceMat = leMat->mDirectArray->GetAt(matIdx); // GetDirectArray()是私有方法,直接访问字段
		break;
	case FbxLayerElement::EReferenceMode::eIndexToDirect:
		index = leMat->GetIndexArray().GetAt(matIdx);
		surfaceMat = leMat->mDirectArray->GetAt(index);
		break;
	default:
		return -1;
	}

    // 处理材质
    ..........
        
    // 返回材质索引
	return matIdx;
}

SubMesh目前我的代码还没有支持,就先这样了。

相关推荐
lang_dye3 天前
FBX SDK的使用:基础知识
fbx sdk·fbx
gis分享者2 个月前
学习threejs,导入FBX格式骨骼绑定模型
threejs·骨骼动画·fbx
哈市雪花7 个月前
3ds Max导出fbx贴图问题简单记录
3dsmax·贴图·fbx·fbxsdk·fbx2gltf
dragonzoebai1 年前
vue-3d-loader
3d·vue·gltf·fbx
dragonzoebai1 年前
vue-3d-model
3d·vue·gltf·fbx
little_fat_sheep1 年前
【libGDX】加载G3DJ模型
libgdx·g3d·g3dj·fbx·fbx-conv
FatherOfCodingMan1 年前
FBX SDK 开发环境配置 visual studio 2022
fbx sdk