Voxel Terrain In Unreal Engine 4 Part 3: Rendering The Terrain

Welcome back to the UE4 Voxel Terrain tutorial, in the last part we tested our code via the PolyVox examples, and in this part we’ll be rendering our terrain in the engine. Originally this was going to be the longest part in the series, because in older versions of the engine you needed to implement the terrain rendering component yourself. Thankfully a few versions ago Epic added the Procedural Mesh Component plugin, so we don’t need to do that anymore. This is probably going to be one of the shortest parts of the series now, so let’s get started.

Enabling The ProceduralMeshComponent

The first step is to make sure you have the ProceduralMeshComponent plugin enabled in the engine. In 4.10.4 the plugin appears to be enabled automatically, however, you should still check that it is enabled before proceeding. If you haven’t used plugins before, open your editor and open the plugins window, which is found in the edit drop-down menu. From there you can find the Procedural Mesh Component in the Rendering section. To enable it just check the Enabled check-box.

Now that you’re sure you have the plugin enabled we need to tell the engine that we’re using it in our C++ code. Open up the Project.Build.cs file for your project, and add ProceduralMeshComponent to the list of strings being added to the PublicDependencyModuleNames variable. If you’ve followed the previous parts of this tutorial exactly then that should be on line 24, and it should look like this once you’ve finished.

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ProceduralMeshComponent" });

After you make the changes to the file go ahead and have the engine regenerate your project files so your IDE recognizes the include files. If anyone working on the engine reads this, having the UBT automatically regenerate the files if it detects a change like this would be super helpful. I’ve forgotten to do this several times and sat there confused because Visual Studio wouldn’t recognize any of my includes every time.

Using The ProceduralMeshComponent

Let’s talk about how you can use the ProceduralMeshComponent before we start to use it in our project; there is a lot you can do with it, and we’ll be making use of all of its features during this series. Looking at the header file, a prominent feature is that the entire class is available to Blueprints, so if you had the patience to implement a noise function entirely in Blueprints you could do this entire tutorial without touching any C++. The next thing that stands out is how simple the class is; there are only 6 public functions defined that you should be using. Of those 6 functions we will be using the following 3 functions.

    /**
	 *	Create/replace a section for this procedural mesh component.
	 *	@param	SectionIndex		Index of the section to create or replace.
	 *	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
	 *	@param	Triangles			Index buffer indicating which vertices make up each triangle. Length must be a multiple of 3.
	 *	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	UV0					Optional array of texture co-ordinates for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	VertexColors		Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	bCreateCollision	Indicates whether collision should be created for this section. This adds significant cost.
	 */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh", meta = (AutoCreateRefTerm = "Normals,UV0,VertexColors,Tangents"))
	void CreateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<int32>& Triangles, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FColor>& VertexColors, const TArray<FProcMeshTangent>& Tangents, bool bCreateCollision);

	/**
	 *	Updates a section of this procedural mesh component. This is faster than CreateMeshSection, but does not let you change topology. Collision info is also updated.
	 *	@param	Vertices			Vertex buffer of all vertex positions to use for this mesh section.
	 *	@param	Normals				Optional array of normal vectors for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	UV0					Optional array of texture co-ordinates for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	VertexColors		Optional array of colors for each vertex. If supplied, must be same length as Vertices array.
	 *	@param	Tangents			Optional array of tangent vector for each vertex. If supplied, must be same length as Vertices array.
	 */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh", meta = (AutoCreateRefTerm = "Normals,UV0,VertexColors,Tangents"))
	void UpdateMeshSection(int32 SectionIndex, const TArray<FVector>& Vertices, const TArray<FVector>& Normals, const TArray<FVector2D>& UV0, const TArray<FColor>& VertexColors, const TArray<FProcMeshTangent>& Tangents);

	/** Clear a section of the procedural mesh. Other sections do not change index. */
	UFUNCTION(BlueprintCallable, Category = "Components|ProceduralMesh")
	void ClearMeshSection(int32 SectionIndex);

The first function CreateMeshSection will be used to create the initial mesh sections. Mesh sections are primarily used to give different parts of the mesh different materials, as each mesh section can only have one material. The second function UpdateMeshSection does exactly what it sounds like it does, it updates the mesh section. This function is useful because it doesn’t need to recreate the entire data structure, it only needs to update what already exists, which makes it much faster than CreateMeshSection. In order to use UpdateMeshSection your new data must have the same triangles as the previous data; you cannot add or remove any triangles or change which vertices belong to them, however, the positions and all other data can be completely different. The final function ClearMeshSection is pretty self-explanatory, it removes all of the data related to a mesh section.

Using these functions is fairly simple if you know anything about how meshes are created. The example code below creates a simple plane complete with correct normals, vertex colors, UV mapping, and collisions.

	// Variables to pass into Mesh->CreateMeshSection
	auto Vertices = TArray<FVector>();
	auto Triangles = TArray<int32>();
	auto Normals = TArray<FVector>();
	auto UVs = TArray<FVector2D>();
	auto Colors = TArray<FColor>();
	auto Tangents = TArray<FProcMeshTangent>();

	// Add uninitialized entries to the arrays
	Vertices.AddUninitialized(4);
	Triangles.AddUninitialized(6);
	Normals.AddUninitialized(4);
	UVs.AddUninitialized(4);
	Colors.AddUninitialized(4);
	Tangents.AddUninitialized(4);

	// Define our 4 vertices
	Vertices[0] = FVector(-100, 100, 0);
	Vertices[1] = FVector(100, 100, 0);
	Vertices[2] = FVector(100, -100, 0);
	Vertices[3] = FVector(-100, -100, 0);

	// Map those vertices to their respective triangles
	Triangles[0] = 0;
	Triangles[4] = 2;
	Triangles[1] = Triangles[3] = 1;
	Triangles[2] = Triangles[5] = 3;

	// Set the normals for all of our vertices to face upwards
	Normals[0] = Normals[1] = Normals[2] = Normals[3] = FVector(0, 0, 1);

	// Set the color of all of our vertices to blue
	Colors[0] = Colors[1] = Colors[2] = Colors[3] = FColor(0, 0, 255);

	// Set the tangents of all of the vertices to face to the left
	Tangents[0] = Tangents[1] = Tangents[2] = Tangents[3] = FProcMeshTangent(0.f, -1.f, 0.f);

	// Setup UV mapping for textures
	UVs[0] = FVector2D(0.f, 0.f);
	UVs[1] = FVector2D(0.f, 1.f);
	UVs[2] = FVector2D(1.f, 1.f);
	UVs[3] = FVector2D(1.f, 0.f);

	// Create the mesh section using the variables we just defined
	Mesh->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, Colors, Tangents, true);

If you wanted to you could then modify that plane using UpdateMeshSection with basically the same arguments. Let’s make the plane smaller, red, and face downwards.

	// Variables to pass into Mesh->UpdateMeshSection
	auto Vertices = TArray<FVector>();
	auto Normals = TArray<FVector>();
	auto UVs = TArray<FVector2D>();
	auto Colors = TArray<FColor>();
	auto Tangents = TArray<FProcMeshTangent>();

	// Add uninitialized entries to the arrays
	Vertices.AddUninitialized(4);
	Normals.AddUninitialized(4);
	UVs.AddUninitialized(4);
	Colors.AddUninitialized(4);
	Tangents.AddUninitialized(4);

	// Define our 4 vertices
	Vertices[0] = FVector(-50, 50, 0);
	Vertices[1] = FVector(50, 50, 0);
	Vertices[2] = FVector(50, -50, 0);
	Vertices[3] = FVector(-50, -50, 0);

	// Set the normals for all of our vertices to face downwards
	Normals[0] = Normals[1] = Normals[2] = Normals[3] = FVector(0, 0, -1);

	// Set the color of all of our vertices to red
	Colors[0] = Colors[1] = Colors[2] = Colors[3] = FColor(255, 0, 0);

	// Set the tangents of all of the vertices to face to the right
	Tangents[0] = Tangents[1] = Tangents[2] = Tangents[3] = FProcMeshTangent(0.f, 1.f, 0.f);

	// Setup UV mapping for textures
	UVs[0] = FVector2D(0.f, 0.f);
	UVs[1] = FVector2D(0.f, 1.f);
	UVs[2] = FVector2D(1.f, 1.f);
	UVs[3] = FVector2D(1.f, 0.f);

	// Create the mesh section using the variables we just defined
	Mesh->UpdateMeshSection(0, Vertices, Normals, UVs, Colors, Tangents);

You probably won’t notice the vertex color change unless you have a material that shows the vertex colors on the mesh, however, the size change and normal flip should be very noticeable. The ClearMeshSection function only needs one line, you use it like this:

Mesh->ClearMeshSection(0);

Simple, right? That’s the basics of how to use the ProceduralMeshComponent, let’s start implementing it with our voxels.

Rendering The Voxels

Alright so to do this we’re going to need to make a couple of small modifications to our header file, but first we need to fix some things I did wrong in part 2. What I did wrong will prevent us from modifying our terrain generation variables, but it’s a very simple thing to fix so let’s get it done. In the terrain actor header file simply replace all instances of VisibleAnywhere with EditAnywhere, and replace PostInitializeProperties with PostInitializeComponents and you should be good to go. If you’ve started doing this tutorial series after this part was published then you don’t need to worry about this, as I’ve already fixed it in the previous parts. Speaking of the header file the next few things we need to do are also in that file so keep it open.

Changes To The Voxel Terrain Actor

First thing’s first, we need to add a couple of new includes. Specifically, we need to include PolyVox/Vector.h and ProceduralMeshComponent.h. The former isn’t strictly required as it is already included in other PolyVox files, however, it does help protect against changes to the PolyVox code. The ProceduralMeshComponent.h is obviously required as we need that to render our voxels.

Next move down to your actor class definition and add the following two members:

	// Called when the actor has begun playing in the level
	virtual void BeginPlay() override;

	// The procedurally generated mesh that represents our voxels
	UPROPERTY(Category = "Voxel Terrain", BlueprintReadWrite, VisibleAnywhere) class UProceduralMeshComponent* Mesh;

This time around we do actually want to use VisibleAnywhere instead of EditAnywhere, so don’t worry I didn’t derp it up again. The BeginPlay function is where we’ll be doing most of the work during this section of the tutorial. We’ll come back to these two variables later once we’ve started working on the implementation file. Before we can move over to that file we need to do one last thing in our header file.

Bridging The PolyVox and Unreal Engine 4 Vector Classes

This is more for simplifying code than anything; you could skip this whole section and manually assign the X, Y, and Z coordinates if you wanted to. Doing this will allow us to write this:

FVector UnrealVector = FPolyVoxVector(PolyVox::Vector3DFloat(0, 0, 10));

Instead of this:

PolyVox::Vector3DFloat PolyVoxVector(0, 0, 10);
FVector UnrealVector(PolyVoxVector.getX(), PolyVoxVector.getY(), PolyVoxVector.getZ());

Which really doesn’t look like that big of an improvement in this example, but in practice it results in much cleaner looking code. To implement this we need to add the following class to our header file:

// Bridge between PolyVox Vector3DFloat and Unreal Engine 4 FVector
struct FPolyVoxVector : public FVector
{
	FORCEINLINE FPolyVoxVector()
	{}

	explicit FORCEINLINE FPolyVoxVector(EForceInit E)
		: FVector(E)
	{}

	FORCEINLINE FPolyVoxVector(float InX, float InY, float InZ)
		: FVector(InX, InY, InX)
	{}

	FORCEINLINE FPolyVoxVector(const FVector &InVec)
	{
		FVector::operator=(InVec);
	}

	FORCEINLINE FPolyVoxVector(const PolyVox::Vector3DFloat &InVec)
	{
		FPolyVoxVector::operator=(InVec);
	}

	FORCEINLINE FVector& operator=(const PolyVox::Vector3DFloat& Other)
	{
		this->X = Other.getX();
		this->Y = Other.getY();
		this->Z = Other.getZ();

		DiagnosticCheckNaN();

		return *this;
	}
};

All this class does is define a new operator and a new constructor that take the PolyVox vector type as an argument. At some point you may want to move this to it’s own file instead of having it in the terrain actor’s header file, but for now that is the only thing that will be using it so we don’t need it anywhere else. Now that we have that done let’s move on to the implementation file.

The Implementation

Once again starting this section off with fixing something from part 2 (last fix I promise), which you won’t need to do if you just started these tutorials, we need to simply rename all instances of PostInitProperties to PostInitializeComponents. After you get that out of the way (if you even had to do it), we can move on with the tutorial.

Initializing The Mesh Component

Before we can start implementing the function we declared earlier we need to initialize our ProceduralMeshComponent. Find your way to your constructor and add the following code:

	// Initialize our mesh component
	Mesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("Terrain Mesh"));
The BeginPlay function

This is actually the only function we need to implement. As usual here is the source for the function and I’ll be going over what is happening here below:

// Called when the actor has begun playing in the level
void AVoxelTerrainActor::BeginPlay()
{
	// Extract the voxel mesh from PolyVox
	PolyVox::Region ToExtract(Vector3DInt32(0, 0, 0), Vector3DInt32(127, 127, 63));
	auto ExtractedMesh = extractCubicMesh(VoxelVolume.Get(), ToExtract);
	auto DecodedMesh = decodeMesh(ExtractedMesh);

	// Define variables to pass into the CreateMeshSection function
	auto Vertices = TArray<FVector>();
	auto Indices = TArray<int32>();
	auto Normals = TArray<FVector>();
	auto UV0 = TArray<FVector2D>();
	auto Colors = TArray<FColor>();
	auto Tangents = TArray<FProcMeshTangent>();

	// Loop over all of the triangle vertex indices
	for (uint32 i = 0; i < DecodedMesh.getNoOfIndices() - 2; i+=3)
	{
		// We need to add the vertices of each triangle in reverse or the mesh will be upside down
		auto Index = DecodedMesh.getIndex(i + 2);
		auto Vertex2 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex2.position) * 100.f));
		
		Index = DecodedMesh.getIndex(i + 1);
		auto Vertex1 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex1.position) * 100.f));
		
		Index = DecodedMesh.getIndex(i);
		auto Vertex0 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex0.position) * 100.f));
		
		// Calculate the tangents of our triangle
		const FVector Edge01 = FPolyVoxVector(Vertex1.position - Vertex0.position);
		const FVector Edge02 = FPolyVoxVector(Vertex2.position - Vertex0.position);

		const FVector TangentX = Edge01.GetSafeNormal();
		FVector TangentZ = (Edge01 ^ Edge02).GetSafeNormal();

		for (int32 i = 0; i < 3; i++)
		{
			Tangents.Add(FProcMeshTangent(TangentX, false));
			Normals.Add(TangentZ);
		}
	}

	// Finally create the mesh
	Mesh->CreateMeshSection(0, Vertices, Indices, Normals, UV0, Colors, Tangents, true);
}

Onward to the breakdown!

	// Extract the voxel mesh from PolyVox
	PolyVox::Region ToExtract(Vector3DInt32(0, 0, 0), Vector3DInt32(127, 127, 63));
	auto ExtractedMesh = extractCubicMesh(VoxelVolume.Get(), ToExtract);
	auto DecodedMesh = decodeMesh(ExtractedMesh);

There are three things happening here:

  1. We define a region of the voxel volume to extract, ranging from (0, 0, 0) to (127, 127, 63).
  2. We extract the mesh using the cubic mesh extractor, which basically makes the terrain look like a bunch of cubes.
  3. And finally we decode the extracted mesh. By default PolyVox encodes the extracted mesh in a way that the engine can’t use, so we need to decode it.

Once we finish those three things we have our mesh ready to be brought into the engine, but first we need to define some variables:

	// Define variables to pass into the CreateMeshSection function
	auto Vertices = TArray<FVector>();
	auto Indices = TArray<int32>();
	auto Normals = TArray<FVector>();
	auto UV0 = TArray<FVector2D>();
	auto Colors = TArray<FColor>();
	auto Tangents = TArray<FProcMeshTangent>();

Those arrays get fed into the CreateMeshSection call once they are populated with data. The for-loop that comes next is what populates the arrays with data.

	// Loop over all of the triangle vertex indices
	for (uint32 i = 0; i < DecodedMesh.getNoOfIndices() - 2; i+=3)
	{
		...
	}

Here we loop over every triangle in the decoded mesh using a standard for-loop with a uint32 index. Because each triangle is made of 3 vertices, we need to increment by 3 every loop and stop three indices prior to the last index to prevent access violations. Inside of this loop is where we populate the arrays from before.

		// We need to add the vertices of each triangle in reverse or the mesh will be upside down
		auto Index = DecodedMesh.getIndex(i + 2);
		auto Vertex2 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex2.position) * 100.f));
		
		Index = DecodedMesh.getIndex(i + 1);
		auto Vertex1 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex1.position) * 100.f));
		
		Index = DecodedMesh.getIndex(i);
		auto Vertex0 = DecodedMesh.getVertex(Index);
		Indices.Add(Vertices.Add(FPolyVoxVector(Vertex0.position) * 100.f));

We start by populating the Indices and Vertices arrays with the data we just got from PolyVox. We have to add the vertices in the following order or the triangle will be facing the wrong direction: 2, 1, 0. Other than the order requirements this section is just the same thing repeated three times, once for each vertex. Here is a more detailed breakdown of this section:

  1. auto Index = DecodedMesh.getIndex(i + 2);

    First we get the index of the vertex we want to work with using the Mesh::getIndex function.

  2. auto Vertex2 = DecodedMesh.getVertex(Index);

    Then we get the actual vertex data by using the index we got previously in the Mesh::getVertex function.

  3. Indices.Add(Vertices.Add(FPolyVoxVector(Vertex2.position) * 100.f));

    And finally we add the position of the vertex multiplied by 100 (convert from meters to centimeters) to the Vertices array using the TArray::Add function, which returns the index we need to add to the Indices array.

Once we’ve added our vertices to the array, we need to calculate the tangents of our triangle so we get correct lighting. Because PolyVox doesn’t calculate normals for us we need to do this on our own.

		// Calculate the tangents of our triangle
		const FVector Edge01 = FPolyVoxVector(Vertex1.position - Vertex0.position);
		const FVector Edge02 = FPolyVoxVector(Vertex2.position - Vertex0.position);

		const FVector TangentX = Edge01.GetSafeNormal();
		FVector TangentZ = (Edge01 ^ Edge02).GetSafeNormal();

		for (int32 i = 0; i < 3; i++)
		{
			Tangents.Add(FProcMeshTangent(TangentX, false));
			Normals.Add(TangentZ);
		}

This consists of some relatively simple vector math that attempts to figure out what direction the triangle is facing using the positions of it’s vertices. It does this by getting the direction between Vertex0 and Vertex1 (Edge01), as well as the direction between Vertex0 and Vertex2 (Edge02). It uses the normalized direction vector Edge01 as the X tangent, and uses the normalized cross product of Edge01 and Edge02 as the Z tangent (also known as the normal). The Y tangent is then automatically calculated by the engine as the cross product between the X and Z tangents. Once it calculates the tangents it adds them to their respective arrays.

	// Finally create the mesh
	Mesh->CreateMeshSection(0, Vertices, Indices, Normals, UV0, Colors, Tangents, true);

And last but not least we exit the for-loop and create the mesh using the CreateMeshSection function we learned about earlier. Once you’re finished implementing this function you should be able to compile the project and play around with it. In the next part of this series we’ll cover texturing the mesh we just created, so make sure you watch out for that!

7 comments on “Voxel Terrain In Unreal Engine 4 Part 3: Rendering The TerrainAdd yours →

  1. I’ve been following this tutorial to learn more about handling procedural mesh generation with Unreal. However, in the version of PolyVox I have, from the current develop branch, extractCubicMesh and decodeMesh have been deprecated and are no longer in the library. I’m having some trouble finding a suitable solution and was wondering if you had any advice.

    1. Ok, so apparently that isn’t my issue. I’m lost here. I rolled polyvox back to the same build you stated in the first article, and I’m still getting that extractCubicMesh() and decodeMesh() are undefined.

      1. Very odd! I can think of two things that would cause that off the top of my head. First of all, make sure you’re on the develop branch. If you’re not on that branch then those functions would be missing, but if you are on that branch then the most likely issue is a bad include or using statement. Check that you have this code somewhere near the top of the file you’re working with:

        // PolyVox
        #include "PolyVox/CubicSurfaceExtractor.h"
        #include "PolyVox/Mesh.h"
        using namespace PolyVox;

        If you have all of that, but it still doesn’t work, then let me know and I’ll see if I can figure out anything else that might have gone wrong.

        1. I had the same problem. I just cross referenced my code with your github repo to find that these two includes were missing. Maybe you could edit the original post to add these two includes? This has been an excellent and informative series so far. 😀

  2. I just finished part two so far, but I was wondering if the terrain would end up being visible in the editor? If not, is it possible to have it generated when the class is dragged onto the map? This would be useful for placing assets etc.

  3. Hi. Could you elaborate on the shadows projected by the voxels (as portrayed by your subsequent screenshots on texturing). Nothing is said about this. So can you provide a few insights, perhaps a default, generic, no non-sense method to create a decent shadowing on this terrain ?

Leave a Reply