So the new game I am working on is supposed to feature, among other things, an endless seamless terrain system for planetary bodies. I toyed with the idea of adding it to Windward on several occasions, but the back-end support for it simply wasn't there and I decided that it would wait until the next project (that I am working on now). Before working on the endless terrain system, TNet first needed to support multiple channels seamlessly, with the ability to enter / join channels (think: regions) at will. Well, TNet 3 supports that feature -- and so I got started.
The idea behind the reason for something like that is pretty straightforward. I want it to be possible to pick a landing site on a planet or a moon, then drive from the landing site to any other point on that celestial body without any loading times -- and I want to be able to do it in multiplayer.
So how would one approach a task like this one? Well, in my case I've been using Unity's terrain system with Windward, so naturally continuing to use it was a logical choice. Problem is... quite obviously it's impossible to generate a terrain that spans the entire planet's surface, so the content has to be streamed in somehow, and instead of using one terrain like in Windward, I'd have to create multiple terrains around the player and stream in their content as the player travels around the world. I actually did something like that back in... 2009 I think it was? It was for a game prototype involving tanks and it wasn't too much of a challenge. Doing it without impacting performance was the only difficulty, and if the actual terrain is generated on a separate thread it shouldn't have any visible impact on the game itself.
To quickly test this idea's feasibility and refresh my memory I wrote a simple test that uses LibNoise to generate a multi-layered noise on one thread, then fills the TerrainData in the other thread. Timing it showed that a 256x256 terrain using 3 perlin noise generators (3 octaves each) + 1 simple noise (3 octaves) takes 242 milliseconds with another 16 milliseconds spent setting the TerrainData and 18 more milliseconds spent setting the terrain's splat maps. Since the setting of terrain data and splat maps are separate actions, this means that the main thread requires 2 frames to set its content and the longest stutter would be roughly 1 frame (1000/18 = 55.56 FPS).
Two problems here. I will focus on the first problem in this post: the terrain looked rather "meh":
Second problem was that even a 1 frame stutter is still going to be unpleasant to have, especially when it's done 2 frames in a row. But hey, one problem at a time. I'm a visual person first. I want to see what i'm working with!
To address the terrain being all boring, I have two solutions. First, I can use different types of noise like I did in Windward, and use one noise to control the blending of other noises. This was used in Windward to great extent to create cliffs next to rolling planes and idyllic sandy beaches. The only issue is that in this case I am creating a terrain to be used for airless celestial bodies, not ridged cliffs towering over sandy beaches.(Plus, have you ever tried driving a vehicle in a low-G environment on a rocky surface? I'll give you a hint: I hope you like flipping over!)
Taking Luna as an example, it's pock-marked with craters, and as this KSP dev blog post
explains, the easiest way to generate craters is to use Voronoi noise.
In simplest terms, Voronoi noise can be used to generate a cell-like pattern where each cell has a center that you can access and calculate a distance to.
Imagining each cell as a crater, the distance from the center can be easily converted to depth. Properly clamped (limit the maximum distance from the center), you can easily use this to figure out where depressions in the terrain should be:
Next step is to add a curve to the crater, as craters not only lower the terrain in the center, but they also raise the terrain at the edges, creating visible ridges. Fortunately creating a curve in Unity is a trivial matter, and adding a public AnimationCurve to the generator class, then sampling it in the code using curve.Evaluate() does the job nicely.
Looking at the photos of various moons it's quite clear that the center of the craters is generally darker than the surrounding area as the lighter dust gets blown out of the crater, exposing the bedrock underneath. Figuring out which areas should be bright and which should be dark is a trivial matter using the data already present:
Finally, the edges of the crater should be brighter in order to give it some visible contrast (and because in reality they usually are anyway). Again, using the existing data we can easily figure out where the edges should be:
Combine the darkened and brightened areas together gives this:
Just to make it prettier, I apply some texturing on top along with some normal maps:
It looks better from the ground:
Unfortunately after applying all of that, the terrain generation time is now up to 1155 milliseconds. Since this happens on a separate thread, it's not that much of an issue -- but there are other limitations with the Unity's built-in terrain system that ultimately caused me to decide to roll my own that I will examine more closely in the next post.