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
"), *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;
}
原文地址:https://www.cnblogs.com/linqing/p/13970414.html