3d Model importer; managing material, texture and normal information

Started by
7 comments, last by malhotraprateek 11 years, 1 month ago

Hi, I am making my own 3d Model importer in C++ (with DirectX 10) for my engine which is also under development.

I am using the FBX SDK which was suggested to me by someone in this forum.

The problem I have is that, even though I have read a lot of tutorials on how to do the rendering via vertex buffers and index buffers, I couldn't specifically get information on how the mesh data (including texture and material info) is arranged or should be arranged.

I would like to render multiple objects in my engine, and at a later stage do some model animations too. Right now all I can do is make a simple mesh with vertex and index buffers and assign a color to each pixel.

-I would like to know how to manage texture and material information in my model class.

-For example, I don't know whether I should include material information for each vertex or should I include it with each face/polygon.

-Also I would like to ask why do we have 'vertex' normals because a triangle can (and should) have 1 normal, not 3 (each for every vertex).

Is there a special reason for that or did I just misinterpret the tutorial?

-Do I need to make index buffers for normals separately or can I include them with each face/polygon data?

I will be grateful if anyone can point me in the right direction, even a good internet resource which solves my specific problems.

Advertisement

1. Typically you'll assign materials to faces, not vertices. This is because vertex data is often shared between multiple faces. Usually what you'll do is you'll sort faces by material, and then you'll keep track of the start and end index of each material. So Material A would be faces 0-500, and Material B would be faces 500-750. Then you can use the start index and number of indices as parameters for DrawIndexed.

2. For most organic shapes you will use "smooth normals", where normals are shared between neighboring faces and are often different at each vertex. Then at runtime you interpolate the normal across the face during rasterization, and this lets you have the appearance of smooth geometry without the underlying polygons actually being "smooth". The following image shows the difference between "smooth normals" and "one normal per face":

[attachment=14197:SmoothNormals.jpg]

3. GPU's can only work with one index buffer. Typically you'll duplicate vertex data so that you end up with one interleaved vertex buffer with all of your vertex data, and then you'll index into that using a single index buffer.

Thank you for the reply MJP; about your 3rd point, do you mean I have to accommodate normal data with the vertex data and index both using only one index buffer?

Yes, that is what I meant. As a simple example, take a cube. A cube only has 8 unique positions. However, if you want each face of the cube to have a flat normal then you need 6 unique normals. To combine these so that you can use a single index buffer, you end up needing 24 interlaced vertices.

-I would like to know how to manage texture and material information in my model class.

Keep all of that stuff seperate from your Model. Instead keep that information in it's own class (e.g. Skin, but not in the skinning sense!). This will allow you to re-use the same underlying geometry, but with differing textures + shaders applied.

-For example, I don't know whether I should include material information for each vertex or should I include it with each face/polygon.


Ideally, one material per surface. A mesh can have multiple materials assigned, but you'll probably want to seperate out the triangle indices into two sets (one for each material assignment). It just makes rendering a lot simpler, and more efficient. Pre-processing your FBX files offline is therefore a good idea!

-Also I would like to ask why do we have 'vertex' normals because a triangle can (and should) have 1 normal, not 3 (each for every vertex).

Is there a special reason for that or did I just misinterpret the tutorial?

Thank you RobTheBloke and MJP for your replies. Could you tell me of some books or other resources where I can read up this kind of stuff?

Books on the subject tend to be few and far between. Many many moons ago, I wrote a doc explaining how to extract the model data from Maya: http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/research/index.htm
Whilst not exactly the same as FBX, the organisation of Maya's data is *very* similar, so some of the info may be useful to figure out what kind of data you're likely to want to store, and how. The "advanced" example at the bottom of the page may prove to be semi useful (although it is OpenGL, the maths classes are terrible, and these days I wouldn't recommend storing all of the data in a single file - seperate anims, base models, & skins make life easier in the long run). I'd also probably try to be far less "clever" in the approach, so rather than trying to handle every case scenario, I'd probably just write some simple processor app that is given an explicit list of what I wanted to grab from the files. (i.e. extract these "named" transforms, in this order, and output these "named" geometry shapes). It makes it much easier when you start needing to get different mesh LOD's and the like....

I am including a chapter on loading .FBX files in my upcoming book. The samples will be free for download so I will upload the one for loading mesh data and generating vertex/index buffers later, but for now you can take a look at the mesh-loader’s header and source files. They are fully commented so it should be clear what they are doing.
#ifndef ___MESH_H___
#define ___MESH_H___

#include "../main.h"
#include <map>
#include <vector>

class CMesh {
public :
	// == Various constructors.
	CMesh();
	~CMesh();

	// == Enumerations.
	enum {
		FC_MAX_UVS				= 8,					/**< Maximum UV coordinates. */
		FC_NORMALS				= (1 << 0),				/**< Normal data is included. */
		FC_COLORS				= (1 << 1),				/**< Color data is included. */
	};

	// == Types.
	/**
	 * A raw fully expanded vertex buffer.
	 */
	typedef struct FC_RAW_VERTEX {
		FbxDouble3				vPosition;				/**< Vertex position. */
		FbxDouble3				vColor;					/**< Vertex color. */
		FbxDouble3				vNormal;				/**< Vertex normal. */
		FbxDouble2				vUvs[FC_MAX_UVS];		/**< Texture coordinates. */


		// == Operators.
		/** Less-than operator. */
		bool					operator < ( const FC_RAW_VERTEX &_vbOther ) const {
			return std::memcmp( this, &_vbOther, sizeof( FC_RAW_VERTEX ) ) < 0;
		}

		/** Equality operator. */
		bool					operator == ( const FC_RAW_VERTEX &_vbOther ) const {
			return std::memcmp( this, &_vbOther, sizeof( FC_RAW_VERTEX ) ) == 0;
		}
	} * LPFC_VERTEX_BUFFER;

	/**
	 * A set of materials which acts as a key to an std::map.
	 */
	typedef struct FC_MATERIAL_KEY {
		const char *			pcMaterial[FC_MAX_UVS];	/**< A set of materials. */


		// == Operators.
		bool					operator < ( const FC_MATERIAL_KEY &_fmkOther ) const {
			// Inlined for speed.
			for ( u32 I = 0UL; I < FC_MAX_UVS; ++I ) {
				// If we are NULL and they are not, we are below them.
				if ( !pcMaterial && _fmkOther.pcMaterial ) { return true; }
				// If they are NULL and we are not, we are above them.
				if ( pcMaterial && !_fmkOther.pcMaterial ) { return false; }
				// If we are both non-NULL, use a string compare.
				if ( pcMaterial && _fmkOther.pcMaterial ) {
					int iCmp = ::strcmp( pcMaterial, _fmkOther.pcMaterial );
					if ( iCmp < 0 ) { return true; }
					if ( iCmp > 0 ) { return false; }
					// Strings are the same.  Continue.
				}
				// This case means we are both NULL or are the same string.  Continue with the loop.
			}
			// We are the same all the way.
			return false;
		}

		bool					operator == ( const FC_MATERIAL_KEY &_fmkOther ) const {
			// Inlined for speed.
			for ( u32 I = 0UL; I < FC_MAX_UVS; ++I ) {
				// Just check for anything being different.
				if ( !pcMaterial && _fmkOther.pcMaterial ) { return false; }
				if ( pcMaterial && !_fmkOther.pcMaterial ) { return false; }
				if ( pcMaterial && _fmkOther.pcMaterial ) {
					if ( ::strcmp( pcMaterial, _fmkOther.pcMaterial ) != 0 ) { return false; }
				}
			}
			// Found no differences.
			return true;
		}
	} * LPFC_MATERIAL_KEY;

	// == Functions.
	/**
	 * Loads a mesh.
	 *
	 * \param _pfmMesh 
	 * \return Returns true if the mesh was loaded.
	 */
	bool						LoadMesh( FbxMesh * _pfmMesh );

protected :
	// == Types.
	/**
	 * Our own intermediate representation of a face/polygon.
	 */
	typedef struct FC_FACE {
		/** A nested structure representing one vertex on a face. */
		typedef struct FC_FACE_VERTEX {
			u32					u32VertexIndex;			/**< Vertex index. */
			u32					u32NormalIndex;			/**< Normal index. */
			u32					u32ColorIndex;			/**< Color index. */
			u32					u32UVIndex[FC_MAX_UVS];	/**< UV index. */
		} * LPFC_FACE_VERTEX;

		/** In order to save memory and time, a single large pool
			of FC_FACE_VERTEX will be maintained.  All faces will simply
			reference parts of it, so all we need to store on this
			structure is the starting index and the total vertices. */
		u32						u32StartVertex;
		u32						u32TotalVertices;
	} * LPFC_FACE;

	/**
	 * Pairing our intermediate faces with the vertex buffers they will
	 *	later be used to fill.
	 */
	typedef struct FC_FACE_VERTEX_PAIR {
		std::vector<FC_FACE>	vFaces;					/**< The faces in this bucket. */
		std::vector<FC_RAW_VERTEX>
								vVertices;				/**< The vertices created by the faces. */
	} * LPFC_FACE_VERTEX_PAIR;

	/**
	 * Pairing a vertex buffer with an index buffer.
	 */
	typedef struct FC_INDEX_VERTEX_PAIR {
		std::vector<u32>		vIndices;				/**< The indices. */
		std::vector<FC_RAW_VERTEX>
								vVertices;				/**< The vertices. */
	} * LPFC_INDEX_VERTEX_PAIR;

	/**
	 * Index buffer ranges associated with materials.
	 */
	typedef struct FC_INDEX_BUFFER_RANGE {
		u32						u32Start;				/**< The start index. */
		u32						u32Total;				/**< Total indices. */
	} * LPFC_INDEX_BUFFER_RANGE;

	/**
	 * Data to pass to AddFace().
	 */
	typedef struct FC_ADD_VERTEX {
		u32						u32IndexCount;			/**< Index count (in/out). */
	} * LPFC_ADD_VERTEX;

	// == Members.
	FbxMesh *					m_pfmMesh;				/**< The source mesh. */
	u32							m_u32Flags;				/**< Which data is included in the vertex buffer? */
	u32							m_u32TexCoords;			/**< How many texture coordinates are there? */
	std::map<FC_MATERIAL_KEY, FC_INDEX_BUFFER_RANGE>
								m_mMaterialMap;			/**< Mapping of which indices go to which materials. */
	FC_INDEX_VERTEX_PAIR		m_ivpVertexIndexBuffer;	/**< The final vertex and index buffers. */


	// == Functions.
	/**
	 * Adds a vertex to a vertex pool and adds an intermediate face to the proper bucket.
	 *
	 * \param _u32FaceIndex Index of the face to add.
	 * \param _mBuckets The buckets to which to add the face.
	 * \param _vVertices The pool to which to add the vertices.
	 * \param _fmkKey The key for _mBuffers to know to which vertex buffer to add the vertex.
	 * \param _favAddVert In/out parameters for adding the vertex.
	 * \return Returns true if the vertex was added.
	 */
	bool						AddFace( u32 _u32FaceIndex,
		std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> &_mBuckets,
		std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices,
		const FC_MATERIAL_KEY &_fmkKey,
		FC_ADD_VERTEX &_favAddVert ) const;

	/**
	 * Adds a (non-intermediate) vertex to a vertex buffer.  These vertices are
	 *	in fully expanded FC_RAW_VERTEX form.
	 *
	 * \param _u32FaceIndex Index of the vertex to add.
	 * \param _vDest The the std::vector to which to add the vertex.
	 * \param _vVertices The pool of intermediate vertices from which to read.
	 * \return Returns true if the vertex was added.
	 */
	bool						AddVertex( u32 _u32VertIndex,
		std::vector<FC_RAW_VERTEX> &_vDest,
		const std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices ) const;

	/**
	 * Takes a raw triangle-list vertex buffer as input and outputs the reduced vertex
	 *	buffer and new index buffer.
	 *
	 * \param _vInputVerts Input vertex buffer.
	 * \param _vOutputVerts The output vertex buffer.
	 * \param _vIndices The output index buffer.
	 * \return Returns true if the index buffer was created successfully.
	 */
	static bool					CreateIndexBuffer( const std::vector<FC_RAW_VERTEX> &_vInputVerts,
		std::vector<FC_RAW_VERTEX> &_vOutputVerts,
		std::vector<u32> &_vIndices );

};

#endif	// #ifndef ___MESH_H___
#include "FCMesh.h"


// == Various constructors.
CMesh::CMesh() :
	m_pfmMesh( NULL ),
	m_u32Flags( 0UL ),
	m_u32TexCoords( 0UL ) {
}
CMesh::~CMesh() {
}

// == Functions.
/**
 * Loads a mesh.
 *
 * \param _pfmMesh 
 * \return Returns true if the mesh was loaded.
 */
bool CMesh::LoadMesh( FbxMesh * _pfmMesh ) {
	m_pfmMesh = _pfmMesh;
	u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
	u32 u32TotalNormals = m_pfmMesh->GetLayer( 0 )->GetNormals() ? m_pfmMesh->GetLayer( 0 )->GetNormals()->GetDirectArray().GetCount() :
		0UL;
	u32 u32TotalColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors() ? m_pfmMesh->GetLayer( 0 )->GetVertexColors()->GetDirectArray().GetCount() :
		0UL;
	if ( u32TotalNormals ) { m_u32Flags |= FC_NORMALS; }
	if ( u32TotalColors ) { m_u32Flags |= FC_COLORS; }
	// Each layer may have 1 or 0 diffuse texture UV?.  Count them layer-by-layer.
	for ( u32 I = 0UL; I < u32TotalLayers; ++I ) {
		const FbxLayerElementUV * pleuUvs = m_pfmMesh->GetLayer( I )->GetUVs( FbxLayerElement::eTextureDiffuse );
		m_u32TexCoords += pleuUvs ? 1UL : 0UL;
	}
	m_u32TexCoords = std::min<u32>( m_u32TexCoords, FC_MAX_UVS );


	// #include <map> added to the header.
	std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> mBuckets;
	std::vector<FC_FACE::FC_FACE_VERTEX> vVertexPool;
	u32 ui32FaceCount = m_pfmMesh->GetPolygonCount();
	FC_ADD_VERTEX favAddVertex = { 0UL };
	for ( u32 I = 0; I < ui32FaceCount; ++I ) {
		//u32 ui32PolyTotal = m_pfmMesh->GetPolygonSize( I );
		//u32 ui32PolyStart = m_pfmMesh->GetPolygonVertexIndex( I );

		FC_MATERIAL_KEY fmkThisFaceMaterials;
		for ( u32 J = 0; J < FC_MAX_UVS; ++J ) {
			// Be sure to fill a value into every pcMaterial[], even if there are not enough
			//	layers on the mesh.
			if ( J >= u32TotalLayers ) {
				fmkThisFaceMaterials.pcMaterial[J] = NULL;
				continue;
			}
			// There is a layer here.  Get its material name.
			const FbxLayerElementMaterial * plemMaterials = m_pfmMesh->GetLayer( J )->GetMaterials();
			if ( !plemMaterials || plemMaterials->GetMappingMode() == FbxGeometryElement::eAllSame ) {
				// No material for this layer or all layers use material 0.
				plemMaterials = m_pfmMesh->GetLayer( 0 )->GetMaterials();
			}
			// Get the index of the material for this face and layer.
			u32 u32MaterialIndex = plemMaterials->GetIndexArray().GetAt( I );
			// Index into the mesh's array of materials to get the name.
			fmkThisFaceMaterials.pcMaterial[J] = reinterpret_cast<FbxSurfaceMaterial *>(m_pfmMesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>( u32MaterialIndex ))->GetName();
		}

		if ( !AddFace( I, mBuckets, vVertexPool, fmkThisFaceMaterials, favAddVertex ) ) {
			return false;
		}
	}

	// Now we can triangulate the faces using our intermediate faces.
	//	Note that it is possible to convert a whole face to our intermediate
	//	format and then to convert that face just afterwards, but we
	//	keep these processes separate for clarity here.
	// Go over each bucket.
	for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I ) {
		// Go over each face in the bucket.
		for ( std::vector<FC_FACE>::iterator J = I->second.vFaces.begin(); J != I->second.vFaces.end(); ++J ) {
			// Go over each vertex on the face.
			u32 u32PolyTotal = J->u32TotalVertices;
			u32 u32PolyStart = J->u32StartVertex;
			// Every triangle's first vertex is at index 0.
			//	Each next vertex is at J and the each final vertex is at J + 1.
			//	Hence the loop goes from 1 to ui32PolyTotal - 1.
			u32 u32LoopEnd = u32PolyTotal - 1;
			for ( u32 J = 1; J < u32LoopEnd; ++J ) {
				// 0 added for clarity.  Each index is offset by ui32PolyStart.
				u32 u32A = 0 + u32PolyStart;
				u32 u32B = J + u32PolyStart;
				u32 u32C = J + 1 + u32PolyStart;
				if ( !AddVertex( u32A, I->second.vVertices, vVertexPool ) ) { return false; }
				if ( !AddVertex( u32B, I->second.vVertices, vVertexPool ) ) { return false; }
				if ( !AddVertex( u32C, I->second.vVertices, vVertexPool ) ) { return false; }
			}
		}
		// We no longer need this intermediate face list.  Remove
		//	it to save memory.
		I->second.vFaces.swap( std::vector<FC_FACE>() );
	}
	// No longer need the pool of vertices.  Remove it to save memory.
	vVertexPool.swap( std::vector<FC_FACE::FC_FACE_VERTEX>() );


	// We will have a vector of index buffer/vertex buffer pairs
	//	such that each element in the vector is a one-to-one
	//	correlation to the materials in mBuckets.
	std::vector<FC_INDEX_VERTEX_PAIR> vIndexVertexBuffers;
	// The possible exception should be handled.
	vIndexVertexBuffers.resize( mBuckets.size() );

	// Go over each vertex buffer in mBuckets and convert
	//	to an index buffer/vertex pair.
	size_t sNewIdx = 0;
	for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I, ++sNewIdx ) {
		if ( !CreateIndexBuffer( I->second.vVertices, vIndexVertexBuffers[sNewIdx].vVertices, vIndexVertexBuffers[sNewIdx].vIndices ) ) {
			return false;
		}
		// We no longer need the expanded vertex buffer.
		I->second.vVertices.swap( std::vector<FC_RAW_VERTEX>() );
	}

	// Finalize the mesh data by combinging all the vertices
	//	and indices into one vertex buffer and one index buffer,
	//	and keep a map of ranges into the index buffers where
	//	the vertices for a material are.
	sNewIdx = 0;
	for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I, ++sNewIdx ) {
		// Store the range of indices for this material.
		// Handling the possible exception is left to the reader.
		FC_INDEX_BUFFER_RANGE ibrRange = {
			static_cast<u32>(m_ivpVertexIndexBuffer.vIndices.size()),
			static_cast<u32>(vIndexVertexBuffers[sNewIdx].vIndices.size()),
		};
		m_mMaterialMap[I->first] = ibrRange;

		// The vertices can be added to the final vertex buffer
		//	via a straight copy, but the indices need to be
		//	adjusted to point to the new locations of the
		//	vertices to which they originally point.
		// Add the indices one-by-one and apply that offset
		//	to each as they are added to the final index buffer.
		// m_ivpVertexIndexBuffer.vVertices.size() is the offset
		//	amount.
		for ( size_t J = 0; J < vIndexVertexBuffers[sNewIdx].vIndices.size(); ++J ) {
			// Handling the possible exception is left to the reader.
			m_ivpVertexIndexBuffer.vIndices.push_back( static_cast<u32>(vIndexVertexBuffers[sNewIdx].vIndices[J] + m_ivpVertexIndexBuffer.vVertices.size()) );
		}
		// Now add the vertices.
		for ( size_t J = 0; J < vIndexVertexBuffers[sNewIdx].vVertices.size(); ++J ) {
			// Handling the possible exception is left to the reader.
			m_ivpVertexIndexBuffer.vVertices.push_back( vIndexVertexBuffers[sNewIdx].vVertices[J] );
		}

	}
	std::cout << "Loaded mesh " << m_pfmMesh->GetName() << "." << std::endl;
	std::cout << '\t' << m_ivpVertexIndexBuffer.vVertices.size() << " vertices, " << m_ivpVertexIndexBuffer.vIndices.size() << " indices." << std::endl;
	return true;
}

/**
 * Adds a vertex to a vertex pool and adds an intermediate face to the proper bucket.
 *
 * \param _u32FaceIndex Index of the face to add.
 * \param _mBuckets The buckets to which to add the face.
 * \param _vVertices The pool to which to add the vertices.
 * \param _fmkKey The key for _mBuffers to know to which vertex buffer to add the vertex.
 * \param _favAddVert In/out parameters for adding the vertex.
 * \return Returns true if the vertex was added.
 */
bool CMesh::AddFace( u32 _u32FaceIndex,
	std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> &_mBuckets,
	std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices,
	const FC_MATERIAL_KEY &_fmkKey,
	FC_ADD_VERTEX &_favAddVert ) const {

	
	u32 ui32PolyTotal = m_pfmMesh->GetPolygonSize( _u32FaceIndex );
	u32 ui32PolyStart = m_pfmMesh->GetPolygonVertexIndex( _u32FaceIndex );
	int * piFacePool = m_pfmMesh->GetPolygonVertices();

	// Create the intermediate face.
	FC_FACE ffFace = {
		static_cast<u32>(_vVertices.size()),	// ffFace.u32StartVertex.
		ui32PolyTotal,							// ffFace.u32TotalVertices
	};
	// Add the face to the appropriate bucket.
	//	It is encouraged that you handle the possible exception here
	//	but it has been omitted here for brevity.
	_mBuckets[_fmkKey].vFaces.push_back( ffFace );

	// Go over each vertex in the face.
	for ( u32 I = 0UL; I < ui32PolyTotal; ++I ) {
		FC_FACE::FC_FACE_VERTEX ffvThisVert;
		u32 u32VertIndex = piFacePool[ui32PolyStart+I];
		ffvThisVert.u32VertexIndex = u32VertIndex;

		if ( m_u32Flags & FC_NORMALS ) {
			ffvThisVert.u32NormalIndex = 0UL;
			const FbxLayerElementNormal * plenNormals = m_pfmMesh->GetLayer( 0 )->GetNormals();
			switch ( plenNormals->GetMappingMode() ) {
				case FbxGeometryElement::eByControlPoint : {
					// The normal index is based on the vertex index.
					if ( plenNormals->GetReferenceMode() == FbxLayerElement::eDirect ) {
						// The normal index is the same as the vertex index.
						ffvThisVert.u32NormalIndex = u32VertIndex;
					}
					else if ( plenNormals->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						// The normal index is found by plugging the vertex index into an array of indices.
						ffvThisVert.u32NormalIndex = plenNormals->GetIndexArray().GetAt( u32VertIndex );
					}
					if ( ffvThisVert.u32NormalIndex >= static_cast<u32>(plenNormals->GetDirectArray().GetCount()) ) {
						std::cout << "Invalid normal index." << std::endl;
					}
					break;
				}
				case FbxGeometryElement::eByPolygonVertex : {
					// The normal index is based off _favAddVert.u32IndexCount, which is
					//	incremented once for each vertex over which we loop.
					if ( plenNormals->GetReferenceMode() == FbxLayerElement::eDirect ) {
						// It is just _favAddVert.u32IndexCount.
						ffvThisVert.u32NormalIndex = _favAddVert.u32IndexCount;
					}
					else if ( plenNormals->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						// The normal index is found by plugging _favAddVert.u32IndexCount into an array of indices.
						ffvThisVert.u32NormalIndex = plenNormals->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
					}
					break;
				}
				default : {
					std::cout << "Unsupported normal mapping mode." << std::endl;
				}
			}
		}

		if ( m_u32Flags & FC_COLORS ) {
			ffvThisVert.u32ColorIndex = 0UL;
			const FbxLayerElementVertexColor * plevcColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors();
			switch ( plevcColors->GetMappingMode() ) {
				case FbxGeometryElement::eByControlPoint : {
					if ( plevcColors->GetReferenceMode() == FbxLayerElement::eDirect ) {
						ffvThisVert.u32ColorIndex = u32VertIndex;
					}
					else if ( plevcColors->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						ffvThisVert.u32ColorIndex = plevcColors->GetIndexArray().GetAt( u32VertIndex );
					}
					if ( ffvThisVert.u32ColorIndex >= static_cast<u32>(plevcColors->GetDirectArray().GetCount()) ) {
						std::cout << "Invalid vertex color index." << std::endl;
					}
					break;
				}
				case FbxGeometryElement::eByPolygonVertex : {
					if ( plevcColors->GetReferenceMode() == FbxLayerElement::eDirect ) {
						ffvThisVert.u32ColorIndex = _favAddVert.u32IndexCount;
					}
					else if ( plevcColors->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						ffvThisVert.u32ColorIndex = plevcColors->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
					}
					break;
				}
				default : {
					std::cout << "Unsupported vertex color mapping mode." << std::endl;
				}
			}
		}

		u32 u32TexIndex = 0UL;
		u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
		for ( u32 K = 0UL; K < u32TotalLayers && u32TexIndex < m_u32TexCoords; ++K ) {
			const FbxLayerElementUV * pleuvUvElements = m_pfmMesh->GetLayer( K )->GetUVs( FbxLayerElement::eTextureDiffuse );
			if ( !pleuvUvElements ) { continue; }
		
			u32 u32Uv = 0;
			switch ( pleuvUvElements->GetMappingMode() ) {
				case FbxGeometryElement::eByControlPoint : {
					if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eDirect ) {
						u32Uv = u32VertIndex;
					}
					else if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						u32Uv = pleuvUvElements->GetIndexArray().GetAt( u32VertIndex );
					}
					break;
				}
				case FbxGeometryElement::eByPolygonVertex : {
					if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eDirect ) {
						u32Uv = _favAddVert.u32IndexCount;
					}
					else if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
						u32Uv = pleuvUvElements->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
					}
					break;
				}
				default : {
					std::cout << "Unsupported UV mapping mode." << std::endl;
				}
			}
			ffvThisVert.u32UVIndex[u32TexIndex++] = u32Uv;
		}

		// Add the vertex to the pool.  Normally you should handle the exception
		//	this could throw, but it is removed for brevity here.
		_vVertices.push_back( ffvThisVert );
		++_favAddVert.u32IndexCount;
	}
	return true;
}

/**
 * Adds a (non-intermediate) vertex to a vertex buffer.  These vertices are
 *	in fully expanded FC_RAW_VERTEX form.
 *
 * \param _u32FaceIndex Index of the vertex to add.
 * \param _vDest The the std::vector to which to add the vertex.
 * \param _vVertices The pool of intermediate vertices from which to read.
 * \return Returns true if the vertex was added.
 */
bool CMesh::AddVertex( u32 _u32VertIndex,
	std::vector<FC_RAW_VERTEX> &_vDest,
	const std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices ) const {
	// For clarity.
	const FC_FACE::FC_FACE_VERTEX & fvThisVert = _vVertices[_u32VertIndex];
	// Initialize the vertex so that the position is filled and
	//	the rest are all 0's.
	FC_RAW_VERTEX fvbBuffer = {
		FbxDouble3( m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][0],
			m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][1],
			m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][2] )
	};

	if ( m_u32Flags & FC_NORMALS ) {
		const FbxLayerElementNormal * plenNormals = m_pfmMesh->GetLayer( 0 )->GetNormals();
		fvbBuffer.vNormal = FbxDouble3( plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[0],
			plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[1],
			plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[2] );
	}

	if ( m_u32Flags & FC_COLORS ) {
		const FbxLayerElementVertexColor * plevcColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors();
		fvbBuffer.vNormal = FbxDouble3( plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[0],
			plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[1],
			plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[2] );
	}

	u32 u32TexIndex = 0UL;
	u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
	for ( u32 K = 0UL; K < u32TotalLayers && u32TexIndex < m_u32TexCoords; ++K ) {
		const FbxLayerElementUV * pleuvUvElements = m_pfmMesh->GetLayer( K )->GetUVs( FbxLayerElement::eTextureDiffuse );
		if ( !pleuvUvElements ) { continue; }
		fvbBuffer.vUvs[u32TexIndex] = FbxDouble3( pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[0],
			pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[1],
			pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[2] );
		++u32TexIndex;
	}

	// You should handle the possible exception here which
	//	has been omitted for brevity.
	_vDest.push_back( fvbBuffer );
	return true;
}

/**
 * Takes a raw triangle-list vertex buffer as input and outputs the reduced vertex
 *	buffer and new index buffer.
 *
 * \param _vInputVerts Input vertex buffer.
 * \param _vOutputVerts The output vertex buffer.
 * \param _vIndices The output index buffer.
 * \return Returns true if the index buffer was created successfully.
 */
bool CMesh::CreateIndexBuffer( const std::vector<FC_RAW_VERTEX> &_vInputVerts,
	std::vector<FC_RAW_VERTEX> &_vOutputVerts,
	std::vector<u32> &_vIndices ) {
	// Keep track of which vertices have been added to the output vertex buffer and
	//	the index where they are in that buffer.
	std::map<FC_RAW_VERTEX, u32> mUsedVerts;
	
	// For each vertex.
	for ( size_t I = 0; I < _vInputVerts.size(); ++I ) {
		// Has it already been added?
		u32 u32Index;
		std::map<FC_RAW_VERTEX, u32>::const_iterator iExisting = mUsedVerts.find( _vInputVerts );
		if ( iExisting == mUsedVerts.end() ) {
			// Has not yet been added.  Add it to the end of the
			//	list and store its index.
			u32Index = static_cast<u32>(_vOutputVerts.size());
			// You should handle the possible exceptions here,
			//	which have been unhandled here for brevity.
			_vOutputVerts.push_back( _vInputVerts );
			mUsedVerts[_vInputVerts] = u32Index;
		}
		else {
			// It has been added.  The index that goes
			//	into the index buffer is the index of
			//	the already-added vertex.
			u32Index = iExisting->second;
		}
		// Add the index to the index buffer, handling the possible
		//	exception in your code.
		_vIndices.push_back( u32Index );
	}
	return true;
}
This class loads a single mesh given an FbxMesh pointer.
It extracts the vertices, colors, normals, etc., and generates vertex buffers and index buffers (removing duplicate vertices), one for each material.
It then combines them into one vertex buffer with a table of ranges for each material, which is what you want for optimal rendering.

You should be able to follow the code with the comments there to help.


What is not yet shown here (will be in a later sub-chapter) is that this loading function should also return back to the caller a list of materials referenced by the mesh, which the higher-level functions can use to then determine which textures are referenced.
The meshes, materials, and textures would go into different parts of the file, and textures may optionally not be embedded for sharing purposes.

The book will be out later this year.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Thanks very much ppl

This topic is closed to new replies.

Advertisement