Virtual Texture and Procedural Mesh

Hello,

Does anyone know how Procedurally created Meshes can draw a Virtual Texture?

I have created an example in ContentExamples. The appearance of the static mesh and the procedural mesh should be projected onto the underlying surface see pic1

.

It seems that it only works for static mesh.
But it works for the splines meshes in the Landscape tool. (see pic2)

I hope someone can help. :slight_smile:

Would love the answer to this as well!

Good evening Epic,
is there a solution or maybe an update of the engine to solve the problem?

Ahh shoot. I was wondering why it doesn’t work.

I think i got answer for your question as I got stuck with same problem. Seems That Runtime virtual texturing is processed in “StaticPipeline” while UProceduralMeshComponent only useded "DynamicPipeline.

Just to tell where it is in the code - UProceduralMeshComponent prepares FMeshBatch in GetDynamicMeshElements - as it was created to support dynamic geometry. In order to get UProceduralMeshComponent working with RVT we would need to have function :

virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)

implemented in UProceduralMeshComponent mesh to get it working for virtualTextures - and used only for virtual textures or there is need to add virutal textures for dynamic geometry.

If we get DrawStaticElements implemented for UProceduralMeshComponent there are two options we dont change geometry or we get performance hit.

I can give you my implementation of this method - as my own UProceduralMeshComponent is used for procedural static geometry and i’m using single section:

virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
{
	const FRYCProcMeshProxySection* Section = Sections[0];
	UMaterialInterface* SectionMat = Section->Material;
	check(SectionMat);

	FMeshBatch MeshBatch;
	MeshBatch.LODIndex = 0;
	MeshBatch.SegmentIndex = 0;

	MeshBatch.VertexFactory = &MeshVertexBuffersSection->VertexFactory;
	MeshBatch.Type = PT_TriangleList;

	MeshBatch.LODIndex = 0;
	MeshBatch.SegmentIndex = 0;

	MeshBatch.bDitheredLODTransition = !IsMovable() && Section->Material->GetRenderProxy()->GetMaterialInterface()->IsDitheredLODTransition();
	MeshBatch.bWireframe = false;
	MeshBatch.CastShadow = false;
	MeshBatch.bUseForDepthPass = false;
	MeshBatch.bUseAsOccluder = false;
	MeshBatch.bUseForMaterial = false;		
	MeshBatch.bRenderToVirtualTexture = true;
	MeshBatch.MaterialRenderProxy = Section->Material->GetRenderProxy();
	MeshBatch.RuntimeVirtualTextureMaterialType = (int32)ERuntimeVirtualTextureMaterialType::BaseColor_Normal_Specular;
	MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();

	MeshBatch.DepthPriorityGroup = SDPG_World;
	MeshBatch.bCanApplyViewModeOverrides = false;

	MeshBatch.Elements.Empty(1);
	FMeshBatchElement BatchElement;
	//BatchElement.PrimitiveUniformBuffer = GetUniformBuffer();
	BatchElement.IndexBuffer = &MeshIndexBuffersSection->IndexBuffer;
	BatchElement.FirstIndex = 0;
	BatchElement.NumPrimitives = MeshIndexBuffersSection->IndexBuffer.Indices.Num() / 3;
	BatchElement.MinVertexIndex = 0;
	//BatchElement.IndexBuffer = CurrentIndexBuffer;
	//BatchElement.NumPrimitives = NumPrimitives;
	BatchElement.MaxVertexIndex = MeshVertexBuffersSection->VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1;		
	MeshBatch.Elements.Add(BatchElement);
	
	PDI->DrawMesh(MeshBatch, FLT_MAX);
	
}

Hope that this helps!:slight_smile:

UProceduralMeshComponents is using “Dynamic draw pipeline” while Virtual Texture is created in “Static Draw pipeline”.
In order to make runtime virtual textures working with UProceduralMeshComponent it would need to have following function implemented just for RVT:

virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)

I came across same problem and solved it by creating my own UProceduralMeshComponent with addition of DrawStaticElements method and it works just fine. In my case i’m not modyfing mesh very often so performance is good.

UProceduralMeshComponents is using “Dynamic draw pipeline” while Virtual Texture is created in “Static Draw pipeline”.
In order to make runtime virtual textures working with UProceduralMeshComponent it would need to have following function implemented just for RVT:

virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)

I came across same problem and solved it by creating my own UProceduralMeshComponent with addition of DrawStaticElements method and it works just fine. In my case i’m not modyfing mesh very often so performance is good.

Here is example of code to this DrawStaticElements method:

virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
{
	if( RuntimeVirtualTextures.Num() == 0 )
	{
		return;
	}
	const FRYCProcMeshProxySection* Section = Sections[0];
	UMaterialInterface* SectionMat = Section->Material;
	check(SectionMat);

	FMeshBatch MeshBatch;
	MeshBatch.LODIndex = 0;
	MeshBatch.SegmentIndex = 0;

	MeshBatch.VertexFactory = &MeshVertexBuffersSection->VertexFactory;
	MeshBatch.Type = PT_TriangleList;

	MeshBatch.LODIndex = 0;
	MeshBatch.SegmentIndex = 0;

	MeshBatch.bDitheredLODTransition = !IsMovable() && Section->Material->GetRenderProxy()->GetMaterialInterface()->IsDitheredLODTransition();

	MeshBatch.bRenderToVirtualTexture = true;
	MeshBatch.MaterialRenderProxy = Section->Material->GetRenderProxy();
	MeshBatch.RuntimeVirtualTextureMaterialType = (int32)ERuntimeVirtualTextureMaterialType::BaseColor_Normal_Specular;
	MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();

	MeshBatch.DepthPriorityGroup = SDPG_World;
	MeshBatch.bCanApplyViewModeOverrides = false;

	MeshBatch.Elements.Empty(1);
	FMeshBatchElement BatchElement;
	BatchElement.IndexBuffer = &MeshIndexBuffersSection->IndexBuffer;
	BatchElement.FirstIndex = 0;
	BatchElement.NumPrimitives = MeshIndexBuffersSection->IndexBuffer.Indices.Num() / 3;
	BatchElement.MinVertexIndex = 0;
	BatchElement.MaxVertexIndex = MeshVertexBuffersSection->VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1;		
	MeshBatch.Elements.Add(BatchElement);
	
	PDI->DrawMesh(MeshBatch, FLT_MAX);
	
}

could you please explain a bit more about what is needed to make this work? UProceduralMeshComponent does not overload this DrawStaticElements function, so where does it come from and how do i make sure it gets used? also it seems that the code you posted contains a bit more custom stuff, like the FRYCProcMeshProxySection for example. is this also necessary?

Hi,

Yes, so UProceduralMeshComponent doesnt have this element as UProceduralMeshComponent is representation of Procedural Mesh in GameThread.
Rendering stuff in which you are interested in is defined in FProceduralMeshSceneProxy - which is representaition on Rendering Thread. Code for it should be available in .cpp file of UProceduralMeshComponent.
FProceduralMeshSceneProxy extends FPrimitiveSceneProxy and this one has DrawStaticElements method.

Now there are two options - you can modify engine and recompile it to have modified version of Procedural Mesh Component or you can create (copy) procedural mesh component and modify it so it supports virtual texturing using code I’ve placed.

In the code I’ve placed you are right there is FRYCProcMeshProxySection as I’ve created my own procedural mesh component. The one I’ve created is based on ProceduralMeshComponent but modified. In order to made my code work with Procedural Mesh Component you would just need to change FRYCProcMeshProxySection to FProcMeshProxySection as FProcMeshProxySection is used internally by Procedural Mesh Component.

Hope that this helps.

Thank you for your reply.
Are you telling me that I should copy the whole ProceduralMeshComponent code, in the FProceduralMeshSceneProxy implementation override the DrawStaticElements method, and then use the new copied component instead of making a component that inherits from it?
I am unfortunately not very familiar with rendering or how it works in unreal, but i would really like to get this working.

Thanks Czochcio, I modified the code like this . It’s worked for me

0001-UProceduralMeshSceneComponent-support-draw-in-virtua.patch (3.0 KB)

	virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override
	{
		if (RuntimeVirtualTextures.Num())
		{
			for (const FProcMeshProxySection* Section : Sections)
			{
				UMaterialInterface* SectionMat = Section->Material;
				check(SectionMat);

				FMeshBatch MeshBatch;
				MeshBatch.LODIndex = 0;
				MeshBatch.SegmentIndex = 0;

				MeshBatch.VertexFactory = &Section->VertexFactory;
				MeshBatch.Type = PT_TriangleList;

				MeshBatch.LODIndex = 0;
				MeshBatch.SegmentIndex = 0;

				MeshBatch.bDitheredLODTransition = !IsMovable() && Section->Material->GetRenderProxy()->GetMaterialInterface()->IsDitheredLODTransition();
				MeshBatch.bWireframe = false;
				MeshBatch.CastShadow = false;
				MeshBatch.bUseForDepthPass = false;
				MeshBatch.bUseAsOccluder = false;
				MeshBatch.bUseForMaterial = false;
				MeshBatch.bRenderToVirtualTexture = true;
				MeshBatch.MaterialRenderProxy = Section->Material->GetRenderProxy();
				MeshBatch.RuntimeVirtualTextureMaterialType = (int32)ERuntimeVirtualTextureMaterialType::BaseColor_Normal_Specular;
				MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();

				MeshBatch.DepthPriorityGroup = SDPG_World;
				MeshBatch.bCanApplyViewModeOverrides = false;

				MeshBatch.Elements.Empty(1);
				FMeshBatchElement BatchElement;
				BatchElement.IndexBuffer = &Section->IndexBuffer;
				BatchElement.FirstIndex = 0;
				BatchElement.NumPrimitives = Section->IndexBuffer.Indices.Num() / 3;
				BatchElement.MinVertexIndex = 0;
				BatchElement.MaxVertexIndex = Section->VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1;
				MeshBatch.Elements.Add(BatchElement);

				PDI->DrawMesh(MeshBatch, FLT_MAX);
			}
		}
	}
1 Like

Is it possible to do the same for meshes generated by geometry script in ue5?

I ended up converting the generated mesh (Geometry script) to a runtime static mesh. That works with RVT. just pass the Target Mesh to this this function:

 UStaticMesh* UGeoConverter::DynamicToStaticMesh(const UDynamicMesh* DynamicMesh, const FName Name)
{
	// Create a new static mesh with unique name
	const UWorld* World = DynamicMesh->GetWorld();
	UStaticMesh* StaticMesh = NewObject<UStaticMesh>(World->GetCurrentLevel(), Name, RF_Transient);

	// Get the static mesh source model
	FStaticMeshSourceModel& SrcModel = StaticMesh->AddSourceModel();
	SrcModel.BuildSettings.bRecomputeNormals = false;

	FMeshDescription MeshDescription;
	FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
	StaticMeshAttributes.Register();

	const FDynamicMesh3* Mesh = DynamicMesh->GetMeshPtr();

	FDynamicMeshToMeshDescription Converter;
	Converter.Convert(Mesh, MeshDescription);
	
	// Build the static mesh render data, one FMeshDescription* per LOD.
	TArray<const FMeshDescription*> MeshDescriptionPtrs;
	MeshDescriptionPtrs.Emplace(&MeshDescription);
	StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs);
	
	return StaticMesh;
}

You also need to include some modules
“GeometryFramework” “StaticMeshDescription”, “MeshDescription”, “MeshConversion”

1 Like

Test_UE5_006_20230317.zip (883.0 KB)
I tried doing the same with the mesh generated by the geometry script in the ue5 runtime, but it had no effect when running or packaging the runtime

	virtual void  FPlottingDynamicMeshSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override
{
	if (RuntimeVirtualTextures.Num())
	{
		TArray<FMeshRenderBufferSet*> Buffers;
		GetActiveRenderBufferSets(Buffers);
		for (const FMeshRenderBufferSet* Buffer : Buffers)
		{
			UMaterialInterface* BufferMat = Buffer->Material;
			check(BufferMat);

			FMeshBatch MeshBatch;
			MeshBatch.LODIndex = 0;
			MeshBatch.SegmentIndex = 0;

			MeshBatch.VertexFactory = &Buffer->VertexFactory;
			MeshBatch.Type = PT_TriangleList;

			MeshBatch.LODIndex = 0;
			MeshBatch.SegmentIndex = 0;

			MeshBatch.bDitheredLODTransition = !IsMovable() && Buffer->Material->GetRenderProxy()->GetMaterialInterface()->IsDitheredLODTransition();
			MeshBatch.bWireframe = false;
			MeshBatch.CastShadow = false;
			MeshBatch.bUseForDepthPass = false;
			MeshBatch.bUseAsOccluder = false;
			MeshBatch.bUseForMaterial = false;
			MeshBatch.bRenderToVirtualTexture = true;
			MeshBatch.MaterialRenderProxy = Buffer->Material->GetRenderProxy();
			MeshBatch.RuntimeVirtualTextureMaterialType = (int32)ERuntimeVirtualTextureMaterialType::BaseColor_Normal_Specular;
			MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative();

			MeshBatch.DepthPriorityGroup = SDPG_World;
			MeshBatch.bCanApplyViewModeOverrides = false;

			MeshBatch.Elements.Empty(1);
			FMeshBatchElement BatchElement;
			BatchElement.IndexBuffer = &Buffer->IndexBuffer;
			BatchElement.FirstIndex = 0;
			BatchElement.NumPrimitives = Buffer->IndexBuffer.Indices.Num() / 3;
			BatchElement.MinVertexIndex = 0;
			BatchElement.MaxVertexIndex = Buffer->StaticMeshVertexBuffer.GetNumVertices() - 1;
			MeshBatch.Elements.Add(BatchElement);

			PDI->DrawMesh(MeshBatch, FLT_MAX);
		}
	}
}
1 Like

Solved, the code RuntimeVirtualTextureMaterialType is the same as the scene.

MeshBatch.RuntimeVirtualTextureMaterialType = (int32)ERuntimeVirtualTextureMaterialType::BaseColor_Normal_Roughness;
1 Like

I modified it a little bit to fit the runtime conversion.

void URuntimeToolBPLibrary::DynamicToStaticMesh(const UDynamicMesh* DynamicMesh, const FName Name, UStaticMesh*& StaticMesh)
{
	if (DynamicMesh == nullptr)
		return;

	const UWorld* World = DynamicMesh->GetWorld();
	UStaticMesh* LocalStaticMesh = NewObject<UStaticMesh>(World->GetCurrentLevel(), Name, RF_Transient);

	LocalStaticMesh->bAllowCPUAccess = true;
	LocalStaticMesh->bSupportGpuUniformlyDistributedSampling = true;

	LocalStaticMesh->SetLightingGuid();

	FMeshDescription MeshDescription;
	FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
	StaticMeshAttributes.Register();

	const FDynamicMesh3* Mesh = DynamicMesh->GetMeshPtr();

	FDynamicMeshToMeshDescription Converter;
	Converter.Convert(Mesh, MeshDescription);

	TArray<const FMeshDescription*> MeshDescriptionPtrs;
	MeshDescriptionPtrs.Emplace(&MeshDescription);

	UStaticMesh::FBuildMeshDescriptionsParams mdParams;
	//mdParams.bBuildSimpleCollision = true;
	mdParams.bFastBuild = true;
	mdParams.bAllowCpuAccess = true;
	mdParams.bCommitMeshDescription = true;

	LocalStaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs, mdParams);

	FStaticMeshRenderData* LocalStaticMeshRenderData = LocalStaticMesh->GetRenderData();
	FStaticMeshLODResources* LocalStaticMeshLODResources = const_cast<FStaticMeshLODResources *>(LocalStaticMeshRenderData->GetCurrentFirstLOD(0));
	if (LocalStaticMeshLODResources && LocalStaticMeshLODResources->Sections.Num() > 0)
	{
		for (int i = 0; LocalStaticMeshLODResources->Sections.Num() > i; i++)
		{
			LocalStaticMeshLODResources->Sections[i].MaterialIndex = 0;
		}
	}

	StaticMesh = LocalStaticMesh;
}
1 Like