Fantasy world map showing diverse biomes and landscapes

For a while I have wanted to create some kind of fantasy world map. My first experiments involved using Midjourney and Dall-E 3 to generate 2D cartography elements that I could then combine to create a world map in Photoshop. Most of these experiments were a failure due to the lack of consistent style, scale, and perspective of the generated images. Reattempting with Midjourney’s updated style references could yield much better results.

Image 1

Mismatch in style and perspective

Image 2

Mismatch in scale

After these failures I still had the urge to create a large fantasy map or overworld. I figured I’d give Gaea a try. I had the software for some time and never really used it for any significant project. Soon enough after starting up the software I was hooked on tuning every detail of the world. And I figured I would use generative AI to help me.

1: Concept Art with Midjourney

Since I did not have a specific end-goal in mind, I did not want to spend much time designing the world. I aimed to demonstrate that it’s possible to create a pleasant looking world map using Gaea that could be used and adjusted for other projects. So instead of using existing generators I figured I’d use Midjourney for the concept art. It didn’t take me long to get a concept that appealed to me. One crucial thing for the prompt was for the map to include different biomes. In my opinion, having distinct geographic regions is essential when designing any large world map as it’s so crucial for the geography and history of the world. Initial fantasy world map concept art with diverse biomes

My initial prompt was as follows:

 Pangea, islands and oceans map, top down, fantasy world, flat

I added ‘Pangea’ to the prompt to reduce the number of continents generated. And then used the following prompt to generate variations:

 diverse fantasy world map

The ability to regenerate parts of the image is amazing to add biomes such as snowlands, deserts, and marshes. In the end it was primarily the overall shape that mattered in the concept art though.

2: Masking in Photoshop

Using the concept art as a base I moved to Photoshop to refine and detail the major geographical features such as oceans, mountains, continents, lakes, and shorelines. This step involved manual editing to create the masks that could then be directly imported into Gaea. In the end I came back quite a few times to these maps to adjust the location of the biomes so they made more sense, such as moving the desert farther from snowy mountains.. I also found that lakes required significant iteration to work well with the simulations in Gaea.

Image 1

Black and White Ocean Mask

Image 2

Grayscale Mountain Mask

3: Terrain Generation in Gaea

Designing the node graph in Gaea was by far the most fun and time consuming process of the entire workflow. I admit that I didn’t maintain a very tidy node structure. In my opinion structuring things neatly is only worth the effort once you’ve stopped iterating or when it becomes a problem. If I decide to come back to it later I’m sure it will be frustrating though. Node graph setup in Gaea for terrain generation

Since it was my first time using Gaea it took a while to get used to the tool. There are definitely some idiosyncrasies to get used to. For example Gaea rounds height values often to whole percentages, but this is visual only - this definitely caused some confusion to me as I thought precision was limited. However, you can input values like 10.1, and although the display rounds it to 10, the actual value remains precise under the hood. Rescaling terrain is also painful, thankfully there is a constant node and blending options are powerful. Other than that it was surprisingly easy to become quickly productive with the tool. The nodes are powerful and mostly intuitive and the performance is acceptable, especially considering it’s all C# scripts and Image Magick under the hood. Maybe some day I’ll make my own tool that would run on the GPU and basically be instant!

Gaea terrain generation process animation

What especially blew my mind was the SatMap node in Gaea. It’s actually simply a lookup texture based on the greyscale texturing that is generated based on height, curvature, AO and other parameters. But given the huge library and good quality of the texturing the results are really impressive. By blending different biomes together with different SatMap lookup textures it was easy to texture the different biomes, especially since I didn’t need to worry about close-ups. It made it also easy to texture the custom landmarks I added such as the volcano and mesa. With the initial terrain and texturing in place it was now ready for export into Unreal.

4: Integration into Unreal Engine

After struggling with the export settings for a while to get it to export 16bit PNG that Unreal can read I imported the height and color map into Unreal using the landscape.

Importing fantasy world terrain and textures into Unreal Engine

After adding the sky atmosphere, clouds and tweaking the lighting the results started to look nice already. However two significant issues were immediately highlighted:

  • Because of the planet curvature the heightmap would not bend with the earth
    • Adjusting the heightmap to account for curvature would loose too much precision
  • The ocean shader would stop at the end of the height map

Circumventing these issues using the heightmap landscape would be challenging. Since I didn’t need collision or Lumen a good candidate to replace the landscape seemed to be Nanite! It should easily have enough precision to handle the curved heightfield as one mesh. Additionally, we should benefit from the added performance of level-of-detail per cluster. But first I would need to generate the mesh from the heightfield. Gaea does not provide this option. Thankfully Houdini has great options for writing simple scripts that modify the landscape, as well as powerful export options.

5: Planet curvature in Houdini

Animating planet curvature adjustment in Houdini

Achieving the desired results required just a few nodes. Here is a quick overview:

  • Load and scale the heightmap from Gaea to match the planet radius in Unreal
  • Also load in the ocean mask output by Gaea
  • Convert the heightfield to a polygon mesh
  • Blast the polygon by the heightfield mask
    • Getting the edges right proved tricky.
    • The best I could do was promoting the point group to primitives using the ‘Minimum’ promotion method
    • Next I filtered by the condition @mask > 0.3
  • Last step was curving the mesh by the planet radius
vector2 delta2D = set(@P.x / radius, @P.z/ radius);

float height = @P.y;
float length2D = length(delta2D);

float offsetXY = sin(length2D);
float offsetZ = cos(length2D);

vector2 normal2D = normalize(delta2D);

vector normal = set(normal2D.x * offsetXY, offsetZ, normal2D.y * offsetXY); 

@P = normal * (radius + height);
@P.y -= radius;

This scripts adds a nice curve with minimal distortion. It does however change the bounds in the XY directions. Additionally to solve the problem of the ocean I cut out the ocean from the mesh. The ocean is then rendered as a separate mesh that covers half the globe. Just using the texture Gaea provided for the ocean in Unreal wouldn’t be enough though.

6: Ocean Material

Dynamic ocean material simulation on fantasy world map Using Houdini it was easy to generate a mesh that followed the planet curve perfectly. This mesh would be used for the ocean material. I probably spent too much time on the ocean material. I tried to create a flowmap based on the heightfield. This worked well on the coastline but didn’t generate a nice macro-flow. Since I didn’t end up recording any animation, the effort probably wasn’t worth it. Other than that the material adjusts color based on distance to shore. It also has some scrolling normal effects that follow the flowmap. I am quite happy with the foam I added based on the normals and ocean height variation texture (which also scales the normal intensity to break up the water).

Generated water texture for ocean material in fantasy world And to add some detail I used Midjourney to generate a tiling texture! It is not very noticeable but I believe this to be a very powerful workflow. Midjourney can be great for creating variation textures.

7: Vegetation Placement

With all this, the fantasy world looks good. But it’s missing detail. The satnav textures are nice but look flat. Gaea has an option to simulate vegetation growth which is baked into the height map. Instead I wanted to use actual tree meshes per biome to generate detail and make the world feel alive. The problem was that I couldn’t easily use the auto-grass that Unreal offers for the landscape to scatter the meshes based on the output density maps. Instead I wrote a simple C++ script that reads the texture and generates instances of trees and bushes. I tried PCG but it was impractical due to poor performance and high memory usage.

TArray<float> Points;
ReadTexture_PlatformData(Texture, Points);
TArray<float> Heights;
ReadTexture_PlatformData(HeightMap, Heights);

double Start = MapSize / 2.0;
double Spacing = MapSize / (double)Width;

for (int32 Y = 0; Y < TextureHeight; ++Y)
{
	for (int32 X = 0; X < TextureWidth; ++X)
	{
		float Value = Points[Y * Width + X];
		float Score = Value * Density - Random.GetFraction() * Noise;

		if (Score > Threshold)
		{
			double Z = Heights[Y * Width + X];
			FVector P(Start - Y * Spacing, -Start + X * Spacing, Z * HeightScale + ZOffset);

			FVector2D Delta2D(P.X / WorldSize, P.Y / WorldSize);
			double Length2D = Delta2D.Length();
			double L = FMath::Sin(Length2D);
			double H = FMath::Cos(Length2D);
			FVector2D Normal2D = Delta2D.GetSafeNormal();
			FVector Normal(Normal2D.X * L, Normal2D.Y * L, H);
			P = Normal * WorldSize + Normal * P.Z;
			P.Z -= WorldSize;

			double ScaleMultiplier = Random.FRandRange(ScaleRange.X, ScaleRange.Y);
			FQuat Rotation = FQuat(Normal, Random.FRandRange(0, UE_DOUBLE_TWO_PI));

			FTransform InstanceTransform;
			InstanceTransform.SetLocation(P);
			InstanceTransform.SetScale3D(FVector(Scale * ScaleMultiplier));
			InstanceTransform.SetRotation(Rotation);
			Mesh->AddInstance(InstanceTransform);
		}
	}
}

10: Atmospheric Effects

With the trees, the ocean and the texture terrain the world was ready. The last step was to tweak the atmospheric setting to nicely fit the small planet size. As well I made sure to tweak the rendering settings for maximum quality. concept These are roughly the render settings I used:

  • No Lumen because there is nothing to bounce offers
  • Realtime sky capture placed at center of the planet
    • Not exactly correct but generated good enough reflections and ambient lighting
  • Heterogenous volumes for clouds
    • I experimented with importing cloud VDBs before.
    • Quality is great but it does not react to the skylight yet
  • Convolution Bloom

11: Final Rendering

Image 1

Original Concept

Image 2

Final Result

In the end I am happy with what was a little over a week of work. For sure there is a lot of room for improvement. For example more details, higher quality meshes, roads and cities, better waves near the coast. With the power of generative AI getting better I am excited to try even more ambitious projects, for example to generate an entire history and population for the world.

Detailed view of the huge fantasy world map Detailed view of lush forest on a fantasy world map Sunset over forest and mountains on a fantasy world map Close up of desert mesa on a fantasy world map Close up of volcano on a fantasy world map