老程序员分享:PhysX和NavMesh在服务器的应用

简介: 老程序员分享:PhysX和NavMesh在服务器的应用

一、 引言


本文源于一个简单的想法 “在LINUX服务器进程中,加载Unity搭建的场景,并驱动AI在客户端的行为”,这个想法引发了一系列的思考:


物理引擎的选择


如何从Unity导出场景


如何用PhysX加载场景,并验证其正确性


如何驱动AI寻路


等等


带着上面的问题,作者花了大概两周的时间完成了组件选型、搭建、测试验证的工作,也整理完了这篇文章,分享给有相同疑问的同事。 因此,本文主要侧重于工作流的介绍和工具的使用,原理的介绍只会在必须的情况下提及,更多的原理需要大家去自行查阅,比如PhysXAPI的使用、CharacterController的应用、Detour库的使用等,可以详读最后一节的参考文献。


下面开始,会逐步解决上面的问题,另外,列举一下文中用到的组件列表,方便大家提前查阅。


PhysX


PhysX Visual Debugger(PVD)


UnityPhysXExport


RecastNavigation


Microsoft Visual Studio


Premake5


首先是物理引擎的选择,这里选择了PhysX,主要有两个原因:


开源,支持Unity\UE3\UE4


有很多成功的游戏,已经成为腾讯内部的主流技术选型


下一节开始,会开始介绍Linux环境下PhysX环境的搭建。


二、 Linux环境PhysX搭建


1. 下载


下载PhysX,首先需要申请加入NVIDIAGameWorks,进入NVIDIA官网,找到PhysX下载页,然后按步骤操作就好,系统会自动审核,大概10分钟就可以搞定了。


得到授权后,可以进入github主页下载版本,地址如下:


2. 编译


PhysX的编译十分简单,github上写的也很清楚,开发机是linux64环境,所以直接进入PhysX-3.4-master/PhysX_3.4/Source/compiler/linux64文件夹,输入make执行即可,编译成功会生成一系列的静态和动态库。


【root@SH-todo-1412181717 /data/physx/PhysX-3.4-master/PhysX_3.4】# find -name ".a"


./Lib/linux64/libLowLevelCHECKED.a


./Lib/linux64/libLowLevelAABBCHECKED.a


./Lib/linux64/libLowLevelDynamicsCHECKED.a


./Lib/linux64/libLowLevelClothCHECKED.a


./Lib/linux64/libLowLevelParticlesCHECKED.a


./Lib/linux64/libPhysX3VehicleCHECKED.a


./Lib/linux64/libPhysX3ExtensionsCHECKED.a


./Lib/linux64/libSceneQueryCHECKED.a


./Lib/linux64/libSimulationControllerCHECKED.a


./Lib/linux64/libLowLevelDEBUG.a


./Lib/linux64/libLowLevelAABBDEBUG.a


./Lib/linux64/libLowLevelDynamicsDEBUG.a


./Lib/linux64/libLowLevelClothDEBUG.a


./Lib/linux64/libLowLevelParticlesDEBUG.a


./Lib/linux64/libPhysX3VehicleDEBUG.a


./Lib/linux64/libPhysX3ExtensionsDEBUG.a


./Lib/linux64/libSceneQueryDEBUG.a


./Lib/linux64/libSimulationControllerDEBUG.a


./Lib/linux64/libLowLevelPROFILE.a


./Lib/linux64/libLowLevelAABBPROFILE.a


./Lib/linux64/libLowLevelDynamicsPROFILE.a


./Lib/linux64/libLowLevelClothPROFILE.a


./Lib/linux64/libLowLevelParticlesPROFILE.a


./Lib/linux64/libPhysX3VehiclePROFILE.a


./Lib/linux64/libPhysX3ExtensionsPROFILE.a


./Lib/linux64/libSceneQueryPROFILE.a


./Lib/linux64/libSimulationControllerPROFILE.a


./Lib/linux64/libLowLevel.a


./Lib/linux64/libLowLevelAABB.a


./Lib/linux64/libLowLevelDynamics.a


./Lib/linux64/libLowLevelCloth.a


./Lib/linux64/libLowLevelParticles.a


./Lib/linux64/libPhysX3Vehicle.a


./Lib/linux64/libPhysX3Extensions.a


./Lib/linux64/libSceneQuery.a


./Lib/linux64/libSimulationController.a


./Snippets/lib/linux64/libSnippetUtilsCHECKED.a


【root@SH-todo-1412181717 /data/physx/PhysX-3.4-master/PhysX_3.4】# find -name ".so"


./Bin/linux64/libPhysX3GpuCHECKED_x64.so


.///代码效果参考:http://hnjlyzjd.com/hw/wz_23991.html

Bin/linux64/libPhysX3GpuDEBUG_x64.so

./Bin/linux64/libPhysX3GpuPROFILE_x64.so


./Bin/linux64/libPhysX3Gpu_x64.so


./Bin/linux64/libPhysX3CommonCHECKED_x64.so


./Bin/linux64/libPhysX3CHECKED_x64.so


./Bin/linux64/libPhysX3CharacterKinematicCHECKED_x64.so


./Bin/linux64/libPhysX3CookingCHECKED_x64.so


./Bin/linux64/libPhysX3CommonDEBUG_x64.so


./Bin/linux64/libPhysX3DEBUG_x64.so


./Bin/linux64/libPhysX3CharacterKinematicDEBUG_x64.so


./Bin/linux64/libPhysX3CookingDEBUG_x64.so


./Bin/linux64/libPhysX3CommonPROFILE_x64.so


./Bin/linux64/libPhysX3PROFILE_x64.so


./Bin/linux64/libPhysX3CharacterKinematicPROFILE_x64.so


./Bin/linux64/libPhysX3CookingPROFILE_x64.so


./Bin/linux64/libPhysX3Common_x64.so


./Bin/linux64/libPhysX3_x64.so


./Bin/linux64/libPhysX3CharacterKinematic_x64.so


./Bin/linux64/libPhysX3Cooking_x64.so


复制


PhysX提供DEBUG和RELEASE库,使用DEBUG库编译可以连接PVD进行调试,另外,因为执行时需要动态链接,所以也需要把动态库的路径添加到ld.so.conf配置中,完成配置后执行ldconfig刷新,同时也可以执行ldconfig -p进行检查是否添加成功。


【root@SH-todo-1412181717 /data/physx/PhysX-3.4-master/PhysX_3.4】# cat /etc/ld.so.conf


include ld.so.conf.d/.conf


/data/physx/PhysX-3.4-master/PxShared/bin/linux64/


/data/physx/PhysX-3.4-master/PhysX_3.4/Bin/linux64/


复制


3. DEMO测试


这里选择的是PhysX-3.4-master/PhysX_3.4/Snippets/SnippetHelloWorld.cpp进行测试,首先需要修改代码中的snippetMain为main,不然编译时会找不到main函数。同时编写makefile,这里的链接顺序不要错了,makefile内容如下


BINARY = $(patsubst %.cpp,%,$(wildcard .cpp))


DEBUG_FLAG=-g -Werror -Wall -fPIC -fno-strict-aliasing -I ../../Include/ -I ../../../PxShared/include/ -DNDEBUG \


-L ../../Bin/linux64/ \


-lPxFoundationDEBUG_x64 \


-lPhysX3CommonDEBUG_x64 \


-lPhysX3DEBUG_x64 \


-lPhysX3CookingDEBUG_x64 \


-lPhysX3PROFILE_x64 \


-lPhysX3CommonPROFILE_x64 \


-lPhysX3CharacterKinematicDEBUG_x64 \


-L ../../Lib/linux64/ \


-lLowLevel \


-lPhysX3Extensions \


-l PhysX3Vehicle \


-l SceneQuery \


-l SimulationController \


-L ../../../PxShared/bin/linux64/ \


-lPxFoundationDEBUG_x64 \


-lPxPvdSDKDEBUG_x64 \


-lPxFoundationPROFILE_x64 \


-lPxPvdSDKPROFILE_x64 \


-L ../../../PxShared/lib/linux64/ \


-lPsFastXmlDEBUG \


-lPxCudaContextManagerDEBUG \


-lPxTaskDEBUG \


-lpthread -ldl -rdynamic


RELEASE_FLAG = -Werror -Wall -fPIC -fno-strict-aliasing -I ../../Include/ -I ../../../PxShared/include/ -DNDEBUG \


-L ../../Bin/linux64/ \


-lPxFoundation_x64 \


-lPhysX3Common_x64 \


-lPhysX3_x64 \


-lPhysX3Cooking_x64 \


-lPhysX3PROFILE_x64 \


-lPhysX3CommonPROFILE_x64 \


-lPhysX3CharacterKinematic_x64 \


-L ../../Lib/linux64/ \


-lLowLevel \


-lPhysX3Extensions \


-l PhysX3Vehicle \


-l SceneQuery \


-l SimulationController \


-L ../../../PxShared/bin/linux64/ \


-lPxFoundation_x64 \


-lPxPvdSDK_x64 \


-lPxFoundationPROFILE_x64 \


-lPxPvdSDKPROFILE_x64 \


-L ../../../PxShared/lib/linux64/ \


-lPsFastXml \


-lPxCudaContextManager \


-lPxTask \


-lpthread -ldl -rdynamic


#all:$BINARY)


all:SnippetHelloWorld.cpp


g++ SnippetHelloWorld.cpp $(DEBUG_FLAG)


复制


这里只编译SnippetHelloWorld.cpp测试,执行make all即可,生成a.out,执行a.out测试是否成功。


三、 PVD调试


上一小节,已经完成了PhysX在服务器端的编译,下面我们开始测试PVD和服务器进程的连通调试。


首先介绍一下PVD的工作原理,PVD启动后会监听127.0.0.1:5425端口,服务器上启动的进程,需要连接上PVD进行,连通后PVD界面上会出现服务器创建的场景。


1. PVD安装


PVD安装十分简单,进入NVIDA官方下载页,下载安装即可,启动后可以看到PVD的主界面,同时在当前机器上,telnet 127.0.0.1 5425可以连通,说明PVD正常启动,并监听端口成功。


下面是连接的示例代码:


gFoundation = PxCreateFoundation(PX_FOUNDATION_VERSION, gAllocator, gErrorCallback);


gPvd = PxCreatePvd(gFoundation);


PxPvdTransport transport = PxDefaultPvdSocketTransportCreate(PVDHOST, 5425, 10);


if ( transport == NULL ) {


printf("Transport Create failed\n");


}


int ret = gPvd->connect(*transport,PxPvdInstrumentationFlag::eALL);


printf ("connect ret:%d\n", ret);


复制


2. 搭建反向隧道


当前公司的LINUX开发机无法直接连上办公区的开发机,这里需要建立反向隧道。这里的反向隧道是把LINUX开发机上127.0.0.1:5425的请求,转到办公区的开发机上,这里才能实现和PVD的连通。


Mac开发机上建反向隧道比较简单,执行ssh -N -f -R 5425:127.0.0.1:5425 username@10.12.234.153 -p 36000就可以,其中10.12.234.153是开发机。


Windows开发机相对麻烦一些,作者先用的openssh测试,启动后进程没有常驻,最后用putty解决了,putty的配置如下所示:


配置完成后,点击open,登陆完成后,就可以连接上了。


这时可以在Linux开发机上telnet 127.0.0.1 5425端口,如果可以连通,就说明反向隧道已经建立成功。


3. 连通PVD测试


在Linux开发机上执行a.out,可以在PVD上看到服务器进程创建的场景,下图中的测试场景是从一个UNITY测试场景导出的。


下面开始介绍如何从一个UNITY测试场景导出在PhysX中可以加载的场景。


4. 场景导出


下载UnityPhysXExport(Github:github.com/tnqiang/UnitySceneExport2PhysX3


),并按照说明操作,可以导出一份xml文件,需要注意的是,要把场景加到BuildSettings中,导出文件如下所示:


[span class="hljs-title class">PhysX30Collection version="3.3.4" >


[span class="hljs-name">UpVector >0 0 0

[span class="hljs-name">Scale >


[span class="hljs-name">Length >0

[span class="hljs-name">Mass >0

[span class="hljs-name">Speed >10


[span class="hljs-name">PxMaterial >


[span class="hljs-name">Id >2253976624

[span class="hljs-name">DynamicFriction >0.6

[span class="hljs-name">StaticFriction >0.6

[span class="hljs-name">Restitution >0

[span class="hljs-name">FrictionCombineMode >eAVERAGE

[span class="hljs-name">RestitutionCombineMode >eAVERAGE


复制


服务器端的加载配置并添加到场景中的代码如下:


//load config


PxDefaultFileInputData inputData("./MainScence.xml");


PxCollection collection = PxSerialization::createCollectionFromXml(inputData, cooking, registry );


if (NULL == collection) {


printf("CreateCollectionFromXml failed\n");


return;


}


//create scene


PxSceneDesc sceneDesc(gPhysics->getTolerancesScale());


sceneDesc.gravity = PxVec3(0.0f, -9.81f, 0.0f);


gDispatcher = PxDefaultCpuDispatcherCreate(2);


sceneDesc.cpuDispatcher = gDispatcher;


sceneDesc.filterShader = PxDefaultSimulationFilterShader;


gScene = gPhysics->createScene(sceneDesc);


gScene->addCollection(collection);


复制


四、 从Unity场景导出NavMesh


从Unity场景导出NavMesh的方法,查到的资料主要有两种方法,这两种方法都是基于Recastnavigation库。


方法一是使用CritterAI库,安装插件到Unity中,项目地址在 ,项目主页上介绍的方法比较简单,但依赖于地形数据,比较耗性能,网上还有一篇采用query_tag选项导出的方法,尝试多次后没有成功,所以放弃了这一方法。


方法二是直接使用Recastnavigation库,这里主要介绍下这种方法的导出过程。


Recast/Detour在线手册 ,有兴趣的同学可以研究下。


1. 导出场景描述的OBJ文件


从wiki上的源码,生成两个cs文件ObjExporter.cs和EditorObjExporter.cs


把两个文件放到当前项目Assets/Editor文件夹下,然后选择一个网格对象,在 Custom 菜单中根据需要选择导出为 Obj 文件,文件将被保存在项目目录中,扩展名为.obj的文件。


2. 安装Recastnavigation库


首先要从github主页下载项目,


下载完后,根据主页上的说明,下载premake5,放到RecastDemo下,通过cmd命令行,生成vs2010依赖的编译文件,premake5的使用方法这里不再介绍,执行的命令如下:


premake5.exe —os=windows vs2010


然后需要下载SDL2库,进入 ,下载SDL2-devel-2.0.5-VC.zip即可,下载完成后放到RecastDemo/Contrib目录下,如下图所示:


用vs2015打开项目,执行编译(如果出现了连接错误,可以尝试将项目——项目属性——配置属性——连接器——清单文件——嵌入清单 “是”改为“否”),编译成功后,可以在bin目录下找到RecastDemo.exe文件,双击打开即可。Demo中提供了两个测试场景,在InputMesh中选择其中一个进行加载,关于Recastnavigation的使用,这里不再详述.


3. 导入场景文件,导出NavMesh文件


把上面导出的.obj文件放到Bin/Meshes下,然后选择目标obj文件,执行Build,即可完成NavMesh的构建,如下图所示:


右侧支持调整参数,调整完成后点击build可以再次生成,确认OK后点击save即可生成服务器可以使用的navmesh.bin文件。


五、 服务器加载NavMesh文件


1. Linux环境,Recastnavigation环境搭建


服务器加载NavMesh文件,需要用到Recastnavigation中的Detour库,所以,首先需要在Linux开发环境,搞完编译问题。


把之前下载的Recastnavigation,在Linux下解压,并在Detour的Source目录下创建makefile,Detour库没有任何外部的依赖,所以编译比较简单,编译生成一个静态库。


OBJS = $(patsubst %.cpp,%.o,$(wildcard .cpp))


FLAG=-g -Werror -Wall -fPIC -fno-strict-aliasing -I ../Include/


TARGET = libdetour.a


all:$(TARGET)


$(TARGET):$(OBJS)


ar q $@ $(OBJS)


%.o:%.cpp


g++ -c $< $(FLAG)


clean:


rm -rf $(TARGET) $(OBJS)


复制


编译成功后,在之前的demo编译的makefile中,加下对Detour库的链接。


DETOUR = -I /data/physx/recastnavigation-master/Detour/Include/ -L /data/physx/recastnavigation-master/Detour/Source/ -l detour


#all:$BINARY)


all:SnippetHelloWorld.cpp


g++ SnippetHelloWorld.cpp $(DEBUG_FLAG) $(DETOUR)


复制


2. 加载NavMesh文件


加载NavMesh文件,需要用到Detour中的dtNavMesh类,类提供了Init接口,但是使用RecastDemo导出的bin文件,不能直接用使用Init接口打开,因为这里用到了自定义的头部结构,头部结构定义在Sample_TileMesh.cpp中,如下所示:


struct NavMeshSetHeader


{


int magic;


int version;


int numTiles;


dtNavMeshParams params;


};


struct NavMeshTileHeader


{


dtTileRef tileRef;


int dataSize;


};


复制


RecastDemo生成的文件头部是NavMeshSetHeader结构,而不是MeshHeader,这里官方没有一个很好的说明,详细可以看下面的加载代码:


FILE fp = fopen(path, "rb");


if (!fp) return 0;


// Read header.


NavMeshSetHeader header;


size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp);


if (readLen != 1)


{


fclose(fp);


return 0;


}


if (header.magic != NAVMESHSET_MAGIC)


{


fclose(fp);


return 0;


}


if (header.version != NAVMESHSET_VERSION)


{


fclose(fp);


return 0;


}


dtNavMesh* mesh = dtAllocNavMesh();


if (!mesh)


{


fclose(fp);


return 0;


}


dtStatus status = mesh->init(&header.params);


if (dtStatusFailed(status))


{


fclose(fp);


return 0;


}


复制


初始化成功后,就可以读取所有的Tiles


// Read tiles.


for (int i = 0; i < header.numTiles; ++i)


{


NavMeshTileHeader tileHeader;


readLen = fread</

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
相关文章
|
2月前
|
弹性计算 关系型数据库 数据库
阿里云服务器ECS是什么?ECS应用场景、租用流程及使用教程整理
阿里云ECS(弹性计算服务)是性能稳定、弹性扩展的云计算服务,支持多种处理器架构和实例类型,适用于网站托管、开发测试、数据存储、企业服务、游戏多媒体及微服务架构等场景。提供从注册、配置到部署、运维的完整使用流程,助力用户高效上云。
|
3月前
|
存储 分布式计算 安全
阿里云服务器ECS实例选型参考:场景适配、应用推荐
选择阿里云服务器ECS实例之前,需要结合性能、价格、工作负载等因素,做出性价比与稳定性最优的决策。对于很多新手用户来说,在初次购买阿里云服务器的时候,面对众多实例规格往往不知道如何选择,因为云服务器实例规格不同,价格也不一样,性能表现更是千差万别。因此,在购买阿里云服务器ECS实例之前,需要结合性能、价格、工作负载等因素,做出性价比与稳定性最优的决策。本文将通过一些常见的选型场景推荐,为大家详细介绍阿里云服务器实例选型的最佳实践,便于大家在选择云服务器实例规格时做个参考。
|
4月前
|
开发框架 人工智能 Java
破茧成蝶:阿里云应用服务器让传统 J2EE 应用无缝升级 AI 原生时代
本文详细介绍了阿里云应用服务器如何助力传统J2EE应用实现智能化升级。文章分为三部分:第一部分阐述了传统J2EE应用在智能化转型中的痛点,如协议鸿沟、资源冲突和观测失明;第二部分展示了阿里云应用服务器的解决方案,包括兼容传统EJB容器与微服务架构、支持大模型即插即用及全景可观测性;第三部分则通过具体步骤说明如何基于EDAS开启J2EE应用的智能化进程,确保十年代码无需重写,轻松实现智能化跃迁。
426 41
|
1月前
|
存储 弹性计算 运维
阿里云服务器全解析:ECS是什么、应用场景、租用流程及优缺点分析
阿里云ECS(Elastic Compute Service)是阿里云提供的高性能、高可用的云计算服务,支持弹性扩展、多样化实例类型和多种计费模式。适用于网站搭建、数据处理、运维测试等多种场景,具备分钟级交付、安全可靠、成本低、易运维等优势,是企业及开发者上云的理想选择。
335 5
|
2月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
312 13
|
4月前
|
安全 应用服务中间件 网络安全
从零(服务器、域名购买)开始搭建雷池WAF到应用上线简明指南
本文详细介绍了基于雷池WAF的网站防护部署全流程,涵盖服务器与域名准备、WAF安装配置、网站接入设置及静态文件站点搭建等内容。通过最低1核CPU/1GB内存的服务器配置,完成Docker环境搭建、雷池一键安装及端口设置,实现域名解析、SSL证书配置和防护策略优化。同时支持301重定向与HTTP到HTTPS自动跳转,确保访问安全与规范。最后还提供了使用静态文件搭建网站的方法,帮助用户快速构建具备基础WAF防护能力的网站系统。
从零(服务器、域名购买)开始搭建雷池WAF到应用上线简明指南
|
4月前
|
关系型数据库 MySQL Linux
购买阿里云服务器选择应用镜像和系统镜像区别,哪个好?
在阿里云购买服务器时,选择应用镜像还是系统镜像需根据需求与技术能力决定。应用镜像适合快速部署、无需环境配置的场景,如建站或测试;系统镜像则提供更高灵活性,适合自定义开发和企业级服务。两者各有优劣:应用镜像操作简单但版本可能较旧,系统镜像可控性强但需手动配置。建议新手优先使用应用镜像,技术用户可选系统镜像以优化性能。
|
10月前
|
存储 缓存 前端开发
如何优化 SSR 应用以减少服务器压力
优化SSR应用以减少服务器压力,可采用代码分割、缓存策略、数据预加载、服务端性能优化、使用CDN、SSR与SSG结合、限制并发请求、SSR与CSR平滑切换、优化前端资源及利用框架特性等策略。这些方法能有效提升性能和稳定性,同时保证用户体验。
309 4
|
5月前
|
开发框架 人工智能 Cloud Native
破茧成蝶:阿里云应用服务器让传统J2EE应用无缝升级AI原生时代
一场跨越20年的技术对话:在杭州某科技园的会议室里,一场特殊的代码评审正在进行。屏幕上同时展示着2005年基于WebLogic开发的供应链系统和2025年接入DeepSeek大模型的智能调度方案——令人惊叹的是,二者的核心业务代码竟保持着惊人的一致性。"我们保住了20年积累的238个核心业务对象,就像修复传世名画时保留了每一笔历史痕迹。"企业CTO的感慨,揭开了阿里云应用服务器助力传统系统智能化转型的奥秘。
132 13
|
4月前
|
监控 5G 定位技术
时钟校准服务器在实际应用中的介绍
时钟校准服务器是一种用于时间同步的设备,通过接收北斗、GPS等标准时间信号,为分布式系统提供统一的时间标度。它广泛应用于通信、电力、金融、交通等领域,确保各模块本地时钟的一致性。随着科技发展,国产时钟服务器已实现高精度授时与国产化替代,如我司生产的SYN2136型北斗NTP网络时间服务器,支持双模授时、冗余备份和毫秒级精度。相比传统钟表,现代时钟校准设备具备高精度、自动调节和远程管理优势,满足科研及特殊机构需求。我司作为国家高新技术企业,拥有核心技术与多项专利,以互联网思维优化产品性价比,推动时间同步技术的发展。文章版权归西安同步所有,严禁洗稿或未经授权转载。