This is the first part of a "making of" series of posts I will do, covering the process of how I put together this giant terrain. The goal is to share knowledge, but also to show how things are often not a straight path to a finished result.
Flak Jack is a game that takes place entirely in the air. Even so, to provide the proper context and backdrop for the action in the sky I wanted to depict the landscape, battlefields, and towns of the Somme and Western Front, to a reasonable degree of accuracy. There are no guides in the game or magic arrows that point you to your next objective, so using geographical landmarks like the Somme river, or trench lines will be all the player has to get their directional bearings.
I also wanted vast view distances as you fly around in the clouds, giving the player a sense of the scale of the battlefields, but also the sense of freedom and loneliness fighter pilots felt high above the trenches. VR is naturally quite a claustrophobic experience and the goal is to counteract that as much as possible.
Given my target frame rate is at least 90fps, making such a vast landscape is a big technical challenge, taxing the game's render resources even before I add any planes, characters, FX etc.
So some quick stats on what I'm currently working towards
- A 200km x 200km area of Northern France and the Somme river, derived from Satellite DEM terrain data
- A view distance of 90-130 km, with the player flying at an average altitude of 1.5-3km
- 8K terrain textures for base colour, roughness and maybe normals
- Real world road data
- Procedurally generated crop and field patterns and tree distributions
- Dressing the terrain with possibly millions of trees, basic buildings for towns, mesh based trench lines, towers of smoke FX breaking up the horizon and ground battle FX
- A playable flying area of approx 50km at any one time, varied from mission to mission
- Truesky driven dynamic lighting, with clouds casting shadows over huge areas of terrain
- A mechanic in the game that limits the player getting any closer to the ground than 1-15km above the terrain, with a story based transition effect that occludes the landscape at that close range.
The last bullet point there is a design choice I made in order to save time, meaning I don't need to create terrain that holds up at a close distance. The game is all about the flying, there is no air-to-ground combat, no take offs, no crashing into the ground. The landscape is just a backdrop, there to provide context and atmosphere.
I'm not trying to be a slave to reality, but I do want to create an environment that feels correct. Anyone who has been in a plane has a sense of what its like to fly over farmland, what it feels like to pass through the clouds etc. With that in mind I decided to use a mix of real world data, and procedural generation. The real world will give me all the natural scales and distributions, the details will come procedurally. I also have the added complication of creating a real place, but as it existed 100 years ago. On top of that, huge areas of my landscape will be heavily damaged, dotted with artillery craters and trench lines. Luckily, there is a stack of material available online. Aerial photographic reconasissance was actually a huge role for aircraft of the time, and many of the photos taken then still exist. They are amazing and terrifying in equal measure, as they give a true sense of the scale of destruction that occured along The Western Front.
Here is a small sample of the reference I've gathered so far. Some is from the area I'm trying to create, some is from other wars like the Vietnam War, some is just good reference for scale, lighting and atmosphere. Other games of course are also an interesting point of reference (The Battlefield 1 concept art book is brilliant inspiration!).
Try and try again
When I started this, I didn't know exactly what approach I would take. It has been a huge amount of trial and error. I've started over from scratch a few times now, sometimes because the results didnt look good, other times because the approach didnt allow for much revision, and also because the methods might not be very efficient and optimised once in engine.
I grapple with technical tasks in a strange way - I don't have a very solid maths or computer science background, so I don't often see the clearest path from the get go. I have just enough grasp on the technical side of things to be able to try different ideas, and more often than not I do manage to get the result I was after, but it's far from an efficient path. Most of what I write on this blog is going to be the messy, unfiltered break down of my process.
Before going to much further, I have to thank my friend Dan on the great guide he put together for getting height maps into UE4 at the correct scale - check it out!
Below is a quick summary of things I've tried and then started over again on in regards to making a 200kmx200m terrain:
- Get DEM data, assemble it into tiles as per UE4 terrain docs
- Cover it with the maximum allowable sized single texture (8k), generated from the height information in the DEM data.
- Add roads extracted from assembled screengrabs taken from snazzymaps, assembled into an 8k texture mask
- No good solution for generating the field patterns at this stage...
- Tested procedural foliage placement tool for trees. Good start, can use texture masks, not totally sure how well it will scale at this point
- Basic truesky added for atmospheric perspective test. View distance feels good. Set base cloud height to 1.5km
- Assembling and processing the DEM data involved jumping between a few different apps. Not very procedural
- Way too many terrain tiles! Good for a streaming tile system, but I need all tiles loaded all the time because of my huge view distance
- Nowhere near enough resolution trying to cover the whole terrain with a single 8k map
- Roads took ages to assemble, and then the base image resolution still wasnt high enough, resulting in blocky, very wide roads. Throws off scale.
- Shadows from clouds in Truesky are done as a light function. At the time of this test light functions don't yet work with the new forward renderer in UE4.
So after getting to this point I decided a few things coud certainly be done better. It was obvious texture resolution was going to be a problem. I didn't know how I was going to generate the farmland crop patterns yet. The number of terrain tiles seemed silly. So I came up with a few revisions.
- Use Houdini - especially the new terrain tools in Houdini 16
- I sourced the DEM data from a higher resolution source. 30m res instead of 90m I think it was...
- Cut back the terrain tile count. I'm not streaming them in, so I don't need anywhere near as many. I think each tile is a draw call, so the less the better
- Make a field patch generator in Houdini
- Refine the roads texture mask, so the roads were thinner
- Cover the entire terrain with a series of smaller, tiled textures. The middle of the map could be higher resolution than the outer half
- Treat all these base textures as masks for distributing tiling detail textures
- Use Houdini to work out the general "direction" of each crop field, so that the field patch textures could be aligned in a logical way based on the direction of the roads that border them
- Maybe generate a texture where this direction angle is stored as a colour, and then applied as a rotation value going into the UV's of my material textures
I prepared the newer higher res DEM data into a single image, and loaded it into Houdini. I enhanced a few areas a bit, just making some of the features more exaggerated. I also tripled the height range towards the very edge of the terrain. I figured it would help break up the horizon, which is an area of the map the player will never get to anyway. I was also able to output some textures from houdini including height, slope, erosion masks etc.
I made some big changes to how I mapped materials and textures to the whole terrain. Instead of using 1 material, I took advantage of the fact that the terrain was already split into tiles, and then made material instances that were mapped to groups of those tiles. In terms of textures, I split into 4 middle tiles and then 4 outer tiles. The middle tile textures are 8k, the outer are 4k. They overlap of course, but when applied to the groups of terrain tiles the overlap is not a problem. I originally tried "stitching" the tiled textures all in the one material. This presented me with a big problem where textures were smearing outside their UV borders. Eventually I realised this was because of mip mapping and general texture compression side effects. I also had a bunch of fiddly UV offsetting to manage to get the texture tile to sit where I wanted it. I have to thank Cory from Morepork Games for helping me out here, explaining the problem. Masking the texture borders could have been done in the material, but it would have added unwanted shader complexity, and in the end his material instancing / mapping to tile groups suggestion was the way to go. Thanks Cory!
So the texture tile breakdown looks something like this:
That is probably enough for this post, part 2 will be coming soon covering how I made these crazy field patterns in Houdini!
Look Ma, I'm a Farmer!