Building Procedural Art Tools in Unreal Engine 4

Arran Langmead shared his article on procedural mesh generation in Unreal explaining how to create basic geometry and generate a foliage tool with blueprints.

Introduction

One of the best parts of Unreal is its accessibility for those who are more artistically inclined. With the initial release of Unreal Engine 4, people found (, myself included,) that they could make entire games without having to touch a line of C++. Since then, people have been pushing the limits of what blueprint can do and Epic has responded by expanding the toolset with each new update.

An incredibly powerful part of this is the procedural mesh component, which takes in arrays of data and outputs a static mesh.

Combine this with the render to texture function and you can procedurally create Textured assets entirely in the engine!

This is useful because:

  • You can see exactly what the asset will look like as you’re building it.
  • You can see the asset in its environment as you're building it.
  • Updates are all in realtime.

There are tonnes of potential applications for this, and one I have been investigating is stylized foliage generation. Traditionally a pain in the backside to do with traditional modeling tools, the procedural mesh component is a great fit, as a game tree consists mostly of simple shapes (cylinders and planes) and is more about the correct placement and slight variation of those shapes rather than their individual complexity.

A selection of trees built using the procedural mesh component:

If you’re interested in trying out the tool you can download it ​here. Check out the ​Trello Board​ to see known bugs / what’s still in development. If you make anything or want to share feedback, contact me on twitter @ArranLangmead.

One thing I also recommend when using this tool is to increase the Max Loop count in your project settings. If we exceed the original Max Loop count our geometry won’t get built correctly!

Creating Basic Geometry

Creating a triangle

Let’s start off really simple and render a triangle. Create a new actor blueprint and add a procedural mesh component. Create a new function called draw triangle and plug it into the construction script.

Add two arrays:

  • An array of Vectors, these are our vertices
  • An array of integers, the target index of the vertex array

Here we have three vertices that represent the points of our triangle and the integer-order required to create that triangle. Note the order we need to list the vertices, if stored 0, 1, 2 the triangle will face the wrong way.

Add a ‘Create Mesh Section’ function from the procedural mesh component reference and plug the arrays into their respective inputs.

Hey presto! We just made a triangle!

Creating a plane

Now let’s get a bit more complex and make a plane. First off, we need to add some more arrays. We need to add:

  • Normals, an array of vectors (0,0,0)
  • Tangents, an array of struct vector + boolean (0,0,0 + 0/1)
  • UVs, an array of 2d vectors (0,0)

But don’t worry, we are going to cheat to get our normals and tangents!

Remember, just like before, we need to clear our old array data.

Then make a new function called Draw Quad and add two nested loops (cringe!). These will be our x and y grid count.

Then take the current x and y values, multiply them by a value (distance) and add that to the vector array. For UV’s, divide the current x and y value by its total (Length) to get a normalized value.

Then we can use the ‘Create Grid Mesh Triangles’ function to automatically generate our triangle count. (We, unfortunately, won’t be able to use this later on, but for now, it's an easy way to generate a grid of triangle data.

We also need our normals and tangents, so we can cheat again and use the ‘Calculate Tangents for Mesh’function to get those.

Here is the final function, use the link to see a zoomed-in view.

And here is the construction script.

Result:

1 of 2

Creating a cylinder

Once we have a grid, it’s a simple matter to change the shape from a plane to a cylinder. Instead of aligning our verts to a grid, we wrap them around an axis.

Take that X and XLength value we created earlier, divide them to get a normalized value and then multiply by 360 giving us our angle. We want to rotate the x axis around the z axis so get those values and plug them in and * by float to control distance.

Swap this new calculation with the original x*float like so (the link for a closer look).

Now our plane is a cylinder!

And if we take the normalized y or z value and reverse it with a 1-x that creates a cone!

See where we’re going with this yet?

Reading a mesh

The last element we need is a mesh read. The ‘Get Section from Static Mesh’ function can grab the data from any mesh and output it for us to use. Now we can sample any mesh we like, modify it and then scatter it on or along a surface.

We now have everything we need to start building our own procedural content. In the next part, we will go over some of the unique quirks of applying these tools to making a simple tree generator.

Foliage Generation

Before we dig into the creation of the foliage tool, I highly recommend downloading it and having a play around, some of the topics we will discuss will be quite abstract without it! You may also notice some references to pivot painter that aren’t covered. This is an extra feature I’m currently working on and is not yet done!

The foliage tool is broken into five separate blueprints:

  • Tree Parent
    • Holds data that will be shared across all children.
    • Trunk, Branch and Leaf blueprints inherit from it.
  • Trunk
    • Inherits from TreeParent
    • A spline controlled mesh
    • Often used for the trunk
    • Only makes one mesh
    • Gives great amount of user control
  • Branch
    • Inherits from TreeParent
    • Spawns a number of branches, either along a surface or randomly
    • User control limited to random variable range
    • Can be layered on top of one another
  • Leaf
    • Inherits from TreeParent
    • Reads an array of meshes and scatters them, either along a trunk/branch or randomly
    • Cannot have children
  • Compositer
    • Takes an array of TreeParent types and rebuilds the mesh as a single asset
    • Detects duplicate materials and merges to reduce the material count

You may be thinking, why do it this way!? Well, breaking the tree up has multiple benefits:

  • Don’t have to redraw the entire mesh when you make a change up the hierarchy
  • Quickly separate and hide elements of the asset
  • Delete sections without ruining the whole tree
  • Assets become componentized, so you can copy and paste bits of a tree onto other trees
  • Splits the random seed generation so you get more control

All these elements come together to let the user build a modular stack of components that “should” allow them to make any kind of tree they want!

Base Parent

To save having to repeat work we should start by creating a base parent that our other blueprints will inherit from. This is useful because any data we put in the parent will be shared across its children. For example, every asset type will need a material reference and a set of arrays for the mesh, so that information should be added to the parent so it propagates to its children.

Add a procedural mesh component to the ‘TreeParent’ blueprint.

We also need a number of variables that will be needed across every child.

Base

  • Material, material to apply to the mesh, this does mean we only support one material per component.
  • ProcMesh, a struct containing the arrays required to make a mesh, Vertex, Triangle, UV and Vertex Colour. Normals are generated after with function.
  • Random Seed, randomize parts of the mesh.

References

  • Parent, TreeParent variable used to reference its owner.
  • Root, references the mesh composit tool, useful for getting stored order.
  • Child Refs, array of treeparent types, used to tell objects parented to it to update.
  • T’s, an array of transform arrays, used to store the branch information.

    Think of it as the skeleton of the tree. Parented components reference this to get spawn location.

A shared function that tells the assets in the children array to update. This will get called every time a component is changed (though remember, these functions or casts won’t exist until we make them).

If we don’t update the child components, then the tree will quickly become disconnected.

Trunk

The trunk blueprint should inherit from TreeParent and is based on an editable spline which allows for a large amount of control. This can be added to any blueprint by selecting spline from the add component drop down, just like we did with the procedural mesh component.

You can see the components we have already inherited from as well.

In the construction script, we add a new function called Draw. This is the function we call across itself and all its children.

The draw function checks to see if a parent trunk already exists, if it does, move the component along the parent spline. If a parent doesn’t exist, move straight along to create the mesh. Finally, tell the objects children to update with ‘Update Children’.

In ‘Create Base Segments’, we clear the old geometry data as we did in part 1, calculate new data, and then redraw the mesh. This will be the main pattern we do across all the components.

For our geometry, we need two integers, one for the number of length segments and one for the number of radial segments.

Get the length of the spline and number of length segments we want, divide them to get the distance between each point and set that to a float called Segment distance.

Then loop for each length segment and get the transform at distance along the spline. The length will be the current loop index * the segment distance. You can use scale x or y to control the thickness of the trunk. Multiply it by an exposed value to add a uniform size to the mesh.

You may need to Swivel the rotation here to line it up with the branch's rotation.

Store the transform in an array, this is really important as we need a generic way of knowing where the branches are in space, and not every blueprint will have a spline component. This will get added to Ts array (created in TreeParent) later on.

Then we draw the vertices around that transform in the Draw Radius Function. This is similar to how we drew geometry before. Click on the link to see the full graph.

The only major difference here is the way we make the triangle array. This is kind of unnecessary for the trunk but is absolutely vital for making multiple branches work. The grid mesh triangles function is only expecting there to be a single mesh, but if we want to make multiple separate meshes, we need to offset our triangle counter.

Once all that is done, we need to take that base transform array we have been building and set it to the Ts array. The index here represents multiple branches but as we are only creating one segment this just gets set to index 0.

Once we have all the data, all that is left is to draw the mesh!

Branch

Hopefully, you are recognizing a pattern by now, clear the old data and make new data. The branch blueprint needs to take in a parent (if one exists) and scatters multiple branches along it. If the parent doesn’t exist we just scatter them on the ground.

Nothing new in clear data, so let’s move on to ‘Make Branch Data’ The bulk of this function is finding the spawn point for each branch we are creating (the link).

Start off by figuring out if the blueprint has a parent or not. If it does, we get the parent’s T’s array, this is an array of transform arrays. Each branch is a new array of transforms. This means that if the parent branch has 8 branches and we choose to make 4 branches, it makes a total of 32 branches. If the parent is a trunk, the T’s array will only have one entry of transforms.

If it doesn’t have a parent, carry on and loop for branch count and figure out normalized distance.

The next step is figuring out what each branch's origin should be. If there is no parent, just get the object's origin. If there is a parent, get the current transform array and find the location based on normalized distance. You can also add a clamp value here to shrink the range down.

Here is a diagram to explain it a bit better if that sounded confusing. In the diagram below, the top value is normalized distance which needs to be translated to the array of transforms. Multiplying by transform index count gives us the nearest index and the remainder is the distance between the next index. This calculation will give us a rough distance along the transform length.

Generate a random location between a min and max vector.

Combine the location, modify rotation with a custom macro and modify the scale.

Sorting out origin rotation is a really important element, the macro, takes in data, like rotation along parent, rotation per index, etc. To get easily controllable results, use rotate about index and use the direction vectors to avoid gimbal locking (the link).

Once we have the origin, add it to a pivot array and create the branch.

In the trunk example, we used the spline to get a transform and then built geometry around it.

We can’t do that here as the spline doesn’t exist. Instead, we have to generate those points (the link).

Here we take the origin point.

Grab the current vert length. We need this to offset our triangle count. The Triangle array is just referencing an index on the vertex array so the offset is needed to keep the triangle reference correct. Generate a random branch length between a min/max.

Then for each point along the branch.

Figure out the location of the next point. Take in the last transform, offset it by multiplying by a direction vector. Adjust it slightly with some controlled rotation.

Create mesh data, again, the same as we did before.

The important feature of the branch blueprint is that it is stackable. We should be able to build branches on branches on branches, etc.

The only problem we may run into is that our branch count is exponential. If there are 6 branches in the first layer and 4 in the 2nd and 3rd, we would get 96 branches, not 14. If we add a 4th layer of 4 branches, we get 384. The entire operation is using loops which means that we will get a lot of hitching if the numbers get too big.

Leaf

Leaf cards can’t have children so they will always be at the end of their respective hierarchy. The blueprint samples an array of meshes and scatters them along either the trunk or branch asset. A useful cheat we can do here is rendering instances instead of drawing the geometry, which massively improves performance. Here we add an exposed boolean toggle to make use of this optimization.

For this component, make an array of meshes that can be set by the user. For each mesh, use the ‘get section from static mesh’ function to get each object's data and store it for use later on (the link).

Same as with the branch blueprint, clear the old data, check for a parent, then loop for each count * parent count.

Find the spawn point, same as before, and then make the mesh or add an instance.

Adding the mesh data is really simple (the link).

We get the triangle offset and pick a mesh to replicate.

Offset the triangles, by the length. Translate the vertex data by the spawn point store it all in the array.

Merging

Once the individual components have been assembled they need to be collated together into a single unified mesh that can be exported out. Instead of running this in the construction script, functions can be exposed to the editor as buttons. This allows for a greater level of control when running the script.

In this function, we clear the old data, build a unique array of materials (this avoids creating any assets with duplicate materials), rebuild the mesh data if necessary and output the mesh. As an optimization, we should rebuild the mesh to avoid lots of repeating material assignments.

Collate the components into a single set of arrays (the link).

Check to see if the material array we have built matches the component array in length. If there are fewer materials than components, we know are some shared materials.

At that point we have to go through every component's triangle data and offset it so it lines up with the vertex array when combined together.

Finally, take that new data structure and create a static mesh.

Click on the procedural Mesh component on the compositor, and press the ‘Create Static Mesh’ button. This will prompt you to pick a location, pick a name and there you go!

You have made a tree!

Conclusion

This is just one example of the kind of assets you can create with the procedural mesh component. There is a massive range of possibilities and it’s not limited to just editor creation either, you can use this function in game as well!

We also can’t finish an article on procedural tools without mentioning Houdini, an awesome tool that focuses purely on procedural content generation. It has a great plugin for UE4 you can use to handle Houdini Engine assets. If you have been bitten by the bug of procedural content generation I highly recommend taking a look at it.

If you’re interested in trying out the tool you can download it here. Check out the Trello Boardto see known bugs / what’s still in development

If you are inspired to make anything (Tree or Other) after reading this, I would love to see it! Share it with me on twitter @ArranLangmead.

Arran Langmead, Technical Artist

Keep reading

You may find this article interesting

Join discussion

Comments 10

  • Anonymous user

    Hi mate,
    very nice turorial!
    I am still stuck in understanding how exactly the triangles work..
    I am trying to create a plane which is two sided and cant find out how to properly do the backside..

    0

    Anonymous user

    ·2 years ago·
  • Anonymous user

    To those stuck at "Tangents, an array of struct vector + boolean (0,0,0 + 0/1)" Instruction.

    the instruction should read "create an array of Proc Mesh Tangents"

    Please Ignore previous advice. The only thing you have to do is create a variable of type "Proc Mesh Tangents" It ships with unreal engine and propagates in the search list.

    0

    Anonymous user

    ·2 years ago·
  • Anonymous user

    To those who are stuck at the "Tangents, an array of struct vector + boolean (0,0,0 + 0/1)" Instruction.

    The struct type doesn't exist, you will have to create one yourself...

    Right click in the content browser, the asset creation menu will appear. Scroll past animation, artificial intelligence, and blendables to blueprints. In the submenu of blueprints, down at the very bottom, there is an asset type called structure. Click it to create a NewUserDefinedStruct.

    Click into your new UserDefined Struct. The instructions given should make more sence now. Give this struct two variable types. One vector and one boolean. Label all the aspects of the structs to your liking, set default variables and save.

    Go back to the blueprint you were making and add a new variable. Edit the variable type and search for your new struct it will propegate in the list now. Don't forget to change the conntainer type from single variable to array like you have for the other variables up to this point.

    0

    Anonymous user

    ·2 years ago·
  • Anonymous user

    solved my past problem - one of two things was the block
    --creating the Tangents by simply "promote to tangents" from the "calculate tangents for mesh" node creates a useable variable
    -- setting a default xlength and ylength so that it actually has some size to generate ...

    0

    Anonymous user

    ·3 years ago·
  • Anonymous user

    to create "Tangents, an array of struct vector + boolean (0,0,0 + 0/1)" are we supposed to create a new blueprint - structure to hold the vector and boolean together? When do that, later on the Create Mesh Section rejects the connection with tooltip "only exactly matching structures are considered compatible" and I also can't get the calculate tangents for mesh to set that value either ...  would greatly appreciate any elaboration or pointing me towards other resources to fill knowledge gaps

    0

    Anonymous user

    ·3 years ago·
  • Anonymous user

    unsure what you mean..? stuck at the step trying to make the first grid, I can make the triangle appear but when I replicate the rest of the steps nothing is built visibly by the construction script.  My understanding of structs is limited so the line "Tangents, an array of struct vector + boolean (0,0,0 + 0/1)" really is throwing me off, I feel like there's some basics I'm missing that are being skated over,  guess I'm more beginner than this tutorial is meant for

    0

    Anonymous user

    ·3 years ago·
  • Anonymous user

    @Anon:
    "Proc Mesh Tangent" if you hover the mouse on the input the tooltip will always say what type it's expects.

    0

    Anonymous user

    ·3 years ago·
  • Anonymous user

    how do I create a struct array that is a "vector + boolean "? it's not an option in variable types

    0

    Anonymous user

    ·3 years ago·
  • _ M

    This is great and super helpful - I couldn't get the quad working // did you have any default values in the vertex/triangle/normals/uv/tangents?

    0

    _ M

    ·3 years ago·
  • James

    This is an awesome article. Thanks for the detail in the breakdown. Really insightful!

    0

    James

    ·3 years ago·

You might also like

We need your consent

We use cookies on this website to make your browsing experience better. By using the site you agree to our use of cookies.Learn more