Unity3D : Adventures in creating huge outdoor areas (terrains) — Part 1: Naiive tests

Miroslav Martinovič
9 min readJul 19, 2020

So this one day, I had a crazy idea: “I want to create a large outdoor area in Unity.”

As in, really large. I mean it. 30x30 km square of, specifically, mountains and coniferous forests of Northern Canada. Why? Not important right now. What’s important is the road full of experimentation, pain and learning that it set me on, and made me go through. In this series, I will focus on describing that road in the hopes it will save someone else as insane as me some of that pain, and lots of time.

Oh, and I almost forgot to mention one important detail: I decided and knew from the start that I want to have the whole range of walkable terrain visible. No hiding behind distance fog! Because how else could I even claim to approximate the monumental size of Canadian mountains?

(I would like to point out that I am aware this is absolutely the biggest, and clearest “don’t do this, stupid!” that any unity programmer worth their salt (and even me) is aware of. That is precisely large part of the point and motivation behind me trying to do it and seeing how far I can get.)

I decided to use Unity 2019.2.17f1 for a nice compromise between an up-to-date version, and actually stable and usable one. I also decided to use SRP (Standard Rendering Pipeline) for good compatibility with the existing library of assets on the store. And according to my decision in the previous paragraph, the first thing I did after opening the new project was to set my main camera’s Far Clipping Plane to 30000. Yes, you read that correctly. Thirty thousand.

The image you see below the title (or above this paragraph, if it shows as blurry below the title as it shows to me) is the result of the first two days of this crazy adventure. Underwhelming, isn’t it? Well… yes and no. Visually, with no context, it certainly is. Visually, with the context that I have spent literally zero time on actual artistry, and only focused on the technical side, I would say it’s surprisingly close to almost-not-crap (as judged by the admittedly low standards of one-man indie games of these days). Technically? 18FPS, with this view, on my machine… I would say that’s a good start, keeping in mind there’s no actual optimizations in it.

In this first part, I will document how I got there. In following parts, I’ll document how I’ll get even further.

Test 1: What happens when I actually just make a terrain of that in-game size?

For this first test I decided the whole area will be a single Unity Terrain object. Of course that’s the worst idea, for all the reasons, but I wanted to actually experience them, to know what is the most basic, barebones, performance baseline.

I went and got a heightmap from here. Not an ideal tool, but the best of all the free sources, plus the only one I know about that actually contains the whole planet. The downside is that the maximum resolution you can get from there is 1081x1081, which as it turns out, is surprisingly usable in many cases, but might not be for you. You’ll have to judge and try that by yourself.

Step 2: Importing into unity. I’d like to note: you don’t have to place the heightmap into your unity project, the Import function on the terrain can pull it from anywhere and re-does it into its own data for terrain which it saves into the project.

So, importing to unity.

There is few important basic settings on the terrain, which aren’t exactly self-explanatory, and it’s important to get them right at the import time, before doing anything else, because changing them later is either almost impossible, or it will destroy everything you’ve done with the terrain afterwards.

This is not how you get those settings right.

You might think, as I did, “well, obviously, the dimensions are squished, I’ll just change the scale of the transform”.

No you won’t. Terrain ignores scale and rotation of the transform, because… well… Because that’s how it is. Let’s go through those settings, then.

The width, length, and height here is how you actually change the terrain dimensions. You can change them after importing heightmap, and the recalculations will happen, but if you want the terrain to actually correspond to the source, you need one piece of information that you’ll need to get from the source of the heightmap: Terrain height. This is the range of heights on the heightmap. When I open up the readme file included in the download from terrain.party, it contains some useful info, including this one: “The original elevation models for this area contained elevations ranging from
780 through 2439 meters.”

That tells me the (actual sea-level elevation) of lowest and highest point, that being a black pixel, and white pixel respectively. Your terrain “height” is the range between those two, in my case 1659 (unity units, which are by default equal to meters).

Heightmap resolution in there, as you can see, is wrong, and when you change it after the import, it does nothing and moreover removes your “Resize button”. So let’s try this again, and do it right. It’s easy, all you need to do is actually set these 4 things correctly in the heightmap import window.

When the format says it’s “.raw”, it really means it. The file itself contains none of this information. Hopefully your heightmap source provides it.

Width and height in here are the actual pixel w/h of the heightmap. This needs to be power of two +1 (or power of two minus one, that also works, but as I later found out, seems to cause seams when tiling terrains). Then, the terrain size are the desired dimensions. You can see the proper settings in there, 30km x 30km, with range of heights calculated based on the sea-level altitude range.

Note that if you set the bit depth incorrectly, as I did at first (the default terrain.party heightmap is 8bit), you’ll most likely get a completely flat terrain. After fixing that…

Viola, as they say in France! (That’s a joke.)

Note that the Heightmap Resolution in inspector is still wrong, because… well… Because raisins. It’s always wrong, even if you don’t touch it at all, and just import the heightmap correctly, but it doesn’t seem to affect anything, so let’s continue. (Yes, we are also ignoring all the other settings for now, we’ll get to them later).

Welcome to your terrain!

Beautiful, isn’t it? Well… No. But it runs surprisingly well! But it’s ugly.

Time for some calculations, and these are important for you to be able to work with terrains properly:

1081x1081 pixels in heightmap, for 30000x30000 unit terrain, that’s 27 units (meters) per one pixel of heightmap, meaning one terrain quad is going to be 27x27 units (meters) large, which means… Sharp edges and large obviously flat surfaces. Yeah. That was to be expected, frankly.

But it runs surprisingly well, relative to what I’ve heard about unity terrains! Don’t worry, it won’t for too long. My next step was to give it some texture and populate it a bit. And since I already know from my previous shallow experiments, that the default terrain texturing is kind of crap, I avoided that alltogether. So I brought in something you might have noticed in there from the start — CTS. Complete Terrain Shader. Plus Tenkoku Dynamic Sky (which I later replaced for Enviro). I was still just experimenting with performance, and I wanted to see what happens when I add these bad boys to the mix.

And suddenly, with tenkoku in the mix, oh boy, we’re at 30fps. But wait, there’s more! I want my environment to be all snowy, and by default basically all snow, so I brought in an asset called “Global Snow”.

Note how much better it actually looks, even though it’s still the same terrain with sharp edges. Also note the FPS going down again, from 30 to 16–20.

I later removed the Global Snow plugin and actually used the CTS, because, sadly, it didn’t play nice with other things. But for now, let’s see how much more we can ruin the performance by adding trees!

I want a lush, dense forest.

Angry Mesh — Winter Environment Pack to the rescue! Let’s start with one tree, add it to the terrain (because you explicitly need to add it to the terrain’s library before using it properly), paint a forest around us, and…

Oh, wow. Unity is being optimistic there in the top right corner with 10 FPS, in reality it was running at that moment at the 3 FPS that CTS claims in upper left. It had some “bright” moments of 14FPS, but… most of the time… nope.

Don’t worry, there’s an easy way to make it less horrible!

No, not that one! Unity ignores that one.

No, not that one! Unity ignores that one for whatever reason.

This one

This Other One! Yes, LOD groups on the tree prefabs themselves. Settings you see are close to what original ones were, and frankly, those were a bit ridiculous, at least for my purposes. What I ended up changing them to was LOD1 starting at 85%, LOD2 starting at 65, and culling I didn’t touch yet. Take care to actually change it inside the prefab editing, not when you just have the prefab selected. Also, take note, that the terrain doesn’t care. You’ll have to remove all the trees, remove the tree from the list in the terrain, add it again, and paint it again! Only then will it reflect the change. So, yeah, that means before you use any tree-like prefab, be sure to check and set the LODs correctly, otherwise… pain.

ReLODed, removed, readded, repainted, we’re back to the land of almost living with FPS between 12–15… at least until the sun starts coming up, that kills us back to 4FPS.

Also this is the point at which i noticed that the Global Snow doesn’t take shadows from trees, which was why I removed it. I need shadows from trees on my terrain.

…oh? What’s that? As if by magic, we’re back to 20–30 FPS? I guess… a general shader that tries to add snow to EVERYTHING… Isn’t that performant.

Cool!

At this point, I was starting to feel like what I’m trying to do will actually be viable, somehow. Yes, the terrain still doesn’t have the proper detail, and the FPS is already below ideal, but this is the stupidest, laziest way to do this, containing literally no optimizations, and it… runs!

So the last thing I decided to do that day was actually CTS-shading the terrain. Last experiment in this naiive first step.

This was just literally 4 click thing. Window->Procedural Worlds->Add CTS to all terrains, then in new component on the terrain Profile, pick, select CTS_Basic, done. We’ll tweak things later.

Still runs! And still very surprisingly well! Great.

And with a feeling of hope, I went to sleep, looking forward to the next day, when I would continue by proper terrain tiling.

--

--

Miroslav Martinovič

Hypercreative hypoactive pessimist with his head up in the clouds, functioning brain, pert mouth/fingers, and no patience for morons and cultists.