愚头的博客

一位图形学爱好者的唠叨

运行时创建UStaticMesh要注意的两点

最新的UE5已经提供了很多几何建模接口,而且这些接口都可以在Runtime下调用,避免了为生成UStaticMesh而改源码所带来的麻烦。

运行时创建UStaticMesh需要用到MeshDescriptionStaticMeshDescription这两个模块。 MeshDescription对基础模型的数据结构进行了定义。 StaticMeshDescription则针对静态模型进行了更详细的定义,同时还提供了很多有用的几何运算函数,如计算法向量、计算切向量、计算UV、计算光照UV等。

加载模型的过程是这样的,首先加载和解析模型文件(.obj、.fbx、gltf等),然后通过所读取的三角面、顶点、法线等数据来构建FMeshDescription,最后调用UStaticMesh::BuildFromMeshDescriptions将模型显示出来。 那么在这个过程中需要注意以下几点:

单模型多材质的处理

我们经常会碰到单个模型上有多个材质,这是通过三角面材质索引列表材质列表来实现的。三角面材质索引列表上记录了每个面所对应的材质索引,通过材质列表[三角面材质索引]就能访问到材质。

在构建这样的模型时首先要通过UStaticMesh::AddMaterial将材质添加到静态模型上,同时将返回的FName保存到材质名队列中,然后将这个队列赋值给MeshAttribute::PolygonGroup::ImportedMaterialSlotName属性。这里要注意材质名队列中的值必须是静态模型UStaticMesh::AddMaterial的返回值,并不是材质UMaterialInterface::GetFName的返回值

/// create the static mesh
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(/* owner */);
TArray<UMaterialInterface*> Materials;
/// TODO: build materials from the model

/// record the material names
TArray<FName> MaterialNames;
for (int32 i = 0, ic = Materials.Num(); i < ic; ++i)
{
    MaterialNames.Add(StaticMesh->AddMaterial(Materials[i]));
}

FMeshDescription MeshDescription;
/// TODO: build your MeshDescription from a model file

/// set the material by the polygon group attribute
TPolygonGroupAttributesRef<FName> ImportedMaterialSlotName = MeshDescription.PolygonGroupAttributes().GetAttributesRef<FName>(MeshAttribute::PolygonGroup::ImportedMaterialSlotName);
for (int32 i = 0, ic = MaterialNames.Num(); i < ic; ++i)
{
    ImportedMaterialSlotName.Set(i, MaterialNames[i]);
}

对静态模型的属性进行注册

如果你想用StaticMeshDescription模块里的函数,那么必须要通过FStaticMeshAttributes::Register注册静态模型属性之后才能调用。这样做的原因是:基础模型与静态模型的属性是有区别的,通过这个注册将静态模型属性赋予FMeshDescription中,StaticMeshDescription的函数就可以从FMeshDescription中访问到所需的静态模型属性来进行运算,否则会出现因访问错误而引起的崩溃

当然你也可以使用UStaticMeshDescription类来进行编辑,同时也需要调用UStaticMeshDescription::RegisterAttributes来完成注册。

FMeshDescription MeshDescription;
/// TODO: set the triangle index, vertex, normal, texcoord
FStaticMeshAttributes(MeshDescription).Register();
/// then you can call functions from `FStaticMeshOperations`
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(MeshDescription);

// or

UStaticMeshDescription* StaticMeshDescription = /* NewObject */;
/// TODO: set the triangle index, vertex, normal, texcoord
StaticMeshDescription->RegisterAttributes();
/// then you can call functions from `FStaticMeshOperations`
/// example: compute the tangent and normal
FStaticMeshOperations::ComputeTriangleTangentsAndNormals(StaticMeshDescription->GetMeshDescription());

题外话

过去很期待的一个功能是在Runtime下生成UStaticMesh,可是当时只能通过FRawMesh在Editor下生成,唯一的办法是改动引擎源码。后来慢慢地出现了很多新的模块,其中许多都可以在Runtime下处理模型。

其中一个很重要的改动是允许FRawMesh运行在Runtime下,这样做的好处是FRawMesh也可以在Runtime下构建UStaticMesh了(通过FStaticMeshOperations::ConvertFromRawMeshFRawMesh转换为FMeshDescription,再调用UStaticMesh::BuildFromMeshDescriptions显示出来),而且过去的旧代码依然能用。