BlueRoseNote/03-UnrealEngine/Gameplay/Gameplay/Assimp & UProceduralMeshComponent实时载入StaticMesh.md

10 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Untitled 2025-01-05 11:32:25

Assimp

加载选项:

  • aiProcess_JoinIdenticalVertices
  • aiProcess_RemoveComponent
  • aiProcess_OptimizeMeshes推荐移除会导致模型只有一个Material。

真正的数据都存储在Scene节点mMeshes[]mMaterials[]中。其RootNode、ChildrenNode存储对应的数据Index。

  • mMeshes[]
    • mVertices[]
    • mNormals[]
    • mTextureCoords[]
    • mFaces[]
      • mIndices[]
    • mMaterialIndex

Materials

aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];

可用组件

URuntimeMeshComponent

第三方插件实现。

UDynamicMeshComponent

  1. https://zhuanlan.zhihu.com/p/506779703
  2. https://www.bilibili.com/opus/798754326935764996
  3. https://zhuanlan.zhihu.com/p/649062059

UProceduralMeshComponent

使用现在StaticMesh构建PMCPMC的MaterialSection是正常的。

  1. UKismetProceduralMeshLibrary::CopyProceduralMeshFromStaticMeshComponent()
void UKismetProceduralMeshLibrary::CopyProceduralMeshFromStaticMeshComponent(UStaticMeshComponent* StaticMeshComponent, int32 LODIndex, UProceduralMeshComponent* ProcMeshComponent, bool bCreateCollision)
{
	if( StaticMeshComponent != nullptr && 
		StaticMeshComponent->GetStaticMesh() != nullptr &&
		ProcMeshComponent != nullptr )
	{
		UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();

		//// MESH DATA

		int32 NumSections = StaticMesh->GetNumSections(LODIndex);
		for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++)
		{
			// Buffers for copying geom data
			TArray<FVector> Vertices;
			TArray<int32> Triangles;
			TArray<FVector> Normals;
			TArray<FVector2D> UVs;
			TArray<FVector2D> UVs1;
			TArray<FVector2D> UVs2;
			TArray<FVector2D> UVs3;
			TArray<FProcMeshTangent> Tangents;

			// Get geom data from static mesh
			GetSectionFromStaticMesh(StaticMesh, LODIndex, SectionIndex, Vertices, Triangles, Normals, UVs, Tangents);

			// Create section using data
			TArray<FLinearColor> DummyColors;
			ProcMeshComponent->CreateMeshSection_LinearColor(SectionIndex, Vertices, Triangles, Normals, UVs, UVs1, UVs2, UVs3, DummyColors, Tangents, bCreateCollision);
		}

		//// SIMPLE COLLISION

		// Clear any existing collision hulls
		ProcMeshComponent->ClearCollisionConvexMeshes();

		if (StaticMesh->GetBodySetup() != nullptr)
		{
			// Iterate over all convex hulls on static mesh..
			const int32 NumConvex = StaticMesh->GetBodySetup()->AggGeom.ConvexElems.Num();
			for (int ConvexIndex = 0; ConvexIndex < NumConvex; ConvexIndex++)
			{
				// Copy convex verts to ProcMesh
				FKConvexElem& MeshConvex = StaticMesh->GetBodySetup()->AggGeom.ConvexElems[ConvexIndex];
				ProcMeshComponent->AddCollisionConvexMesh(MeshConvex.VertexData);
			}
		}

		//// MATERIALS

		for (int32 MatIndex = 0; MatIndex < StaticMeshComponent->GetNumMaterials(); MatIndex++)
		{
			ProcMeshComponent->SetMaterial(MatIndex, StaticMeshComponent->GetMaterial(MatIndex));
		}
	}
}
void UKismetProceduralMeshLibrary::GetSectionFromStaticMesh(UStaticMesh* InMesh, int32 LODIndex, int32 SectionIndex, TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FProcMeshTangent>& Tangents)
{
	if(	InMesh != nullptr )
	{
		if (!InMesh->bAllowCPUAccess)
		{
			FMessageLog("PIE").Warning()
				->AddToken(FTextToken::Create(LOCTEXT("GetSectionFromStaticMeshStart", "Calling GetSectionFromStaticMesh on")))
				->AddToken(FUObjectToken::Create(InMesh))
				->AddToken(FTextToken::Create(LOCTEXT("GetSectionFromStaticMeshEnd", "but 'Allow CPU Access' is not enabled. This is required for converting StaticMesh to ProceduralMeshComponent in cooked builds.")));
		}

		if (InMesh->GetRenderData() != nullptr && InMesh->GetRenderData()->LODResources.IsValidIndex(LODIndex))
		{
			const FStaticMeshLODResources& LOD = InMesh->GetRenderData()->LODResources[LODIndex];
			if (LOD.Sections.IsValidIndex(SectionIndex))
			{
				// Empty output buffers
				Vertices.Reset();
				Triangles.Reset();
				Normals.Reset();
				UVs.Reset();
				Tangents.Reset();

				// Map from vert buffer for whole mesh to vert buffer for section of interest
				TMap<int32, int32> MeshToSectionVertMap;

				const FStaticMeshSection& Section = LOD.Sections[SectionIndex];//获取指定的MeshSection
				const uint32 OnePastLastIndex = Section.FirstIndex + Section.NumTriangles * 3;//计算最后一个VertexIndex
				FIndexArrayView Indices = LOD.IndexBuffer.GetArrayView();//获得IndexArray

				//遍历所有IndexBuffer并且复制顶点
				for (uint32 i = Section.FirstIndex; i < OnePastLastIndex; i++)
				{
					uint32 MeshVertIndex = Indices[i];//取得VertexIndex

					// See if we have this vert already in our section vert buffer, and copy vert in if not 
					// 从VertexBuffers.StaticMeshVertexBuffer中读取并且添加Vertex Position、Normal、UVs、Tangents构建MeshToSectionVertMap作为缓存如果能在Map找到则直接返回Index。
					int32 SectionVertIndex = GetNewIndexForOldVertIndex(MeshVertIndex, MeshToSectionVertMap, LOD.VertexBuffers, Vertices, Normals, UVs, Tangents);

					// Add to index buffer
					Triangles.Add(SectionVertIndex);
				}
			}
		}
	}
}

static int32 GetNewIndexForOldVertIndex(int32 MeshVertIndex, TMap<int32, int32>& MeshToSectionVertMap, const FStaticMeshVertexBuffers& VertexBuffers, TArray<FVector>& Vertices, TArray<FVector>& Normals, TArray<FVector2D>& UVs, TArray<FProcMeshTangent>& Tangents)
{
	int32* NewIndexPtr = MeshToSectionVertMap.Find(MeshVertIndex);
	if (NewIndexPtr != nullptr)
	{
		return *NewIndexPtr;
	}
	else
	{
		// Copy position
		int32 SectionVertIndex = Vertices.Add((FVector)VertexBuffers.PositionVertexBuffer.VertexPosition(MeshVertIndex));

		// Copy normal
		Normals.Add(FVector4(VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(MeshVertIndex)));
		check(Normals.Num() == Vertices.Num());

		// Copy UVs
		UVs.Add(FVector2D(VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(MeshVertIndex, 0)));
		check(UVs.Num() == Vertices.Num());

		// Copy tangents
		FVector4 TangentX = (FVector4)VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(MeshVertIndex);
		FProcMeshTangent NewTangent(TangentX, TangentX.W < 0.f);
		Tangents.Add(NewTangent);
		check(Tangents.Num() == Vertices.Num());

		MeshToSectionVertMap.Add(MeshVertIndex, SectionVertIndex);

		return SectionVertIndex;
	}
}

Editor转换成StaticMesh逻辑 FProceduralMeshComponentDetails::ClickedOnConvertToStaticMesh()

https://forums.unrealengine.com/t/procedural-mesh-not-saving-all-of-its-sections-to-static-mesh/382319/10 感觉链接中的这个代码是正确:

//Hallo from Unreal Forums
UStaticMesh* UWeedFarmerBFL::SaveProcmesh(UProceduralMeshComponent* ProcMesh, FString SavePath, FString Name)
{
	UProceduralMeshComponent* ProcMeshComp = ProcMesh;
	if (ProcMeshComp != nullptr)
	{
		FString PackageName = SavePath;
		FRawMesh RawMesh;
		TArray<UMaterialInterface*> MeshMaterials;
		const int32 NumSections = ProcMeshComp->GetNumSections();
		int32 VertexBase = 0;
		for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
		{
			FProcMeshSection* ProcSection = ProcMeshComp->GetProcMeshSection(SectionIdx);
			// Copy verts
			for (FProcMeshVertex& Vert : ProcSection->ProcVertexBuffer)
			{
				RawMesh.VertexPositions.Add(FVector3f(Vert.Position));
			}
			// Copy 'wedge' info
			int32 NumIndices = ProcSection->ProcIndexBuffer.Num();
			for (int32 IndexIdx = 0; IndexIdx < NumIndices; IndexIdx++)
			{
				int32 Index = ProcSection->ProcIndexBuffer[IndexIdx];
				RawMesh.WedgeIndices.Add(Index + VertexBase);
				FProcMeshVertex& ProcVertex = ProcSection->ProcVertexBuffer[Index];
				FVector3f TangentX = FVector3f(ProcVertex.Tangent.TangentX);
				FVector3f TangentZ = FVector3f(ProcVertex.Normal);
				FVector3f TangentY = FVector3f(
					(TangentX ^ TangentZ).GetSafeNormal() * (ProcVertex.Tangent.bFlipTangentY ? -1.f : 1.f));
				RawMesh.WedgeTangentX.Add(TangentX);
				RawMesh.WedgeTangentY.Add(TangentY);
				RawMesh.WedgeTangentZ.Add(TangentZ);
				RawMesh.WedgeTexCoords[0].Add(FVector2f(ProcVertex.UV0));
				RawMesh.WedgeColors.Add(ProcVertex.Color);
			}
			// copy face info
			int32 NumTris = NumIndices / 3;
			for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++)
			{
				RawMesh.FaceMaterialIndices.Add(SectionIdx);
				RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
			}
			// Remember material
			MeshMaterials.Add(ProcMeshComp->GetMaterial(SectionIdx));
			// Update offset for creating one big index/vertex buffer
			VertexBase += ProcSection->ProcVertexBuffer.Num();
		}
		// If we got some valid data.
		if (RawMesh.VertexPositions.Num() > 3 && RawMesh.WedgeIndices.Num() > 3)
		{
			// Then find/create it.
			UPackage* Package = CreatePackage(*PackageName);
			check(Package);
			// Create StaticMesh object
			UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, FName(*Name), RF_Public | RF_Standalone);
			StaticMesh->InitResources();
			FGuid::NewGuid() = StaticMesh->GetLightingGuid();
			//StaticMesh->GetLightingGuid() = FGuid::NewGuid();
			// Create a Source Model then set it to variable
			StaticMesh->AddSourceModel();
			FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(0);
			// Add source to new StaticMesh
			SrcModel.BuildSettings.bRecomputeNormals = false;
			SrcModel.BuildSettings.bRecomputeTangents = false;
			SrcModel.BuildSettings.bRemoveDegenerates = false;
			SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
			SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
			SrcModel.BuildSettings.bGenerateLightmapUVs = true;
			SrcModel.BuildSettings.SrcLightmapIndex = 0;
			SrcModel.BuildSettings.DstLightmapIndex = 1;
			SrcModel.RawMeshBulkData->SaveRawMesh(RawMesh);
			// Copy materials to new mesh
			for (UMaterialInterface* Material : MeshMaterials)
			{
				StaticMesh->GetStaticMaterials().Add(FStaticMaterial(Material));
			}
			//Set the Imported version before calling the build
			StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
			// Build mesh from source
			StaticMesh->Build(false);
			StaticMesh->PostEditChange();
			// Notify asset registry of new asset
			FAssetRegistryModule::AssetCreated(StaticMesh);
			return StaticMesh;
		}
	}
	return nullptr;
}

RuntimeFBXImport