Runtime Mesh Component

Ok. A few weeks back I said that I hoped to have v2 out a few weeks ago if all went to plan. Obviously it didn’t unfortunately so here’s where it stands.

First up the marketplace doesn’t support 4.13 yet, I just sent a message to Epic to ask them to update it as it should be updated soon. V1.2 the current marketplace version is what will be updated until v2 is done, which I’m working on completing asap but honestly can’t give a definitive date, just hopefully within a week.

Next, what is currently done and on github for v2.

  • RuntimeMesh <-> StaticMesh conversions. This should work in both directions in editor with all engine versions 4.10+, and it also supports RMC -> SMC at runtime on engine 4.13+
  • The new vertex struct is done and up, with that much better serialization support will be soon.
  • Mesh builder, this is used internally to support the utilities and you can use it as well. It still needs some work, and would welcome input as to what would work best.

What’s coming to github very soon (hopefully today/tomorrow)

  • Normal/Tangent calculation
  • Tessellation
  • A couple of bug fixes

The big remaining part is collision for v2.
The expectation here, assuming everything works as intended is the RMC will gain 3 modes of operation when it comes to collision. The first is the normal cook on commit which will halt the game thread and will be inserted on the following frame. This is the way the PMC and the RMC currently operate. The second is an internal thread pool. When you commit an update to a section it will queue a build on a shared threadpool and will update collision whenever the cook finishes. This might be on the next frame, it might not, but it won’t halt the game thread to cook. The third is an option you have to supply the RMC with cooked collision data, and a utility you can call from your own threads to cook a mesh. This means you have complete control of what thread cooks it, and when it gets inserted. You can even save the cooked result and load it back later.

V2 of the RMC will still support engine versions 4.10+ and certain features like the extended collision support and SMC -> RMC runtime conversion will only enable themselves and function when they’re used with a supported engine version. In the case of SMC->RMC runtime conversion that is engine version 4.13 and the collision is TBD as that will require engine changes to hopefully work through the PR process.

If you have questions/comments, feel free to message me on the Unreal Slackers new Discord.

Github: https://github.com//UE4RuntimeMeshComponent

i just think there’s a queue for updates and new marketplace content. plus i also think the launcher needs to handle a lot of content and servers need updating to accept that.

For all of you wanting to update to 4.13 using the marketplace installed version. It is coming, but Epic is having a slight technical issue that will take a little time to work through. If you want to go ahead and move to 4.13 you can download the marketplace (or even a newer version) from github at the link below and install it either to your engine plugins folder or your project plugins folder. Instructions for this can be found on the github wiki.

https://github.com//UE4RuntimeMeshComponent

Hey, just checking in to see if v2 is any closer yet? Just curious as I’ve paused my work with RMC for the moment while I wait. I’m really interested in exploring the SMC -> RMC and Normal/Tangent calculations as these two features alone can save huge amounts of time in building meshes for RMC. I’m also hoping that v2 will solve the shadowing and nav mesh issues I raised earlier. Sorry, not trying to be pushy, just really excited for where this can go and eager to get my hands on it.

I recently migrated my project to VR on the CV1. I’m noticing a huge delay when I use BeginMeshSectionPositionUpdate vs SetMeshSectionVisible. In the first, I fetch a triangle from a raycast and then grab the local neighbors which are precalculated creating a list of 13 triangles, which I multiply their Z value to move them down and out of the way. This method causes me to cross over the 90 FPS requirement. So instead I use the same method of fetching neighbors only this time I grab the lower subdivision levels and put the grouped lower subdivision levels on their own section on the RMT. So when my raycast hits and fetches the neighbors (sections neighbors) I turn that section off. This cause no delay and is the same number of triangle loops where instead of neighbors per face I’m grabbing per subdivision neighbors and turning the groups of triangles of. Technically its’ more triangles because each section is many polygons whereas the original method was only 13 triangles.

When you update faces in place does it really matter how large the container is (within reason)? For instance I have a container that has 737,280 triangle faces in it. Now I want to move 13 polygons to a new location. My polygons have collision on them. When I move those polygons I see my frame rate dip down (below 90 FPS because I’m in VR). However if I do the same thing on the next subdivision level lower (184,320 triangles), my framerate is fine. It’s still only modifying 13 triangles, and the matter in which those triangles are discovered is identical. There’s a visible stutter in the CV1 when I use the higher subdivision container. Any ideas how I can improve this?

Now I could be off base with this as I’m no expert on the subject. However, I believe that the entire list of vertices needs to be pushed to VRAM regardless of how many you actually change. So it makes sense that a list of ~700k vertices will take more time to send across than a ~200k list. At least my own testing verified this for me, so that’s the assumption I’m rolling with for now until corrected.

So what I did to get around the problem was to break my world up into multiple RMC’s. I used a quad-tree approach to break the world up into even square chunks. Vertices are then assigned to a chunk based on their initial world location. So instead of one RMC with ~700k vertices, you end up with say 16 RMC’s with ~40k vertices each. Then when you update a triangle, you are only updating that one chunk. I can now update multiple chunks every frame with no noticeable hit on frame time.

Bare in mind that this approach results in 16 draw calls as opposed to 1 draw call. That was an acceptable trade-off for me as I update the chunks frequently. You would need to decide what is acceptable to you based on how often you update.

Oh and one more thing… don’t try any of this in BP. Stick to C++ for this kind of stuff. Not just for the performance of it, but because BP would get out of control with the math and what not going on.

First off. Sorry for the delay everyone, I am still working on v2 very slowly, just haven’t had much time due to other things going on. I hope to get everything but collision fully finalized this week, not sure about collision as it requires me having more time to work in one consecutive shot then I’ve had recently.

@wilberolive I’ll let you know when the normal/tangent work is done and pushed to github! (It’s the next thing being pushed once I find one last bug) The smc->rmc conversions are done on github.
@Thumper

Wilber is on the right track, so I’ll break down the major areas that affect what it sounds like you’re trying to do…

First up the difference between BeginMeshSectionUpdate/EndMeshSectionUpdate and UpdateMeshSection is that the first allows you to access and edit the RMC’s stored version which removes one potentially costly copy operation and also allows you to skip storing it yourself as well.
The problem is for both of these paths the next part is identical. When you commit an update either view EndMeshSectionUpdate or implicitly in UpdateMeshSection it copies the entire section and passes is to the render thread, which then locks a GPU buffer and copies that data to the GPU. This means that there’s 2 types of impact you’ll get from this. first is the obvious time to copy those 2 times, and the second is the time required to lock the GPU buffer. So many tiny buffers updating frequently can be slow dominated by lock time, and giant buffers are slow due to copy time.

The other thing that’s probably the far bigger factor is collision, and I’d be interested to see what the performance difference is for you with collision off. Unfortunately the PMC and RMC (Currently) have a rather brute force setup for collision. For PhysX (The underlying physics engine) to work with things like static meshes it must first ‘cook’ them which does a list of things to improve detection performance. The problem is this is slow (like 20+ms slow in some cases) and will very quickly destroy your fps. This is the big thing I’m working on for v2, and hope to have a testable version here pretty quick.
The quick breakdown on the collision issue with the PMC/RMC is when you update any 1 section, it will cause that entire PMC/RMC to batch all the sections back together into one giant mesh and send it to the PhysX cooker on the game thread thereby halting your execuation while it cooks.

What you can do for now is like wilber said, divide up the mesh, and if you’re using collision separate them between multiple RMC’s to reduce the collision impact. This will increase draw calls, but it should reduce update times substantially.

Thanks for the update. Super excited about the work you are doing. I’ve only been using the v1.2 plugin at the moment as I’m not really familiar with the process of compiling and installing a plugin from GitHub.

Hi :slight_smile:

I tried to compile this plugin for PS4 and Linux and it seems like your code has some issues with clang. We implemented some workarounds but you might want to look into it :wink:

@ I’m not sure if I’ve found a bug or if this is just the way it is supposed to work.

For some reason CreateMeshSection() was failing to create a mesh, even though all the inputs were valid (I stepped though my C++ and inspected them). After calling it, the RMC was still showing as having no mesh sections internally. After hours of scratching my head and fiddling I eventually figured out that it has to do with the way you create the FBox bounding box. It turns out my FBox was not valid.

Here is the code I was using.


FBox Bounds;
Bounds.Min.Set(Start.X, Start.Y, 0.0f);
Bounds..Set(End.X, End.Y, Z);

Looks perfectly fine. However, the problem is that the internal FBox IsValid flag is not being set since I’m setting the Min/ directly rather than going through the FBox constructor. So I changed the code to this.


FBox Bounds = FBox(FVector(Start.X, Start.Y, 0.0f), FVector(End.X, End.Y, Z));

Now the IsValid flag is being set and CreateMeshSection() produces a mesh section. Great!

So my question… Is this intended? Seems odd that the mesh section isn’t created just because the IsValid flag was not set. It means you have to always create the FBox using one of its constructors. You can’t manually create an FBox.

Hi,

I don’t know if this is possible but I have been trying to set the animated mesh to use a custom material that varies in colour with world position (varying from blue to red in the z-direction). The material works with other meshes but not the RMC. Is there any way to get this to work with the RMC? I’m pretty new to UE4 so don’t fully understand how the RMC works. Images attached.

Thanks in advance!

RMC(with edited height variable):


Custom material:

This is indeed intended behavior. If the box is not valid, it can’t be used for culling and such so it will bail when it sees an invalid box. In editor it should log an error message, in a packaged game it will outright crash you. Those are picked up by the validation logic which can be found at the top of RuntimeMeshComponent.h

As for creating the box, there’s a couple ways to do it. First is like you just showed…


FBox Bounds = FBox(FVector(Start.X, Start.Y, 0.0f), FVector(End.X, End.Y, Z));

The next would be like you did originally with 1 addition.


FBox Bounds;
Bounds.Min.Set(Start.X, Start.Y, 0.0f);
Bounds..Set(End.X, End.Y, Z);
Bounds.IsValid = true;

The third way is useful when you just need to sum up the points and don’t know directly which is to do something along the lines of…


FBox Bounds(0);
Bounds += Point;

where ‘Point’ is a FVector point that the box needs to adjust to fit.

I hope that helped!

If I’m not mistaken, the WorldAlignedBlend works by blending on the normal of the mesh, not the z position of the vertex like what it sounds like you’re wanting. The RMC will work fine with either case, but that plane doesn’t have its normals computed as there’s 600k vertices IIRC and the usual normal/tangent calculator is entirely too slow to calculate that in real-time. With something like the cubes in that scene it should work correctly.

Ahh ok, thanks ! I will look into it some more and see if I can come up with a new method of achieving what I am looking for.

I’ve started playing with RMC today, have been backporting an example into C++, and I’m having some trouble actually generating something visible. As far as I can tell, everything I’m passing into CreateMeshSection is valid, and it isn’t dropping out early, but I never see anything in the engine.

Here’s a snippet - am I missing something obvious / vital?


		//URuntimeMeshComponent* NewTile = CreateDefaultSubobject<URuntimeMeshComponent>(TEXT("TerrainTile"));
		URuntimeMeshComponent* NewTile = NewObject<URuntimeMeshComponent>();
		NewTile->SetupAttachment(RootComponent);

		//NewTile->bUseComplexAsSimpleCollision = true;
		//NewTile->bShouldSerializeMeshData = true;

		NewTile->SetRelativeLocation(RelativeTileLocations[TilesToSpawn - 1]);

		TArray<FVector> Vertices;

		for (int i = 0; i < TilesPerAxis; i++)
			for (int j = 0; j < TilesPerAxis; j++)
			{
				FVector NewVert = FVector(0, 0, 0);

				NewVert.X = j * (TileSize / TilesPerAxis);
				NewVert.Y = i * (TileSize / TilesPerAxis);

				Vertices.Add(NewVert);
			}

		int32 NumRowsColumns = FMath::TruncToInt(FMath::Sqrt(Vertices.Num()));

		TArray<FVector2D> UVs;

		//££ Todo: Duplicate UVs into Lightmap UVs.
		TArray<FVector2D> LightmapUVs;

		//Calculate UVs
		for(int i = 0; i < NumRowsColumns; i++)
			for (int j = 0; j < NumRowsColumns; j++)
			{
				FVector2D Coords = FVector2D(j * TileSize, i * TileSize);
				UVs.Add(Coords);
			}

		//Generate Tris
		TArray<int32> Triangles;

		UKismetProceduralMeshLibrary::CreateGridMeshTriangles(NumRowsColumns, NumRowsColumns, true, Triangles);

		//Calc Tangents
		TArray<FVector> Normals;
		TArray<FProcMeshTangent> KPTangents;

		UKismetProceduralMeshLibrary::CalculateTangentsForMesh(Vertices, Triangles, UVs, Normals, KPTangents);

		TArray<FRuntimeMeshTangent> Tangents;

		for (FProcMeshTangent Tangent : KPTangents)
		{
			FRuntimeMeshTangent NewTangent = FRuntimeMeshTangent(Tangent.TangentX, false);

			Tangents.Add(NewTangent);
		}

		//££ build black/white colour array.
		TArray<FLinearColor> VertexColours;


		//void URuntimeMeshComponent::CreateMeshSection_Blueprint(int32, const TArray<FVector, FDefaultAllocator> &, const TArray<ElementType, FDefaultAllocator> &, const TArray<FVector, FDefaultAllocator> &, const TArray<FRuntimeMeshTangent, FDefaultAllocator> &, const TArray<FVector2D, FDefaultAllocator> &, const TArray<FVector2D, FDefaultAllocator> &, const TArray<FLinearColor, FDefaultAllocator> &, bool, EUpdateFrequency)': cannot convert argument 7 from 'int' to 'const TArray<FVector2D, FDefaultAllocator> &'
		NewTile->CreateMeshSection_Blueprint(0, Vertices, Triangles, Normals, Tangents, UVs, LightmapUVs, VertexColours, true, EUpdateFrequency::Frequent);

		NewTile->SetMaterial(0, Material);

You’re welcome! Good luck! My only suggestion would be to take world position in Z and localize it to a starting/ending height you want to blend between and then lerp between the two colors. There’s probably a better way but that’s a quick way to do it.

Looking at it, the only thing that stands out as potentially a problem is registration. You’re not giving it a parent object or registering it.


		
		URuntimeMeshComponent* NewTile = NewObject<URuntimeMeshComponent>(this);
                NewTile->RegisterComponent();
		NewTile->SetupAttachment(RootComponent);



but if you’re in the constructor then the easier way is just


		
		URuntimeMeshComponent* NewTile = CreateDefaultSubobject<URuntimeMeshComponent>(TEXT("TerrainTile"));
                RootComponent = NewTile;


(All of the above is done by memory, as I’m not at my desk atm, but I think it’s right)

@ Have run into another very strange issue with RMC. I’m trying to use a translucent material with my generated mesh. However, the first time I generate the mesh (at runtime), it is invisible. I’ve stepped through the code and it appears to be generating a mesh and adding it as a mesh section, but it just doesn’t render on screen at all. However, if I clear that mesh section and then generate it again (while in runtime, not in the editor), it renders fine.

So the issue seems to be that the first time you create a mesh section at runtime with a translucent material it doesn’t render. You have to clear it once, then create it again for it to render.

As a test, I just swapped the code over to use PMC instead and it works fine. The mesh renders first time every time with a translucent material. So it appears to be an issue with RMC.

Well that’s new…

Which version are you using? Wondering since github master is a bit unstable atm, but I might be about to push a rather large update, so will look into it with this round.

I’m using UE 4.13.1 with the built in RMC 1.2.

I would like to try out the GitHub version, but I’m not sure how. I’m assuming I need to download the code, compile it and then add it to the engine as a plugin? I just have no idea how to do that.

On a side note, shadows now appear to be working with RMC. For a long time they didn’t work, then today I noticed there was something different about my scene but couldn’t put my finger on it until I realised there were shadows where there never used to be shadows. The only thing I’ve changed recently is upgrading from UE 4.12.5 to UE 4.13.1 about a week ago.

However the issue with RMC not affecting the nav mesh still exists. I was hoping maybe that had fixed itself too after upgrading, but no. Have you had any luck with that bug as my game is completely broken because of it? My AI just walks through everything I place in the world like its not there. LOL.