基于FBX SDK的FBX模型解析与加载 -(一)

FBX SDK下载安装教程 阅读详情

1. 简介 

FBX是Autodesk的一个用于跨平台的免费三维数据交换的格式(最早不是由Autodesk开发,但后来被其收购),目前被 众多的标准建模软件所支持,在游戏开发领域也常用来作为各种建模工具的标准导出格式。Autodesk提供了基于C++(还有Python)的SDK来实现对FBX格式的各种读写、修改以及转换等操作,之所以如此是因为FBX的格式不是公开的,这也是FBX的诟病之一。与FBX相对的则是格式开源的Collada,它的应用也很广泛。总体来说这两种格式还是各有优劣不相上下,关于两种格式在游戏开发中使用的对比与讨论也比较多,可见GameDev中的帖子:http://www.gamedev.net/topic/467753-collada-vs-autodesk-fbx , 这里就不再论述了。大多数情况下我们是需要解析模型在程序中渲染使用,因此这里主要讨论使用FBX SDK来对FBX模型进行解析与加载(主要包括几何网格、材质、Light与Camera、Skeleton动画等),而对于在建模工具中可能涉及到的FBX写出等则没有涉及。

2. FBX SDK的配置

首先,在使用前需要下载安装FBX的SDK,可以从Autodesk的网站上进行获得最新的版本http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=7478532(需要填些基本信息注册下)。安装之后在VS里边的配置就跟D3D类似。其中的Samples基本上涵盖了FBX相关的应用,可以在使用之前好好研究一下。最新的SDK版本(2012版)与之前的版本会在细节上有所不同(有些较大的改动是实现某些功能 的API接口的修改,具体细节可以用2012的Programmer's guide中找到),而且支持最新的FBX格式,因此最好就直接用最新的版本。

3. FBX模型的组织结构

FBX是以scene graph的结构来存储模型的所有信息(也可以认为是一个多叉树),类似于OSG中的组织方式,这一点可以从SDK所带的Sample里边很清楚的看出来。一个较为典型的模型的组织结构与下图所示:

整个Scene是从一个空属性的根结点开始,其中每个结点均是一个KFbxNode的对象,所有对象之间的关联均是双向的,比如从子结点可以索引到父结点,从父结点也可以索引到子结点;从单个结点可以索引到整个Scene,从Scene也可以索引到该结点。每个结点都会有一个标记属性的Enum值,比如eMesh、eLight、eCamera或eSkeleton等,分别用来标记当前结点是Mesh、Light、Camera或Skeleton。在整个结构的遍历过程中可以通过判断不同的结点属性而进行不同的处理操作。

在进行使用SDK进行FBX的处理操作之前需要先初始化两个必须的FBX对象:KFbxSdkManager和KFbxScene。前者用来对所有的FBX对象进行内在管理,所有使用SDK加载的资源均在此对象的管控之下,最终的资源释放也由其来完成。有了内存管理器之后再在其上创建一个相关的KFbxScene对象之后即可以进行模型的加截与处理了。KFbxScene其实相当于Manager提供的整个场景对象的一个入口。两个对象的初始化与配置代码如下所述:

 初始化SDKManager

bool FBXImporter::Initialize()
{
    // Create the FBX SDK Manager, destroy the old manager at first
    if(mpFBXSDKManager)
    {
	   mpFBXSDKManager->Destroy();
    }
    mpFBXSDKManager = KFbxSdkManager::Create();

    if(mpFBXSDKManager == NULL)
    {
	   return false;
    }

    // Create an IOSettings object
    KFbxIOSettings* ios = KFbxIOSettings::Create(mpFBXSDKManager , IOSROOT);
    mpFBXSDKManager->SetIOSettings(ios);

    // Load plug-ins from the executable directory
    KString lExtension = "dll";
    KString lPath = KFbxGetApplicationDirectory();
    mpFBXSDKManager->LoadPluginsDirectory(lPath.Buffer() , lExtension.Buffer());

    // Create the entity that hold the whole Scene
    mpFBXSDKScene = KFbxScene::Create(mpFBXSDKManager , "");

    return true;
}

 FbxScene的初始化

bool FBXImporter::LoadScene(const char* pSeneName)
{
    if(mpFBXSDKManager == NULL)
    {
	   return false;
    }

    // Get the file version number generate by the FBX SDK.
    KFbxSdkManager::GetFileFormatVersion(mSDKVersion.mMajor , mSDKVersion.mMinor , mSDKVersion.mRevision);

    // Create an importer.
    KFbxImporter* pKFBXImporter = KFbxImporter::Create(mpFBXSDKManager , "");

    // Initialize the importer by providing a filename
    FBXFileVersion fileVersion;

    bool importStatus = pKFBXImporter->Initialize(fileName , -1 , mpFBXSDKManager->GetIOSettings());
    lImporter->GetFileVersion(fileVersion.mMajor , fileVersion.mMinor , fileVersion.mRevision);

    if(!importStatus)
    {
        return false;
    }

    // Import the scene
    mpFBXScene->Clear();

    importStatus = pKFBXImporter->Import(m_mpFBXScene);

    // Destroy the importer.
    pKFBXImporter->Destroy();

    return importStatus;
}

 在完成了对KFbxScene的初始化操作之后即可以从其中得到整个模型的所有信息。由于FBX的是采用了类似于树的结构来进行存储,因而就很容易使用类似于树的递归方法来遍历其中的每个结点,并根据结点的属性选择合适的处理操作,下述代码就完成了从根结点开始的全局遍历:

void ProcessNode(KFbxNode* pNode)
{
	KFbxNodeAttribute::EAttributeType attributeType;

	if(pNode->GetNodeAttribute())
	{
		switch(pNode->GetNodeAttribute()->GetAttributeType())
		{
		case KFbxNodeAttribute::eMESH:
			ProcessMesh(pNode);
			break;
		case KFbxNodeAttribute::eSKELETON:
			ProcessSkeleton(pNode);
			break;
		case KFbxNodeAttribute::eLIGHT:
			ProcessLight(pNode);
			break;
		case KFbxNodeAttribute::eCAMERA:
			ProcessCamera();
			break;
		}
	}

	for(int i = 0 ; i < pNode->GetChildCount() ; ++i)
	{
		ProcessNode(pNode->GetChild(i));
	}
}

上述代码比较简单,直接传入由KFbxScene中获得的根结点之后即可遍历到每一个子结点。在FBX的存储中,每个父结点可以包含多个子结点,但每个子结点只有一个根结点,而且这其中的联系是双向的,这样很方便,比如在处理Skeleton时就常常需要从子结点中得到父结点的matrix等信息,而这种双向关系使得这些操作很容易实现。注意,上述代码中有pNode->GetNodeAttribute()检查操作是必须的,因为并不是所有的结点都有相应的属性(Attribute也是以子结点的方式关联到当前的结点上的,因而可能为空)。

4. 加载几何网格

FBX对几何网格支持得还是很好的,Nurbes、Polygon、Triangle等均可以存储。不过为了简化加载和处理时的操作,最好直接在FBX导出插件中选择一种统一的模式。比如可以在导出生成FBX时选中Triangluation的属性,那么FBX导出插件会自动把所有的Nurbes、Polygon三角化为三角形进行存储。当然,这个过程也可以在模型进行加载时来进行。这样在得到的FBX中就只有三角形这样一种网格模型,方便了加载的操作。模型的几何数据主要包括以下部分:

  • Vertex      组成网格的顶点信息,这一部分是必须的。
  • Color        每个顶点的颜色。
  • Normal    每个顶点所对应的法向,是由FBX导出插件计算生成,可以是逐面片或逐顶点。
  • Tangent    每个顶点所对应的切向,是由FBX导出插件计算生成,可以是逐面片或逐顶点。
  • UV 每个顶点所对应的贴图UV值,一般来说,每个UV对应一个Layer,一个顶点可以有多个UV通道,这在读入的时间需要进行判断

几何网格的加载比较简单,直接递归地从根结点来遍历整个graph,检测当前的结点是否为eMESH的属性,若是即处理其中的几何数据,主要代码如下所示:

void ProcessMesh(KFbxNode* pNode)
{
	KFbxMesh* pMesh = pNode->GetMesh();
	if(pMesh == NULL)
	{
		return;
	}

	D3DXVECTOR3 vertex[3];
	D3DXVECTOR4 color[3];
	D3DXVECTOR3 normal[3];
	D3DXVECTOR3 tangent[3];
	D3DXVECTOR2 uv[3][2];

	int triangleCount = pMesh->GetPolygonCount();
	int vertexCounter = 0;

	for(int i = 0 ; i < triangleCount ; ++i)
	{
		for(int j = 0 ; j < 3 ; j++)
		{
			int ctrlPointIndex = pMesh->GetPolygonVertex(i , j);

			// Read the vertex
			ReadVertex(pMesh , ctrlPointIndex , &vertex[j]);

			// Read the color of each vertex
			ReadColor(pMesh , ctrlPointIndex , vertexCounter , &color[j]);

			// Read the UV of each vertex
			for(int k = 0 ; k < 2 ; ++k)
			{
				ReadUV(pMesh , ctrlPointIndex , pMesh->GetTextureUVIndex(i, j) , k , &(uv[j][k]));
			}

			// Read the normal of each vertex
			ReadNormal(pMesh , ctrlPointIndex , vertexCounter , &normal[j]);

			// Read the tangent of each vertex
			ReadTangent(pMesh , ctrlPointIndex , vertexCounter , &tangent[j]);

			vertexCounter++;
		}

		// 根据读入的信息组装三角形,并以某种方式使用即可,比如存入到列表中、保存到文件等...
	}
}

上述代码完成了从一个Node里边读出相应的网格信息。首先,从Node里边得到相应KFbxMesh指针,可知,如果该Node不是eMESH属性的话那么该指针就为空,后继操作不能再进行。注意其中用triangleCount变量来存储pMesh->GetPolygonCount()的值,这主要是在前面也提到过了,假定对于所有的FBX模型在存储时均选定了Triangulation的操作,因而其中存储的Polygon是三角形,如此一来每个里边一定只包含3个顶点,依次读入这3个顶点所对应的各属性信息即可。在FBX中对于每个顶点所对应的各种额外属性,比如Normal、Tangent、UV等均可对应多个通道,这可以通过在每个Mesh里边增加相应属性的一个Layer即可实现,在使用FBX SDK写出FBX文件时很容易做到。比如上述代码中就从FBX中读出4个UV通道中的值(第一个是正常的贴图通道,第二层是LightMap的通道)。vertexCounter是记录已经处理过的顶点的数目,这主要是顶点信息读取在某些映射模式下(比如下述使用到vertexCounter的eBY_POLYGON_VERTEX等)需要知道其在全局顶ControlPoints中的信息,因而增加这样的一个变量来进行记录。

读入顶点:

void ReadVertex(KFbxMesh* pMesh , int ctrlPointIndex , D3DXVECTOR3* pVertex)
{
	KFbxVector4* pCtrlPoint = pMesh->GetControlPoints();

	pVertex->x = pCtrlPoint[ctrlPointIndex].GetAt(0);
	pVertex->y = pCtrlPoint[ctrlPointIndex].GetAt(1);
	pVertex->z = pCtrlPoint[ctrlPointIndex].GetAt(2);
}

读入Color:

void ReadColor(KFbxMesh* pMesh , int ctrlPointIndex , int vertexCounter , D3DXVECTOR4* pColor)
{
	if(pMesh->GetElementVertexColorCount < 1)
	{
		return;
	}

	KFbxGeometryElementVertexColor* pVertexColor = pMesh->GetElementVertexColor(0);
	switch(pVertexColor->GetMappingMode())
	{
	case KFbxGeometryElement::eBY_CONTROL_POINT:
		{
			switch(pVertexColor->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pColor->x = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mRed;
					pColor->y = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mGreen;
					pColor->z = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mBlue;
					pColor->w = pVertexColor->GetDirectArray().GetAt(ctrlPointIndex).mAlpha;
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = pVertexColor->GetIndexArray().GetAt(ctrlPointIndex);
					pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed;
					pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen;
					pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue;
					pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha;
				}
				break;

			default:
				break;
			}
		}
		break;

	case KFbxGeometryElement::eBY_POLYGON_VERTEX:
		{
			switch (pVertexColor->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pColor->x = pVertexColor->GetDirectArray().GetAt(vertexCounter).mRed;
					pColor->y = pVertexColor->GetDirectArray().GetAt(vertexCounter).mGreen;
					pColor->z = pVertexColor->GetDirectArray().GetAt(vertexCounter).mBlue;
					pColor->w = pVertexColor->GetDirectArray().GetAt(vertexCounter).mAlpha;
				}
				break;
			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = pVertexColor->GetIndexArray().GetAt(vertexCounter);
					pColor->x = pVertexColor->GetDirectArray().GetAt(id).mRed;
					pColor->y = pVertexColor->GetDirectArray().GetAt(id).mGreen;
					pColor->z = pVertexColor->GetDirectArray().GetAt(id).mBlue;
					pColor->w = pVertexColor->GetDirectArray().GetAt(id).mAlpha;
				}
				break;
			default:
				break;
			}
		}
		break;
	}
}

读入UV:

void ReadUV(KFbxMesh* pMesh , int ctrlPointIndex , int textureUVIndex , int uvLayer , D3DXVECTOR2* pUV)
{
	if(uvLayer >= 2 || pMesh->GetElementUVCount() <= uvLayer)
	{
		return false;
	}

	KFbxGeometryElementUV* pVertexUV = pMesh->GetElementUV(uvLayer);

	switch(pVertexUV->GetMappingMode())
	{
	case KFbxGeometryElement::eBY_CONTROL_POINT:
		{
			switch(pVertexUV->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pUV->x = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
					pUV->y = pVertexUV->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = pVertexUV->GetIndexArray().GetAt(ctrlPointIndex);
					pUV->x = pVertexUV->GetDirectArray().GetAt(id).GetAt(0);
					pUV->y = pVertexUV->GetDirectArray().GetAt(id).GetAt(1);
				}
				break;

			default:
				break;
			}
		}
		break;

	case KFbxGeometryElement::eBY_POLYGON_VERTEX:
		{
			switch (pVertexUV->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					pUV->x = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(0);
					pUV->y = pVertexUV->GetDirectArray().GetAt(textureUVIndex).GetAt(1);
				}
				break;

			default:
				break;
			}
		}
		break;
	}
}

读入Normal:

void ReadNormal(KFbxMesh* pMesh , int ctrlPointIndex , int vertexCounter , D3DXVECTOR3* pNormal)
{
	if(pMesh->GetElementNormalCount() < 1)
	{
		return;
	}

	KFbxGeometryElementNormal* leNormal = pMesh->GetElementNormal(0);
	switch(leNormal->GetMappingMode())
	{
	case KFbxGeometryElement::eBY_CONTROL_POINT:
		{
			switch(leNormal->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pNormal->x = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
					pNormal->y = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
					pNormal->z = leNormal->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2);
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = leNormal->GetIndexArray().GetAt(ctrlPointIndex);
					pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0);
					pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1);
					pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2);
				}
				break;

			default:
				break;
			}
		}
		break;

	case KFbxGeometryElement::eBY_POLYGON_VERTEX:
		{
			switch(leNormal->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pNormal->x = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(0);
					pNormal->y = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(1);
					pNormal->z = leNormal->GetDirectArray().GetAt(vertexCounter).GetAt(2);
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = leNormal->GetIndexArray().GetAt(vertexCounter);
					pNormal->x = leNormal->GetDirectArray().GetAt(id).GetAt(0);
					pNormal->y = leNormal->GetDirectArray().GetAt(id).GetAt(1);
					pNormal->z = leNormal->GetDirectArray().GetAt(id).GetAt(2);
				}
				break;

			default:
				break;
			}
		}
		break;
	}
}

读入Tangent:

void ReadTangent(KFbxMesh* pMesh , int ctrlPointIndex , int vertecCounter , D3DXVECTOR3* pTangent)
{
	if(pMesh->GetElementTangentCount() < 1)
	{
		return;
	}

	KFbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(0);

	switch(leTangent->GetMappingMode())
	{
	case KFbxGeometryElement::eBY_CONTROL_POINT:
		{
			switch(leTangent->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pTangent->x = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(0);
					pTangent->y = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(1);
					pTangent->z = leTangent->GetDirectArray().GetAt(ctrlPointIndex).GetAt(2);
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = leTangent->GetIndexArray().GetAt(ctrlPointIndex);
					pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0);
					pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1);
					pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2);
				}
				break;

			default:
				break;
			}
		}
		break;

	case KFbxGeometryElement::eBY_POLYGON_VERTEX:
		{
			switch(leTangent->GetReferenceMode())
			{
			case KFbxGeometryElement::eDIRECT:
				{
					pTangent->x = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(0);
					pTangent->y = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(1);
					pTangent->z = leTangent->GetDirectArray().GetAt(vertecCounter).GetAt(2);
				}
				break;

			case KFbxGeometryElement::eINDEX_TO_DIRECT:
				{
					int id = leTangent->GetIndexArray().GetAt(vertecCounter);
					pTangent->x = leTangent->GetDirectArray().GetAt(id).GetAt(0);
					pTangent->y = leTangent->GetDirectArray().GetAt(id).GetAt(1);
					pTangent->z = leTangent->GetDirectArray().GetAt(id).GetAt(2);
				}
				break;

			default:
				break;
			}
		}
		break;
	}
}

上述几个Normal、Tangent、UV等信息读取的函数的实现其实都差不多,首先需要判断有没有相应的Layer关联在当前的Mesh中,若有则获取其地址,然后根据不同的映射方式使用不同的方法从内存中读取相应的值即可。

完成了这些基本几何信息的读取之后即可以使用其进行渲染了:

 

下一篇来介绍一下材质的读取与使用...

基于FBX SDK的FBX模型解析与加载 -(二) 5. 加载材质  Material是一个模型渲染时必不可少的部分,当然,这些信息也被存到了FBX之中(甚至各种贴图等也可以直接内嵌到FBX内部),就需要从FBX中加载这些信息以完成带有材质的渲染。材质的加载可以与Mesh的加载相结合来完成,但更好的方法是独立进行,这样各模块间的关系更清晰,但这就需要一个额外的操作,那就是关联Mesh与Material。FBX中的材质对象包含了丰富的信息,比如最常 阅读详情

相关推荐

FBX SDK C++开发环境配置in vs2022,因为uinty fbx export package删除UV,color后有时会变形

下载windows的sdk并安装.添加预处理宏 FBX_SHARED=1。创建一个c++ console 工程。添加fbx sdk lib 目录。设置include目录。

2391

FBX SDK 使用入门(Python)

FBX SDK 是用 C++ 开发的,同时提供 Python 库,本文介绍 FBX Python SDK 的使用。

dzhaoll1001的博客 7430

AssetStudio下载与使用指南:Unity资源解包工具全解析

Unity引擎开发的游戏,其模型、贴图、音频等资源通常被序列化打包为assets或AssetBundle文件。理解这类二进制格式的解析原理,是资源逆向与二次创作的基础。借助专业的解包工具,开发者可以高效提取并转换这些资源,用于技术分析、汉化修改或学习研究。AssetStudio正是这样一款开源工具,它能够直接读取Unity序列化文件,将网格、纹理、动画、音频等还原为FBX、PNG、WAV等通用格式。从下载安装到版本选择,从基础操作到FBX导出避坑,本文提供一份完整的AssetStudio下载与使用指南,帮助

weixin_29021195的博客 375

FBX SDK开发快速上手指南

一段时间以来,我一直想制作一个 FBX Exporter 将 FBX 文件转换为我自己的格式。整个过程不是很顺利,主要是FBX的官方文档不是很清楚。另外,由于 FBX 格式被许多应用程序使用,而不仅仅是游戏引擎,因此提供的示例代码没有使用我们在游戏开发中使用的更常见的技术术语。我搜索了互联网上几乎所有的角落来澄清事情,以便我可以从 FBX SDK 的数据到我在游戏引擎中需要的数据有一个清晰的映射。推荐:用快速搭建可编程3D场景。

新缸中之脑 2429

深入理解加载FBX模型文件

每个模型文件都有自己的格式,有自研引擎的模型格式,有AutoDesk提供的模型文件格式,比如FBX模型文件,因为Unity与UE4引擎的使用而备受关注,网上也有很多关于这方面的文章,但是都没有真正解释FBX文件的内部结构,以及如何自己封装程序加载FBX模型文件。本篇博客就教给读者这两方面的知识,这样更有助于读者理解Unity加载设置FBX文件的界面。 FBX文件内部结构 我们要加载模型文件,...

海洋个人博客 8万+

基于FBX SDK的FBX模型解析与加载 -(四)

8. 骨骼蒙皮动画 骨骼蒙皮动画是当前游戏引擎中最常用的一种动画方式,关于其基本原理网络上的资料较多,关于到涉及的其它较复杂操作,如插值、融合等在这里也就先不再讨论了,而且其实现方式也与具体引擎的动作管理系统相关;在这里就主要简单介绍一下如何从FBX里加载骨骼以及蒙皮信息并完成最基本的蒙皮动画效果。骨骼动画的实现主要包括骨骼的驱动和蒙皮两部分操作,骨骼的驱动在前一篇中介绍动画数据的加载时已经完成

BugRunner的专栏 2万+

基于FBX SDK的FBX模型解析与加载-(一)

1. 简介 FBX是Autodesk的一个用于跨平台的免费三维数据交换的格式(最早不是由Autodesk开发,但后来被其收购),目前被 众多的标准建模软件所支持,在游戏开发领域也常用来作为各种建模工具的标准导出格式。Autodesk提供了基于C++(还有Python)的SDK来实现对FBX格式的各种读写、修改以及转换等操作,之所以如此是因为FBX的格式不是公开的,这也是FBX的诟病之一。与F

熊小屁的专栏 2586

Ubuntu 24.04 LTS安装WPS Office 2025冬季版全攻略(附字体缺失解决方案)

本文提供了在Ubuntu 24.04 LTS系统上安装WPS Office 2025冬季版的完整指南,详细对比了dpkg、Snap和GUI三种安装方式的优劣。针对安装后常见的中文字体缺失、思源黑体粗细异常等核心问题,给出了从原理分析到手动补充字体库、修改字体配置文件的深度解决方案,确保WPS Office在Linux环境下获得最佳使用体验。

weixin_29284201的博客 618

基于FBX SDK的FBX模型解析与加载

1. 简介 FBX是Autodesk的一个用于跨平台的免费三维数 据交换的格式(最早不是由Autodesk开发,但后来被其收购),目前被众多的标准建模软件所支持,在游戏开发领域也常用来作为各种建模工具的标准导出 格式。Autodesk提供了基于C++(还有Python)的SDK来实现对FBX格式的各种读写、修改以及转换等操作,之所以如此是因为FBX的格式 不是公开的,这也是FBX的诟病之一。与FBX相对的则是格式开源的Collada,它的应用也很广泛。总体来说这两种格式还是各有优劣不相上下,关于两 种格式

翻肚鱼儿的博客 5334

ESP32S3 Korvo2V3四通道AEC配置实战:基于AFE的回声消除优化

本文详细介绍了在ESP32S3 Korvo2V3平台上配置四通道AEC(回声消除)的实战经验。通过AFE算法优化和硬件连接调试,实现高质量回声消除,显著提升智能音箱和会议设备的语音交互体验。重点解析了四通道配置、数据格式对齐和参数调优等关键技术要点。

elastic6hunter的博客 932

麒麟v10(ky10.x86_64)升级——openssl-3.2.2、openssh-9.8p1

执行命令: make -j 4 && make install 这里要等待一段时间,等待编译完成。执行命令: ./configure --prefix=/usr/local/src/zlilb。执行命令: ./config --prefix=/usr/local/src/openssl。执行命令: make -j 4 && make test && make install。执行命令: make -j 4 && make install。执行命令: /etc/init.d/sshd restart。

X_yufei的博客 7427

基于FBX SDK的FBX模型解析与加载 -(三)

6. 加载Camera和Light 在FBX模型中除了几何数据外较为常用的信息可能就是Camera和Light,虽然在游戏中一般不直接从模型中得到这两部分信息,而是由引擎来提供,但是FBX中提供了对这些信息保存的支持。其实单纯加载这两部分的信息很简单,就像之前介绍的在整个Scene Graph中对每个Node遍历过程中,判断得到当前结点是Camera或Light时调用相应的ProcessCame

BugRunner的专栏 1万+

Meta-RL之Meta-Gradient Reinforcement Learning

这篇文章是用元学习算法去学习RL的超参数η={γ,λ}\eta=\{\gamma,\lambda\}η={γ,λ}。当然不仅限于这2个超参数,还可以是和回报相关的超参数。 本文的核心思想:我们之前接触过Meta-Learning参数的初始化(MAML、Reptile)以及Meta-Learning参数的更新优化(L2L-by-gd-by-gd、Opt as a model for few-shot learning),那么这篇就是能否Meta-Learning到η={γ,λ}\eta=\{\gamma,\l

Ton的博客 2520
上一篇: 使用XeLaTeX/XeTeX编译中文文档
下一篇: 基于FBX SDK的FBX模型解析与加载 -(二)
BugRunner
博客等级 码龄17年 791粉丝 77原创
评论 46
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值