阿里妹导读
背景介绍
大型语言模型(Large Language Models, LLMs)的出现标志着自然语言处理领域的一个变革时代,使机器能够以前所未有的方式理解、生成和互动人类语言。然而,物理世界本质上是三维的,理解空间3D环境对于涉及感知、导航和互动的许多现实应用至关重要。
将LLMs与3D数据融合,提供了一个独特的机会,可以增强计算模型对物理世界的理解和互动,从而在多个领域引领创新,包括自主系统、增强现实、机器人导航和机器人操作。近期的研究表明,整合LLMs与3D数据可以通过利用LLMs的固有优势,如零样本学习、先进推理和广泛知识,在复杂的3D环境中进行解释、推理和交互。
然而,LLMs与3D数据的整合并非易事。3D数据表示、模型可扩展性和计算效率等问题仍是重大障碍。此外,确保模型能够在现实世界环境中运行,需要克服与数据多样性和环境复杂性相关的挑战。解决这些问题对于充分实现LLMs在3D应用中的潜力,创造动态和情境感知的人工智能系统至关重要。
阿里云数据可视化产品DataV团队一直在三维交互领域进行前沿探索,为了解决上述LLMs与3D结合的问题,近期我们在虚幻引擎内结合通义千问大模型家族打造了一套基于LLM的实时可交互3D世界方案,通过自然语言来与引擎内的3D世界进行交互,包括:
-
模型搜索与创建:根据用户自然语言描述从三维模型库中搜索与之匹配的模型,并创建对象。例如,您可以要求在场景中生成一张现代风格的双人床。
-
3D对象操作:与场景中的物体进行物理交互,这可以包括拾取和移动物体,甚至是更复杂的动作序列以及多物体的联合操作,如:更改场景中咖啡桌和杯子的相对位置。
-
场景理解与编辑:在运行时以创造性或有用的方式修改现有场景,例如进行房间布局或者更改各种物体的颜色以适应色盲人士等。
【视频:LLM驱动的三维场景交互预览】
一、三维世界的元数据生成与表达
模型信息的收集
-
多边形网格 由顶点和表面组成,以紧凑的方式描述复杂的3D形状。然而,其非结构化和不可微分的性质在将其与神经网络集成以实现端到端可微管道时提出了挑战。为解决这一问题,一些基于梯度近似的方法只能使用手工计算的梯度,其他解决方案如可微光栅化器可能导致渲染结果不精确,如内容模糊。
-
点云 通过空间中的一组数据点表示3D形状,存储每个点在3D笛卡尔坐标系中的位置。除了存储位置外,还可以存储其他信息(例如颜色、法线)。基于点云的方法以其低存储占用而闻名,但缺乏表面拓扑信息。获取点云的典型来源包括LiDAR传感器、结构光扫描仪、飞行时间相机、立体视图和摄影测量等。
-
体素网格 由3D空间中的单元立方体组成,类似于2D中的像素表示。每个体素至少编码占用信息(以二进制或概率形式),但也可以编码到表面的距离,例如签名距离函数(SDF)或截断签名距离函数(TSDF)。然而,当需要高分辨率细节时,内存占用可能会变得过高。
-
神经场 在3D研究社区中逐渐受到关注,与依赖几何原语的传统表示形式不同。神经场是一种从空间坐标到场景属性(如占用、颜色、辐射等)的映射,与体素网格中从离散单元到该体素值的映射不同,神经场的映射是一个学习函数,通常是多层感知机(MLP)。这种方式使神经场能够隐式地学习紧凑、连续和可微的3D形状和场景表示。
-
混合表示 尝试将NeRF技术与传统的体积基础方法结合,促进高质量、实时渲染。例如,将体素网格或多分辨率哈希网格与神经网络结合,大大减少了NeRF的训练和推理时间。
-
3D高斯溅射 是点云的一种变体,每个点包含代表该点周围空间区域发出的辐射的额外信息,作为各向异性3D高斯“斑点”。这些3D高斯通常从SfM点云初始化,并使用可微渲染进行优化。3D高斯溅射通过利用高效的光栅化而非光线追踪,实现了比NeRF计算量小得多的状态-of-the-art新视图合成。
然而传统LLMs仅限于文本作为输入和输出,缺乏对外部世界或实时环境的理解。它们只依赖于所接受的训练文本,无法直接接收上述的3D表示形式。为了能够让LLMs感知到3D信息,总体思路是将3D物体或场景信息映射到语言空间,建立语言空间的3D模型表示,从而使LLMs能够理解和处理这些3D输入。
为了描述一个模型,我们可以建立如下的模型描述文件,包括模型的资产信息、语义描述、几何信息、材质信息、等:
{
//1. 资产信息
"ReferencePath":"/Game/ArchVizInteriorVol3/Meshes/SM_Bed.SM_Bed",//资产引用路径
"Name":"SM_Bed",//资产名字
//2. 语义描述
"Description":"这是一张床",// 语音描述
//3. 几何信息
"Pivot":"物体中心",//物体锚点
"GeometryInfo":{
"vertices":59997,//顶点数
"Triangles":"114502",//三角形数
"xxx":"" },
"BoundingBox":{
"center":[xx,xx],
"extend":[xx,xx]
},//包围盒信息
//4. 基本材质信息
"Materials":[
{"MI_Bed_Fabric_1":[
"BaseColor":xxx,
"BaseFallof":xxx
]}
]
}
其中的大部分信息,我们可以在Unreal引擎中通过对模型资产基本信息的解析进行填充:
-
资产基本信息:如ReferencePath,这是模型在引擎中的索引路径,可以根据索引路径加载对应的模型
-
材质信息:主要的可以动态修改的材质参数
-
几何信息:顶点数、UV信息等
但是只靠UE资产的信息解析显然是不够的,其中缺少关键的语义描述信息“Description。传统的方法是基于人工打标的方式给3D模型进行打标,这些标签便成为模型在语言空间的表示,并通过这些标签在模型库中进行搜索。这种方法不但费时费力,并且,如果想全面的描述一个模型,可能需要大量的tag进行标记:
我想要一张床 || 我想要一张双人床 || 我想要一张现代风格的双人床 || 我想要一个顶点数小一些的双人床 || 我想要一个红色的双人床 || 我想要一张床上有个黑色小熊的红色双人床
为了能够填充一个合适的语义描述信息,我们使用视觉-语言模型(Vision-Language Models, VLMs)进行描述信息的填充, 这种任务类型通常被称为物体级标注:
物体级标注 要求模型生成单个3D物体的简短自然语言描述。此描述应关注物体的关键特征,包括其形状和语义特征。
在Unreal引擎中利用VLMs进行物体级标注,比如对于这样一张床,让通义千问VL-Max帮我们形成其描述信息,可以看到是非常准确的,大模型不仅帮我们总结了三维模型的主体构成部分,甚至连光影效果、设计风格等细节部分都进行了整理。
当把标注好的信息填充到模型描述文件的“Description”字段后,我们就完成了对一个三维模型的自然语言级别的表示。对所有的模型进行这个编码后,我们便可以获得一个自然语言空间下的三维模型库,并通过“ReferencePath”这个字段与实体的三维模型进行关联,核心步骤如下:
-
对于每个模型:
-
根据UE资产生成基本信息
-
生成大小为640x640的缩略图
-
options:生成多视角缩略图
-
向大模型发起请求:Message{1. 这是一个三维模型的截图,请帮我详细描述这个三维模型的信息 2 Image[缩略图]}
-
获取大模型的返回,填充到Description字段
-
将模型描述信息填充到DataTable,并导出.csv文件
收集所有的模型,即可建立一个自然语言空间下的三维模型库。如下图所示,并通过“ReferencePath”这个字段与实体的三维模型进行关联。
模型搜索
至此能够被大模型理解的模型的信息库已经全部生成,下一步就是大模型如何利用这个信息库,辅助我们进行模型的搜索,最终实现能够根据用户自然语言的输入,输出模型在引擎中的描述路径“ReferencePath”,从而找到对应的三维模型实体。
我想要一张床 || 我想要一张双人床 || 我想要一张现代风格的双人床 || 我想要一个顶点数小一些的双人床 || 我想要一个红色的双人床 || 我想要一张床上有个黑色小熊的红色双人床
为了能够让大模型感知到这个模型信息库,我们使用大模型的知识检索增强(RAG)能力,这里以阿里云百炼平台+通义千问-max模型为例,结合我们3D模型表示过程中建立的三维模型库的知识库展示搭建一个智能体应用的过程,专门用于三维模型的搜索。
Assistant API 支持知识检索增强(RAG)工具,让智能体能够根据您的需求获取外部知识,例如私有产品知识或客户的偏好信息。本文介绍了一个简单的“手机导购”示例,帮助您快速上手RAG工具的基本使用方法。
步骤一:在“数据管理”-“结构化数据”下导入三维模型库信息
步骤二:在“知识索引”-"创建知识库"下导入上一步的知识,建立知识库
步骤三:在“我的应用”-“新增应用”-“创建RAG应用”下,创建RAG应用,并配置刚刚建立好的知识库,然后可以进行一些搜索的验证。
步骤四:智能体应用调用,可以在Unreal引擎中通过HTTP的接口调用百炼的应用
至此,我们便通过VLMs建立了每个三维模型的自然语言空间的表示,并借助大模型的RAG能力可以快速的找到符合我们自然语言描述的模型,大大的提高搜索效率。
【模型搜索和创建】
二、基于大模型的3D场景理解
在有了3D模型的表示后,下一步就是形成场景级别的表示,被称为3D场景理解,也被称为场景级标注:
场景级标注 是为整个3D场景生成简短自然语言描述。此类描述通常关注全局场景信息(如房间类型和风格)、场景中的关键物体及其关系。并建立与场景中的实体的一一映射关系,也即为每个场景中的实体增加语义标签。
为了获取场景级标注中的全局场景信息,以及关键物体及其关系,我们可以将不同视角下的场景截图直接喂给VLMs,可以看到大模型不但给出了物体信息、布局信息、空间信息,而且就很多细节以及场景的特点都给出了准确的描述。然而这其中有个关键的Mapping问题:因为VLMs只提供了3D场景的抽象文本描述,未能建立描述与Unreal引擎场景内实体的对应关系。在Unreal引擎中的场景描述是一个一个的Actor,我们只知道其中有两个大型吊灯,却不知道哪个Actor是大型吊灯,从而接下来的三维场景交互也就无从说起。因此场景理解的关键点就是建立抽象文本描述与场景内的一一映射关系。
为了解决这个问题,我们有多种策略,本质上就是解决多对多的Mapping问题。
策略一:
核心思路:控制出现在场景截图中的物体数量
核心步骤:
-
收集场景中的所有模型
-
隐藏所有模型
-
【for循环】对于每个模型
-
设置其可见性为true
-
根据BoundingBox计算相机位置,并移动相机到正确的位置
-
生成640x640的缩略图
-
options:生成多视角缩略图
-
向大模型发起请求:Message {1. 这是一个三维模型的截图,请帮我详细描述这个三维模型的信息 2 Image}
-
获取大模型的返回,根据3D模型的UUID,填充到对应物体的Description字段上
优势: 准确
劣势:
-
在场景元素众多的情况下,这个for 循环可能非常大
-
隐藏场景后,丢失场景辅助信息,可能识别不准,比如同样是一个杯子,如果在咖啡厅环境下,可能被认为是一个咖啡杯,如果是在酒吧环境中,可能被认为是一个酒杯。
策略二:
核心思路:保留全部物体,使用实体的唯一标识符标记要重点识别的物体
核心步骤:
-
收集场景中的所有模型
-
隐藏所有模型
-
【for循环】对于每个模型
-
设置其可见性为true
-
根据BoundingBox计算相机位置,并移动相机到正确的位置
-
设置该模型为选中态,为该模型生成outline
-
生成640x640的缩略图
-
向大模型发起请求:Message{1. 这是一个三维场景的截图,其中关键物体已经被黄色框选中,请帮我详细描述这个被黄色框选中物体的信息 2. Image}
-
获取大模型的返回,获取3D模型的UUID,根据UUID填充到对应物体的Description字段上
输入:【高脚椅被选中】
优势:
-
准确
-
有场景辅助信息,增强场景整体逻辑的描述
劣势:
-
在场景元素众多的情况下,这个for 循环可能非常大
策略三:
核心思路:保留场景辅助信息的同时,在一个请求的情况下,使用实体UUID尽可能多的标记物体
核心步骤:
-
收集场景中的所有模型
-
【for循环】对于每个关键视角
-
视口内的Actor(实体模型)计算其屏幕空间的mask,并在该mask上添加该Actor的唯一标识符
-
生成640x640的缩略图
-
options:生成多视角缩略图
-
向大模型发起请求:Message{1. 这是一个三维场景的截图,我使用红色框圈了一些主要物体,请给出这些方框分别描述了什么物体,以及对该物体进行描述,注意,如果方框内存在多个物体,返回的物体应该满足以下要求:在所有存在方框内的物体,方框圈住该物体的比例占该物体全部范围最大。以json格式输出其物品类型以及描述。2 Image}
-
获取大模型的返回,根据Key找到对应的Actor,然后将Value填充到Description字段
-
重复
输入:【手动框选几个主要物体,并标注】
优势:
-
准确
-
有场景辅助信息,增强场景整体逻辑的描述
-
同一张图批量获取模型信息,减少大模型调用次数
劣势:
-
多物体可能存在遮挡情况,致使大模型判别不准
综合使用如上几种策略后,我们一方面可以获取场景的全局场景信息(如房间类型和风格)、场景中的关键物体及其关系,也建立与场景中的实体的一一映射关系,同时我们也可以根据实体的唯一标识符收集一些引擎内的信息,如位置、锚点、空间关系等。综合上述所有信息,可以形成这样一份场景描述文件,这个场景描述文件+当前场景的截图构成了大模型与3D场景之间相互握手的桥梁,辅助大模型认识、感知和理解3D场景。举例:大模型通过图片识别到场景中有几把椅子,并通过场景描述文件,将图片中的椅子与3D场景中的椅子建立了映射。
{
"name": "StaticMeshActor_1",
"uUId": "7D70F97241D37F14A1C649860C7FE24D",
"Description": "一张双人床",
"transform":
{
"rotation":
{
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"translation":
{
"x": 0,
"y": 0,
"z": 0
},
"scale3D":
{
"x": 1,
"y": 1,
"z": 1
}
},
"boundingBoxCenter":
{
"x": 21.368782043457031,
"y": 1.3006591796875,
"z": 49.454704284667969
},
"boundingBoxExtent":
{
"x": 118.88101959228516,
"y": 112.37855529785156,
"z": 49.406597137451172
}
},
{
"name": "StaticMeshActor_2",
"uUId": "453F4F714546496132DA4EA97D22A636",
"Description": "一个书架",
"transform":
{
"rotation":
{
"x": -1.2325951644078309e-32,
"y": -0.70710678118654746,
"z": 6.1629758220391547e-33,
"w": 0.70710678118654735
},
"translation":
{
"x": 1.5,
"y": 0.5,
"z": 0
},
"scale3D":
{
"x": 1,
"y": 1,
"z": 1
}
},
"boundingBoxCenter":
{
"x": -24.001873016357415,
"y": 1.0605430603027344,
"z": -0.34783363342284446
},
"boundingBoxExtent":
{
"x": 25.503879547119134,
"y": 19.990333557128906,
"z": 19.498573303222656
}
}
三、对话式的3D世界交互
3D世界交互操作指的是自然语言与Unreal引擎内物体物理交互的能力,从移动物体到复杂的操作序列,如组装零件或开门或者调整天气等。在建立好三维模型表示以及能够对场景进行理解后,使LLMs能够执行操作任务的核心思想在于将动作序列标记化。为了让LLMs输出特定动作,然后Unreal引擎执行对应的动作,首先需要FunctionCall,使LLMs能够根据任务和3D场景上下文生成这些FunctionCall。
举例:
user: 我想要在场景中的桌子上放置一个杯子
大模型接收到这个逻辑后,会先去 收集场景信息,确定桌子的位置信息 等等,然后去 三维模型库去找杯子的ReferencePath ,然后尝试 生成这个杯子到桌子上去 ,逻辑是这个逻辑,但是我们非常清楚,标红的四个行为都是要对应一个函数调用的,要不然只能存在在语言层面,不会产生实际的行为。通过对对应Unreal引擎API的描述,让大模型知道在放置对应模型时应该调用哪个Unreal函数以及提供什么参数
Function Calling(函数调用)使开发人员能够描述函数(也称为工具,您可以将其视为模型要执行的操作,例如执行计算或下订单),并让模型智能地选择输出包含参数的 JSON 对象来调用这些函数。
在上述例子中我们至少要添加如下几个FunctionCall:
-
GatherSceneInfo:负责发起场景理解,形成场景描述文件
-
GetObjectReferencePath:负责根据用户的描述,去RAG应用里搜索三维模型库,然后返回ReferencePath等以及其他重要信息
-
SpawnObject:根据Referencepath生成Actor
-
MoveObject:根据输入的Transform调整Actor的Transform
本例中我们以SpawnObjec为例,说明Function Call的流程:
-
在Unreal引擎内添加SpawnObject的函数,并实现生成物体的逻辑
-
描述Function
-
描述函数的名字、函数的描述、主要参数、主要参数的描述。
{
"type": "function",
"function":
{