UE盒体碰撞体非均匀缩放问题
UE问题记录
本文参考UE4.27.2源码
问题与解决方案
起因是发现在fbx文件内给模型添加轴对齐的盒体碰撞体时,如果将碰撞体命名为UCX,导入UE后缩放模型时碰撞体均正常缩放;如果将碰撞体命名为UBX,导入UE后非均匀缩放模型时出现碰撞体缩放不一致的问题。如果直接在UE中添加盒体碰撞体,均正常缩放。
检索时发现了同样的问题:
这个问题的原因在于,UE的UBX由FKBoxElem存储,只记录了盒体的中心、旋转和三条边长,在对模型进行缩放时,其盒体碰撞体相当于在盒体坐标系(与盒体轴对齐的坐标系)下进行缩放。当从fbx导入盒体碰撞体时,调用StaticMeshImportUtils::AddBoxGeomFromTris函数生成盒体,其原理是遍历所有三角面片,统计每个法线方向上出现的平面(理论上盒体有3组法线,每组有2个平面),判断是否可以生成合适的盒体。这个过程生成的盒体的轴对齐坐标系不一定和模型的坐标系一致,例如当盒体坐标系的XYZ轴和模型坐标系的YZX轴对齐时,在缩放模型的X轴时,会缩放盒体的Z轴。这就会导致在非均匀缩放时,盒体碰撞体的缩放与模型并不一致。如果直接在UE中添加盒体碰撞体,此时盒体的坐标系和模型的坐标系默认是一致的,所以不会出现这种问题。UCX由FKConvexElem存储,记录的是顶点数据,所以可以正确非均匀缩放。
通过修改StaticMeshImportUtils::AddBoxGeomFromTris函数(调换盒体的XYZ三边的次序使其轴对称坐标系与模型坐标系一致),可以保证在fbx导入的盒体碰撞体无旋转的情况下(即盒体轴对齐)确保碰撞体与模型坐标系一致,即和直接在UE中添加的盒体碰撞体一致,这样模型在非均匀缩放时碰撞体能跟着正确缩放。
但是如果UBX盒体碰撞体本身不是轴对齐的,那还是无法正确非均匀缩放。本质上来说,如果盒体和模型坐标系不是轴对齐的,当对模型非均匀缩放后,盒体若正确缩放,则可能变形而不再是正确的盒体。
只要制作中使用的盒体碰撞体是轴对齐的,那么以UBX形式导入UE后就可以跟着模型非均匀缩放。否则只能以UCX导入。
附:静态网格体碰撞体命名规则及注意事项
在3D建模软件(如Maya、3ds Max、Blender)中为模型手动创建自定义碰撞体,并通过特定前缀命名,导出到FBX文件后再导入到UE,UE会自动识别这些碰撞体并将其绑定到对应(除前缀外命名相同)的静态网格体上,无需在UE中手动添加或调整碰撞体。这种自定义碰撞体称为简单碰撞体,复杂碰撞体即将模型的三角形网格作为碰撞体。
命名规则
| 命名规则 | 说明 |
|---|---|
UBX_[RenderMeshName]_## |
盒体(Box)必须使用规则的长方体3D对象创建。不能移动顶点或对其进行任何变形,否则无法正常工作。 |
UCP_[RenderMeshName]_## |
胶囊体(Capsule)必须是两端带半球的圆柱体。分段数不需要太多(8段即可),因为会被转换为真正的胶囊体用于碰撞。和盒体一样不应随意移动顶点。 |
USP_[RenderMeshName]_## |
球体(Sphere)分段数不需要太多(8段即可),因为会被转换为真正的球体用于碰撞。和盒体一样不应随意移动顶点。 |
UCX_[RenderMeshName]_## |
凸包(Convex)任意完全封闭的凸3D形状。盒体也可以是凸包。下图展示了什么是凸包,什么不是。 |
MCDCX_前缀等同于UCX_,意为Multiple Convex
Decomposition
Collision,通常用于区分第三方自动分解工具(如Maya插件、Havok工具等)生成的多个凸包碰撞体,表示这些凸包是通过“多凸分解算法”自动生成的。
源码位于
FFbxImporter::ImportCollisionModels。
注意事项
同样的形状,命名不同,UE底层采用的碰撞体类型和算法不同,性能也会有差异。建议能用
UBX_、USP_、UCP_等简单形状时优先使用,只有需要复杂凸形时才用UCX_。例如,如果将长方体命名为UCX_,网格会被解析为“凸包”,并通过UBodySetup::AggGeom.ConvexElems存,而不是通过UBodySetup::AggGeom.BoxElems存。引擎对命名的识别优先级 1.UCX(MCDCX)2.UBX 3.USP 4.UCP,按优先级顺序进行识别(识别内容不包含下划线),识别到命名即进行导入(即跳过低优先级的命名),就算命名不在开头也会被识别,因此
UBX_UCX_SM_Example会被识别为UCX。所以不要混用多个命名。命名不代表一定会生成对应类型的碰撞体,如果无法转换,则不会生成碰撞体。例如,fbx中的矩形碰撞体若以USP命名,导入引擎后不会生成球体碰撞体。导入时若不勾选“生成缺失碰撞”选项,在无法生成碰撞体的情况下就不会有碰撞。
命名规则中的
RenderMeshName部分必须与3D应用程序中和碰撞网格关联的渲染网格的名称相同。如果在3D应用程序中有一个名为Tree_01的渲染网格,则碰撞网格应与该网格一起位于场景中,并命名为UCX_Tree_01,然后与渲染网格一起导出到同一个FBX文件。如果一个网格需要多个碰撞体可以使用更多标识符扩展它们的名称,例如:UCX_Tree_01_00、UCX_Tree_01_01、UCX_Tree_01_02等。它们都将关联为该网格的碰撞体。导入.FBX文件到UE编辑器中,它会找到碰撞网格,将其从渲染网格中删除,并将其转换为碰撞模型。可以在Max或Maya中自行将碰撞模型分解为凸包。如果对象的碰撞由多个凸包定义,最好让彼此不相交。
相关类介绍
静态网格体:UObject
->UStreamableRenderAsset
[IInterface_CollisionDataProvider,IInterface_AssetUserData]
->UStaticMesh。碰撞信息保存在自身的成员class UBodySetup* BodySetup。例如凸包碰撞保存在BodySetup->AggGeom.ConvexElems中。
骨骼网格体:UObject
->UStreamableRenderAsset
[IInterface_CollisionDataProvider,IInterface_AssetUserData,INodeMappingProviderInterface]
->USkeletalMesh。碰撞信息保存在对应的USkinnedMeshComponent的成员class UPhysicsAsset* PhysicsAssetOverride的成员TArray<USkeletalBodySetup*> SkeletalBodySetups。
碰撞信息:UObject -> UBodySetupCore -> UBodySetup -> USkeletalBodySetup。
引擎侧碰撞拟合算法
(通过静态网格体编辑界面的顶部菜单栏Collision->"Add XXX Simplified
Colllision"调用,函数名为GenerateXXXAsSimpleCollision)
盒体拟合:取包围盒。即遍历所有顶点,逐个加入FBox。
球体拟合:基于Ritter算法和AABB中心算法计算两个球体,选择半径更小的。Ritter算法对于三条坐标轴计算每个轴上的最远点对,再取这三个最远点对中最远的点对的中点作为球心,初始半径取球心到所有最远点对的最大值,遍历所有顶点,若顶点不在球内,按方向将球心平移,同时更新球心和半径,使新球既包含原来球也包含该点。AABB中心算法取包围盒的中心作为球心,遍历所有顶点,更新半径。
胶囊体拟合:类似Ritter算法,但不更新中心。
k-DOP(k-Directional Oriented
Polytope)拟合:输入预定义在GeomFitUtils.h中的表示方向的单位向量数组(例如const FVector KDopDir10X[10]),拟合凸包碰撞。