UE4笔记-Runtime下使用Assimp库加载任意3D格式文件(fbx/obj/gltf等)到UStaticMesh
备忘笔记..........
---------------------------- 割 --------------------------------------------
实现的插件地址:
https://github.com/linqingwudiv1/RuntimeMeshLoaderextend
(居于https://github.com/GameInstitute/RuntimeMeshLoader 开源的修改)
原本的RuntimeMeshLoader 源码仅支持从Assimp数据到UProducalMeshComponent。
并不能满足我希望从assimp网格化数据到UStaticMesh的转换
所以就参考UE4源码的ProceduralMeshComponentEditor 模块的ProceduralMeshComponentDetails.cpp里的ClickedOnConvertToStaticMesh 函数,
对RuntimeMeshLoader插件进行扩展
予人肥皂,手有余香 ( 滑稽 )
可优化点记录: (随缘更新完善)
1.使用TBB加速
2.StaticMesh 单分段单材质,并导入格式自带贴图
3.SkeletalMesh的动态加载
4.可Transform变换
5.以及全平台支持(Assimp是支持全平台的,但是,目前我只编译了Windows 和 Mac OSX 平台下的Assimp Library(带 export 模块),linux或andorid/IOS等需要自己编译集成.a,.so)
6.Collision多方式支持
-------------------------------------- 割 -------------------------------------------
nOTE:有个挺严重的问题:打包后Collision失效,正在翻UStaticMesh的BodySetup尝试决解ing..(人生苦短我看源码,难啊!)
翻源码的分析过程记录:
目标:在不改动源码的的情况下,在Runtime环境下根据Assimp加载的网格创建UStaticMesh
主要参考 UE4 自带插件 UProceduralMeshComponent 的 Editor 功能:FProceduralMeshComponentDetails::ClickedOnConvertToStaticMesh 函数,
在使用assimp库导入原生信息时,Vertex到StaticMesh的过程,中间有遇到一些Editor函数转
通过assimp加载网格 环境完整代码参考:
.h
UENUM(BlueprintType) enum class EPathType : uint8 { Absolute UMETA(DisplayName = "Absolute"), Relative UMETA(DisplayName = "Relative") }; USTRUCT(BlueprintType) struct FMeshInfo { GENERATED_USTRUCT_BODY() UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector> Vertices; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") /** Vertices index */ TArray<int32> Triangles; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector> Normals; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector2D> UV0; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector2D> UV1; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector2D> UV2; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FVector2D> UV3; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FLinearColor> VertexColors; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FProcMeshTangent> Tangents; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") FTransform RelativeTransform; }; USTRUCT(BlueprintType) struct FReturnedData { GENERATED_USTRUCT_BODY() public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") /**/ bool bSuccess; /** Contain Mesh Count */ UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") int32 NumMeshes; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "ReturnedData") TArray<FMeshInfo> meshInfo; }; /** * */ UCLASS() class RUNTIMEMESHLOADER_API ULoaderBPFunctionLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: /** */ UFUNCTION( BlueprintCallable, Category="MeshLoader") static FReturnedData LoadMesh(const FString& filepath, const FTransform& tran, EPathType type= EPathType:: Absolute ); /** */ UFUNCTION( BlueprintCallable, Category = "MeshLoader", meta = ( HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject" ) ) static UStaticMesh* LoadMeshToStaticMesh( UObject* WorldContextObject, const FString& filepath, const FTransform& tran, EPathType type = EPathType::Absolute ); };
.cpp
void FindMeshInfo(const aiScene* scene, aiNode* node, FReturnedData& result,const FTransform &tran = FTransform()) { //transform... aiMatrix4x4 TranMat,tempMat; bool bTran = false; if ( !tran.GetLocation().Equals(FVector{ 0.0f }, 0.01f ) ) { bTran = true; TranMat = TranMat.Translation(aiVector3D{ tran.GetLocation().X, tran.GetLocation().Y, tran.GetLocation().Z }, tempMat); } if ( !tran.GetScale3D().Equals( FVector{ 1.0f }, 0.01f ) ) { bTran = true; TranMat = TranMat.Scaling(aiVector3D{ tran.GetScale3D().X, tran.GetScale3D().Y, tran.GetScale3D().Z }, tempMat); } if ( !tran.GetRotation().Equals( FRotator{ 0.0f }.Quaternion(), 0.01f ) ) { bTran = true; TranMat = TranMat.RotationX( PI / 180.f * tran.GetRotation().Rotator().Roll , tempMat ); TranMat = TranMat.RotationY( PI / 180.f * tran.GetRotation().Rotator().Yaw , tempMat ); TranMat = TranMat.RotationZ( PI / 180.f * tran.GetRotation().Rotator().Pitch , tempMat ); } // for (uint32 i = 0; i < node->) for (uint32 i = 0; i < node->mNumMeshes; i++) { std::string TestString = node->mName.C_Str(); FString Fs = FString(TestString.c_str()); UE_LOG(LogTemp, Warning, TEXT("FindMeshInfo. %s\n"), *Fs); int meshidx = *node->mMeshes; aiMesh *mesh = scene->mMeshes [ meshidx ]; FMeshInfo &mi = result.meshInfo [ meshidx ]; aiMatrix4x4 tempTrans = node->mTransformation; //如果变换 if (bTran) { tempTrans = tempTrans * TranMat; } FMatrix tempMatrix; // e.g // _______________ // | A0,B0,C0,D0 | // | A1,B1,C1,D1 | // | A2,B2,C2,D2 | // | A3,B3,C3,D3 | // |_____________| // tempMatrix.M[0][0] = tempTrans.a1; tempMatrix.M[0][1] = tempTrans.b1; tempMatrix.M[0][2] = tempTrans.c1; tempMatrix.M[0][3] = tempTrans.d1; tempMatrix.M[1][0] = tempTrans.a2; tempMatrix.M[1][1] = tempTrans.b2; tempMatrix.M[1][2] = tempTrans.c2; tempMatrix.M[1][3] = tempTrans.d2; tempMatrix.M[2][0] = tempTrans.a3; tempMatrix.M[2][1] = tempTrans.b3; tempMatrix.M[2][2] = tempTrans.c3; tempMatrix.M[2][3] = tempTrans.d3; tempMatrix.M[3][0] = tempTrans.a4; tempMatrix.M[3][1] = tempTrans.b4; tempMatrix.M[3][2] = tempTrans.c4; tempMatrix.M[3][3] = tempTrans.d4; // Mesh transform on scene mi.RelativeTransform = FTransform(tempMatrix); // fill Mesh Vertices 填充Mesh顶点 for (uint32 j = 0; j < mesh->mNumVertices; ++j) { FVector vertex = FVector ( mesh->mVertices[j].x , mesh->mVertices[j].y , mesh->mVertices[j].z ); vertex = mi.RelativeTransform.TransformFVector4(vertex); // vertex = mi.RelativeTransform.Trans mi.Vertices.Push( vertex ); //Normal if (mesh->HasNormals()) { FVector normal = FVector( mesh->mNormals[j].x , mesh->mNormals[j].y , mesh->mNormals[j].z );
if (bTran)
{
normal = mi.RelativeTransform.TransformFVector4(normal);
}
mi.Normals.Push(normal); } else { mi.Normals.Push(FVector::ZeroVector); } // UV0 Coordinates - inconsistent coordinates if (mesh->HasTextureCoords(0)) { FVector2D uv = FVector2D(mesh->mTextureCoords[0][j].x, -mesh->mTextureCoords[0][j].y); mi.UV0.Add(uv); } // UV1 Coordinates - inconsistent coordinates if (mesh->HasTextureCoords(1)) { FVector2D uv = FVector2D(mesh->mTextureCoords[1][j].x, -mesh->mTextureCoords[1][j].y); mi.UV1.Add(uv); } // UV2 Coordinates - inconsistent coordinates if (mesh->HasTextureCoords(2)) { FVector2D uv = FVector2D(mesh->mTextureCoords[2][j].x, -mesh->mTextureCoords[2][j].y); mi.UV2.Add(uv); } // UV3 Coordinates - inconsistent coordinates if (mesh->HasTextureCoords(3)) { FVector2D uv = FVector2D(mesh->mTextureCoords[3][j].x, -mesh->mTextureCoords[3][j].y); mi.UV3.Add(uv); } // Tangent /切线 if (mesh->HasTangentsAndBitangents()) { FProcMeshTangent meshTangent = FProcMeshTangent( mesh->mTangents[j].x, mesh->mTangents[j].y, mesh->mTangents[j].z ); mi.Tangents.Push(meshTangent); } //Vertex color if (mesh->HasVertexColors(0)) { FLinearColor color = FLinearColor( mesh->mColors[0][j].r, mesh->mColors[0][j].g, mesh->mColors[0][j].b, mesh->mColors[0][j].a ); mi.VertexColors.Push(color); } } } } void FindMesh(const aiScene* scene, aiNode* node, FReturnedData& retdata, const FTransform &tran) { FindMeshInfo(scene, node, retdata); // tree node for ( uint32 m = 0; m < node->mNumChildren; ++m ) { FindMesh(scene, node->mChildren[m], retdata, tran); } } /** * */ TMap<UMaterialInterface*, FPolygonGroupID> BuildMaterialMapExchange(FReturnedData& ReturnedData, /* UProceduralMeshComponent* ProcMeshComp ,*/ FMeshDescription& MeshDescription) { TMap<UMaterialInterface*, FPolygonGroupID> UniqueMaterials; const int32 NumSections = ReturnedData.meshInfo.Num(); //ProcMeshComp->GetNumSections(); UniqueMaterials.Reserve(NumSections); FStaticMeshAttributes AttributeGetter(MeshDescription); TPolygonGroupAttributesRef<FName> PolygonGroupNames = AttributeGetter.GetPolygonGroupMaterialSlotNames(); for ( int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++ ) { FMeshInfo MeshInfo = ReturnedData.meshInfo[SectionIdx]; // MeshInfo.Normals UMaterialInterface* Material = UMaterial::GetDefaultMaterial(MD_Surface); if ( !UniqueMaterials.Contains(Material) ) { FPolygonGroupID NewPolygonGroup = MeshDescription.CreatePolygonGroup(); UniqueMaterials.Add(Material, NewPolygonGroup); PolygonGroupNames[NewPolygonGroup] = Material->GetFName(); } } return UniqueMaterials; } /** * */ FMeshDescription BuildMeshDescriptionExtend( FReturnedData& MeshsData /* UProceduralMeshComponent* ProcMeshComp */) { FMeshDescription MeshDescription; FStaticMeshAttributes AttributeGetter(MeshDescription); AttributeGetter.Register(); TPolygonGroupAttributesRef<FName> PolygonGroupNames = AttributeGetter.GetPolygonGroupMaterialSlotNames(); TVertexAttributesRef<FVector> VertexPositions = AttributeGetter.GetVertexPositions(); TVertexInstanceAttributesRef<FVector> Tangents = AttributeGetter.GetVertexInstanceTangents(); TVertexInstanceAttributesRef<float> BinormalSigns = AttributeGetter.GetVertexInstanceBinormalSigns(); TVertexInstanceAttributesRef<FVector> Normals = AttributeGetter.GetVertexInstanceNormals(); TVertexInstanceAttributesRef<FVector4> Colors = AttributeGetter.GetVertexInstanceColors(); TVertexInstanceAttributesRef<FVector2D> UVs = AttributeGetter.GetVertexInstanceUVs(); // Materials to apply to new mesh const int32 NumSections = MeshsData.meshInfo.Num(); // ProcMeshComp->GetNumSections(); int32 VertexCount = 0; int32 VertexInstanceCount = 0; int32 PolygonCount = 0; TMap<UMaterialInterface*, FPolygonGroupID> UniqueMaterials = BuildMaterialMapExchange(MeshsData, MeshDescription); TArray<FPolygonGroupID> PolygonGroupForSection; PolygonGroupForSection.Reserve(NumSections); // Calculate the totals for each ProcMesh element type for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++) { FMeshInfo MeshInfo = MeshsData.meshInfo[SectionIdx]; VertexCount += MeshInfo.Vertices.Num () ; // ProcSection->ProcVertexBuffer.Num(); VertexInstanceCount += MeshInfo.Triangles.Num() ; // ProcSection->ProcIndexBuffer.Num(); PolygonCount += MeshInfo.Triangles.Num() / 3 ; // ProcSection->ProcIndexBuffer.Num() / 3; } MeshDescription.ReserveNewVertices(VertexCount); MeshDescription.ReserveNewVertexInstances(VertexInstanceCount); MeshDescription.ReserveNewPolygons( PolygonCount ); MeshDescription.ReserveNewEdges( PolygonCount * 2); UVs.SetNumIndices(4); // Create the Polygon Groups for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++) { FMeshInfo MeshInfo = MeshsData.meshInfo[SectionIdx]; UMaterialInterface* Material = UMaterial::GetDefaultMaterial(MD_Surface); FPolygonGroupID* PolygonGroupID = UniqueMaterials.Find(Material); check( PolygonGroupID != nullptr ); PolygonGroupForSection.Add(*PolygonGroupID); } // Add Vertex and VertexInstance and polygon for each section for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++) { FMeshInfo MeshInfo = MeshsData.meshInfo[SectionIdx]; FPolygonGroupID PolygonGroupID = PolygonGroupForSection[SectionIdx]; // Create the vertex int32 NumVertex = MeshInfo.Vertices.Num(); TMap<int32, FVertexID> VertexIndexToVertexID; VertexIndexToVertexID.Reserve(NumVertex); for (int32 VertexIndex = 0; VertexIndex < NumVertex; ++VertexIndex) { FVector Vert = MeshInfo.Vertices[VertexIndex]; const FVertexID VertexID = MeshDescription.CreateVertex(); VertexPositions[VertexID] = Vert; VertexIndexToVertexID.Add(VertexIndex, VertexID); } // Create the VertexInstance int32 NumIndices = MeshInfo.Triangles.Num(); int32 NumTri = NumIndices / 3; TMap<int32, FVertexInstanceID> IndiceIndexToVertexInstanceID; IndiceIndexToVertexInstanceID.Reserve(NumVertex); for (int32 IndiceIndex = 0; IndiceIndex < NumIndices; IndiceIndex++) { const int32 VertexIndex = MeshInfo.Triangles[IndiceIndex]; const FVertexID VertexID = VertexIndexToVertexID[VertexIndex]; const FVertexInstanceID VertexInstanceID = MeshDescription.CreateVertexInstance(VertexID); IndiceIndexToVertexInstanceID.Add(IndiceIndex, VertexInstanceID); FVector ProcVertex = MeshInfo.Vertices[VertexIndex]; // FProcMeshVertex& ProcVertex = ProcSection->ProcVertexBuffer[VertexIndex]; FProcMeshTangent VertexTanents = MeshInfo.Tangents[VertexIndex]; FLinearColor VertexColor = MeshInfo.VertexColors.Num() > VertexIndex ? MeshInfo.VertexColors[VertexIndex] : FLinearColor(1.0, 0.0, 0.0); Tangents[VertexInstanceID] = VertexTanents.TangentX; // ProcVertex.Tangent.TangentX; Normals[VertexInstanceID] = MeshInfo.Normals[VertexIndex]; // ProcVertex.Normal; BinormalSigns[VertexInstanceID] = VertexTanents.bFlipTangentY ? -1.f : 1.f; Colors[VertexInstanceID] = VertexColor; //FLinearColor(ProcVertex.Color); if ( MeshInfo.UV0.Num() > VertexIndex) { UVs.Set(VertexInstanceID, 0, MeshInfo.UV0[VertexIndex]); } if ( MeshInfo.UV1.Num() > VertexIndex) { UVs.Set(VertexInstanceID, 1, MeshInfo.UV1[VertexIndex]); } if ( MeshInfo.UV2.Num() > VertexIndex ) { UVs.Set(VertexInstanceID, 2, MeshInfo.UV2[VertexIndex]); } if ( MeshInfo.UV3.Num() > VertexIndex ) { UVs.Set(VertexInstanceID, 3, MeshInfo.UV3[VertexIndex]); } } // Create the polygons for this section for (int32 TriIdx = 0; TriIdx < NumTri; TriIdx++) { FVertexID VertexIndexes[3]; TArray<FVertexInstanceID> VertexInstanceIDs; VertexInstanceIDs.SetNum(3); for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { const int32 IndiceIndex = (TriIdx * 3) + CornerIndex; const int32 VertexIndex = MeshInfo.Triangles[IndiceIndex]; //ProcSection->ProcIndexBuffer[IndiceIndex]; VertexIndexes[CornerIndex] = VertexIndexToVertexID[VertexIndex]; VertexInstanceIDs[CornerIndex] = IndiceIndexToVertexInstanceID[IndiceIndex]; } // Insert a polygon into the mesh MeshDescription.CreatePolygon(PolygonGroupID, VertexInstanceIDs); } } return MeshDescription; } UStaticMesh* ULoaderBPFunctionLibrary::LoadMeshToStaticMesh( UObject* WorldContextObject, const FString& filepath, const FTransform& tran, EPathType type /* = EPathType::Absolute */ ) { FReturnedData&& MeshInfo = ULoaderBPFunctionLibrary::LoadMesh(filepath, tran, type); FString NewNameSuggestion = FString(TEXT("ProcMesh")); FString PackageName = FString(TEXT("/Game/Meshes/")) + NewNameSuggestion; FString Name; FString UserPackageName = TEXT(""); FName MeshName(*FPackageName::GetLongPackageAssetName(UserPackageName)); // Check if the user inputed a valid asset name, if they did not, give it the generated default name if (MeshName == NAME_None) { // Use the defaults that were already generated. UserPackageName = PackageName; MeshName = *Name; } FMeshDescription MeshDescription = BuildMeshDescriptionExtend(MeshInfo); UStaticMesh* StaticMesh = NewObject<UStaticMesh>(WorldContextObject, MeshName, RF_Public | RF_Standalone); StaticMesh->InitResources(); StaticMesh->LightingGuid = FGuid::NewGuid(); TArray<const FMeshDescription*> arr; arr.Add(&MeshDescription); StaticMesh->BuildFromMeshDescriptions(arr, false); //// MATERIALS TSet<UMaterialInterface*> UniqueMaterials; const int32 NumSections = 1; for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++) { UMaterialInterface* Material = UMaterial::GetDefaultMaterial(MD_Surface); UniqueMaterials.Add(Material); } // Copy materials to new mesh int32 MaterialID = 0; for (UMaterialInterface* Material : UniqueMaterials) { // Material FStaticMaterial&& StaticMat = FStaticMaterial(Material); StaticMat.UVChannelData.bInitialized = true; StaticMesh->StaticMaterials.Add(StaticMat); #pragma region 模拟填充 FMeshSectionInfo FStaticMeshRenderData* const RenderData = StaticMesh->RenderData.Get(); int32 LODIndex = 0; int32 MaxLODs = RenderData->LODResources.Num(); for (; LODIndex < MaxLODs; ++LODIndex) { FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex]; for (int32 SectionIndex = 0; SectionIndex < LOD.Sections.Num(); ++SectionIndex) { FStaticMeshSection& Section = LOD.Sections[SectionIndex]; Section.MaterialIndex = MaterialID; Section.bEnableCollision = true; Section.bCastShadow = true; Section.bForceOpaque = false; } } #pragma endregion MaterialID++; } return StaticMesh; }