读取顶点数据
要将一个Mesh渲染出来,必须要有顶点的位置,法线,UV等顶点属性,和三角面的顶点索引数组。在提取这些数据之前,先理解FBX SDK里面的几个概念:
- Control Point 顶点的位置,就是x,y,z坐标;
- Polygon Vertex Control Point的索引,Control Point存在一个数组里面,根据这个索引去获取它的值;
- 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目前我的代码还没有支持,就先这样了。