Creating Procedural Planets In Unity — Part 2

Welcome to Part 2 of an ongoing tutorial series about the Poly Universe Project, where I’m sharing what I’ve learned about making procedural planets in Unity. If didn’t just finish reading Part 1, here’s a quick recap: We’ve talked about different methods for subdividing simple objects, and we have the code necessary to generate a round sphere of polygons.

Now let’s start on the code for extruding and insetting groups of faces, which will let us add landmasses to our planets! Like before, you can find an example Unity project that includes all of this code right here, on GitHub.

A semi-arid world, created by extruding polygons to create oceans and multiple layers of hills.

Step 1 — Stitching Polygons

Extruding a set of polygons means separating them from their neighbors, and pulling them upwards. Since each poly in our planet currently shares vertices with its neighbors, the first thing we’ll need to do is identify the edges that need to be separated, and then create new vertices for the polygons that we want to extrude. This will let us move them without dragging their neighbors along with them.

But before we can do that we need to let each polygon on our planet know who its neighbors are. First let’s add a List of neighbors to the Polygon class, and a simple function to ask if two Polygons are neighbors. We’ll also add a way to replace neighbors, for when we want to split the world surface apart, and we’ll give each poly a color, so that we can tell the ocean apart from the land…

public class Polygon
{
public List<int> m_Vertices;
public List<Polygon> m_Neighbors;
public Color32 m_Color;
public bool m_SmoothNormals;

Notice that we can’t fill out the m_Neighbors list in the same way that we fill out the m_Vertices list in the constructor. This has to be done later, once all the polygons have been created. So let’s add a new function to the Planet class to calculate the neighbors for each Polygon:

public class Planet
{
...

public void CalculateNeighbors()
{
foreach (Polygon poly in m_Polygons)
{
foreach (Polygon other_poly in m_Polygons)
{
if (poly == other_poly)
continue;

if (poly.IsNeighborOf (other_poly))
poly.m_Neighbors.Add(other_poly);
}
}
}
}

Time for a quick digression about run-time performance! Calculating each Polygon’s neighbors, like we just did above, is pretty slow. There are much faster ways to do this than by looping over each polygon in the planet twice, but faster methods are also going to be much harder to read and understand. So here, and in general in these tutorials, if there’s a trade-off between clarity and efficiency, I’ve opted for clarity. If you’re interested in optimization, let me know in the comments; I can do another tutorial in the future to show how these techniques can be sped up.

Back to business! Now that each Polygon knows who its neighbors are, what we really want to be able to do is to grab a set of Polygons and separate them from any neighbors who are outside of that set. This will let us grab a section of the planet and drag it upwards to make land, without dragging the edge of the ocean up with it. To do this we need to identify the Edges that lay between the Polygons that we’ll be separating. Let’s create an Edge class:

public class Edge
{
// The Poly that's inside the Edge. This is the one
// we'll be extruding or insetting.
public Polygon m_InnerPoly;

And while we’re at it, let’s create a helper class called EdgeSet. This will a set of unique Edges, so we’ll use HashSet<Edge>, with a few extra convenience functions just for us:

public class EdgeSet : HashSet<Edge>
{
// Split - Given a list of original vertex indices and a list of
// replacements, update m_InnerVerts to use the new replacement
// vertices.

Now let’s do the same thing for Polygons by making a helper class called PolySet — to handle operations that we’ll need to do on a groups of polys. We’ll add a function to generate an EdgeSet out of the Edges that surround a PolySet, and a function to give us a list of the vertices used by Polygons in the Set (with no duplicates):

public class PolySet : HashSet<Polygon>
{
//Given a set of Polys, calculate the set of Edges
//that surround them.

…And we’ll need to add one more function to the Planet class: A method that takes a list of vertices, and creates new vertices with the same positions. This is the first step in splitting a set of Polygons away from their neighbors. The Polys inside the set will be given the new cloned vertices, while their neighbors outside the set will keep the old ones.

public class Planet
{
....

With that done, we now have enough to write the function that will take a group of polygons and separate them from their neighbors, then ‘stitch’ a row row of newly created polygons to fill in the seam.

public class Planet
{
....

Step 2 — Extruding

If we call the StitchEdges() function on our planet now, we wouldn’t see any visible changes. One set of polygons has been disconnected from another, in theory, but in practice they’re still located at the same place we left them. So let’s create a function to take a PolySet and push it outwards from the planet’s core:

public class Planet
{
....

Step 3 — Insetting

To create multiple layers of hills, it’s handy to be able to pull groups of Polys inward towards each other, then extrude them again. So let’s also add a simple Inset() function to pull the vertices of a group of Polys together:

public class Planet
{
....

My apologies for making this a very code-intensive tutorial, but we’re almost done. We just need a function to grab a set of polygons from inside a random sphere, and we’ll have everything we need to extrude landforms on our tiny planet:

public class Planet
{
....

public PolySet GetPolysInSphere(Vector3 center,
float radius,
IEnumerable<Polygon> source)
{
PolySet newSet = new PolySet();
foreach(Polygon p in source)
{
foreach(int vertexIndex in p.m_Vertices)
{
float distanceToSphere = Vector3.Distance(center,
m_Vertices[vertexIndex]);

if (distanceToSphere <= radius)
{
newSet.Add(p);
break;
}
}
}
return newSet;
}
}

And that’s it! By calling GetPolysInSphere() with randomly generated spheres, then calling Extrude() on the results, we get a tiny planet complete with oceans, plains, and hills:

In the next few tutorials I’ll talk more about artistic questions, like how to choose different sets of polygons to achieve different effects, how to create translucent oceans, and how to add an efficient form of ambient occlusion to your planets. Speaking of which, you can find Tutorial 3 here!

If you didn’t grab it already, a working Unity demo of this tutorial is available here. And you can see a demo of this kind of planet generation tech being used in an actual game right over here.

Thanks for reading, and good luck creating worlds!