Procedural Mesh in UE4 #1 – Triangle
I’m not going to dive into hardcore theory behind a procedural generation. With a little bit of googling you can find plenty of articles that describe it much better than I could ever do. Instead, I will focus on practical uses of procedural generation. Stuff like simple landscape generation, use of splines in procedural generation, generating cave systems, etc. I would also like to do some more experimental stuff like generative art. I can’t promise right now, that all that will be covered since I myself don’t exactly know where this series will take me. What I can promise is that I will cover the basics of procedural generation in UE4. I will be coding all examples in C++ since blueprints, in this case, are not exactly readable. However, I will probably make one part on procedural generation in blueprints. Ok, enough talking, let’s get to it.
Creating a triangle
Ok, let’s jump into Unreal. Create empty C++ project and add C++ class. As a parent class, choose Actor.
First, you need to add ProceduralMeshComponent into modules in Build.cs file.
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ProceduralMeshComponent" });
In header file of the new class, include ProceduralMeshComponent.h. Also, add ProceduralMeshComponent to the class. Next, add all the necessary arrays for creating a procedural mesh. For now, you only need vertices and triangles. Unfortunately, the function for creating procedural meshes needs all the other arrays, so create them too. Next, add OnConstruction function to the header file. This is basically the construction script. That’s where the mesh will be created. Also, create ClearMeshData function. This will be a function for clearing the arrays.
#include "GameFramework/Actor.h" #include "ProceduralMeshComponent.h" #include "MyProceduralMesh.generated.h" UCLASS() class TUTORIALS_API AMyProceduralMesh : public AActor { GENERATED_BODY() UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh") UProceduralMeshComponent* pm; public: AMyProceduralMesh(); UPROPERTY() TArray<FVector> vertices; UPROPERTY() TArray<FVector> normals; UPROPERTY() TArray<int32> triangles; UPROPERTY() TArray<FVector2D> uvs; UPROPERTY() TArray<FLinearColor> vertexColors; UPROPERTY() TArray<FProcMeshTangent> tangents; virtual void OnConstruction(const FTransform& Transform) override; void ClearMeshData(); };
In the cpp file, in the constructor, the first thing you need to do is to create the ProceduralMeshComponent.
AMyProceduralMesh::AMyProceduralMesh() { PrimaryActorTick.bCanEverTick = true; pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh")); pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); }
Then implement the OnConstruction function and add the vertices. For now, create them on X and Y axis. Next step will be creating the triangle. You can do it by adding the indices of the vertices into triangle array. Here you need to be careful because the order of the vertices will determine the front face of the triangle. You need to insert the vertices (indices of vertices) in the counter-clockwise direction. So looking down onto the triangle it will be 0-2-1 .
Next, initialize rest of the arrays. All of them should be the same length as the vertices array. Now, that you have all the data needed to create the triangle, you can actually create it.
void AMyProceduralMesh::OnConstruction(const FTransform& Transform) { vertices.Add(FVector(0.0f, 0.0f, 0.0f)); vertices.Add(FVector(100.0f, 0.0f, 0.0f)); vertices.Add(FVector(0.0f, 100.0f, 0.0f)); triangles.Add(0); triangles.Add(2); triangles.Add(1); uvs.Init(FVector2D(0.0f, 0.0f), 3); normals.Init(FVector(0.0f, 0.0f, 1.0f), 3); vertexColors.Init(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f), 3); tangents.Init(FProcMeshTangent(1.0f, 0.0f, 0.0f), 3); //Function that creates mesh section pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false); }
The last thing before you compile it, you have to implement ClearMeshData function.
void AMyProceduralMesh::ClearMeshData() { vertices.Empty(); triangles.Empty(); uvs.Empty(); normals.Empty(); vertexColors.Empty(); tangents.Empty(); }
This will basically delete all the arrays. The reason why is this necessary is that the construction script (OnConstruction function) will run every time you’ll change something on that object. That would result in new 3 vertices and new triangle added after every modification. And that’s something you do not want (also it would probably crash because the length of the other arrays would not match the vertices array). It’s super messy solution, but it’ll do for now. You can compile now.
Create a new blueprint and as a parent class choose the procedural mesh class you’ve just created. You can then add that blueprint into the level and you should see a triangle.
Texturing triangle
In order to properly texture the triangle, you need to provide for each vertex a 2D UV coordinate in the 0-1 range. In this case, it will be pretty easy, since the opposite and adjacent side are the same length(triangle is half of a square).
uvs.Add(FVector2D(0.0f, 0.0f)); uvs.Add(FVector2D(1.0f, 0.0f)); uvs.Add(FVector2D(0.0f, 1.0f)); //uvs.Init(FVector2D(0.0f, 0.0f), 3);
Conclusion
And that’s it for now. Next part will be all about a subdivided plane(creating, updating). There will also be a bonus experiment.
Source code
#include "GameFramework/Actor.h" #include "ProceduralMeshComponent.h" #include "MyProceduralMesh.generated.h" UCLASS() class TUTORIALS_API AMyProceduralMesh : public AActor { GENERATED_BODY() UPROPERTY(VisibleAnywhere, Category = "MyProceduralMesh") UProceduralMeshComponent* pm; public: AMyProceduralMesh(); UPROPERTY() TArray<FVector> vertices; UPROPERTY() TArray<FVector> normals; UPROPERTY() TArray<int32> triangles; UPROPERTY() TArray<FVector2D> uvs; UPROPERTY() TArray<FLinearColor> vertexColors; UPROPERTY() TArray<FProcMeshTangent> tangents; virtual void OnConstruction(const FTransform& Transform) override; void ClearMeshData(); };
#include "Tutorials.h" #include "MyProceduralMesh.h" AMyProceduralMesh::AMyProceduralMesh() { PrimaryActorTick.bCanEverTick = true; pm = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("ProceduralMesh")); pm->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); } void AMyProceduralMesh::OnConstruction(const FTransform& Transform) { ClearMeshData(); vertices.Add(FVector(0.0f, 0.0f, 0.0f)); vertices.Add(FVector(100.0f, 0.0f, 0.0f)); vertices.Add(FVector(0.0f, 100.0f, 0.0f)); triangles.Add(0); triangles.Add(2); triangles.Add(1); uvs.Add(FVector2D(0.0f, 0.0f)); uvs.Add(FVector2D(1.0f, 0.0f)); uvs.Add(FVector2D(0.0f, 1.0f)); //uvs.Init(FVector2D(0.0f, 0.0f), 3); normals.Init(FVector(0.0f, 0.0f, 1.0f), 3); vertexColors.Init(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f), 3); tangents.Init(FProcMeshTangent(1.0f, 0.0f, 0.0f), 3); //Function that creates mesh section pm->CreateMeshSection_LinearColor(0, vertices, triangles, normals, uvs, vertexColors, tangents, false); } void AMyProceduralMesh::ClearMeshData() { vertices.Empty(); triangles.Empty(); uvs.Empty(); normals.Empty(); vertexColors.Empty(); tangents.Empty(); }<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
Leave a Reply