The Great War - Recreating the Western Front from the air - Part 3
This post continues on from Part 1 & Part 2.
Here I cover an entirely new approach to texturing my terrain.
This is a BIG post. I've pretty much redone all of the work covered in the last 2 posts. Strap in.
Rebuilding everything, using OPEN STREET MAPS
So as I have covered in earlier blog posts, I have been working on recreating a massive area of Northern France as seen from the air, as it might have appeared 100 years ago.
I had originally bashed together the road network that defines the fields and farmland from screen captured areas of google maps, processed through a site called Snazzymaps. It wasn't ideal, I was already starting with a source that was too low resolution, and wasn't vectorized. It was a pain to put together, it wasn't procedural, and it needed a bunch of clean up work when imported into houdini and converted to polygons. I lost much of the finer shape detail through fusing points and resampling. I felt like I needed to take a step back and find better, more manageable source data. Open Streep Maps was the answer.
At first I was just going to use Open Street Maps to gather better roads data, but once I had gathered all the .osm tiles I needed, I realised that all the data I wanted was in these files. Roads, buildings, water, forest areas - everything. It all lined up, it all came from the same source, it was vectorised and tagged with all sorts of useful info - it was obvious - why not derive everything from these .osm files?
So, I bit the bullet and decided to start fresh - I was going to pretty much throw away all the previous data I had generated. It would mean a lot of doing things over again, but it would pay off because I wouldn't need to manually line things up from different sources, I wouldn't be fighting my existing, slightly dodgy source files.
This was an instance where a procedural workflow really pays off - I already had my Houdini scenes for manipulating and analysing the fields, they were still valid and reusable, I would just be feeding them better source data, from the .osm files.
On the left is a browser view of Open Streep Maps (OSM), which can be downloaded as vectorised data. On the right is Googles satellite view of the same area. It shows the kind of textural detail and complexity I would need to create.
Open Street Map (OSM) + HOUDINI
As luck would have it, an open street map importer has been released as part of Houdini's game development toolset. It lets you download regions of OSM data, reads them into Houdini as polygon primitives, and tags primitives with all sorts of useful data from OSM.
The core idea I had here was to extract the various types of data from the .osm files, such as buildings, wooded areas, waterways and roads. This data would then be used in a few different ways such as texture masks, and asset placement for Unreal Engine. The first step was to download all the info I needed from open street maps. There is a great intro video over on the Houdini page on how the process works. Because the area I'm working with is so big, I had to go through and download smaller regions bit by bit. I stored the coverage coordinates of each tile in the .osm file name, along with the zoom level.
For example - amiens_zoom300m_n50.0783_e2.6182_s49.6935_w1.9940.osm
Each file was fairly big, averaging around 400mb. I loaded them all into Houdini, and then started the process of breaking down the files.
PROCESSING THE OSM data AND FITTING TO the height field TERRAIN
TRANSFORMING:
My terrain height field was already in Houdini (see previous blog post). So the next big step was to get the .osm data into the same transform space as the terrain. This involved a certain amount of lining things up by eye. I had already perfectly aligned each .osm tile to its neighbour, and fused any overlapping points. Then I just had to align the .osm to the heightmap. The old road network I had assembled also came in handy here as I guide. In the end it's not 100% perfect, but it's very very close - certainly much more accurate than before.
FILTERING:
There is lots of info stored in each .osm file, and I only needed some of it. The first step was to filter and split the data into things like roads, buildings, forest areas, railways, urban and industrial zones, bridges, key landmark buildings like churches and cathedrals, and waterways. I could remove certain things that were not required like modern day highways, footpaths, very small or very large buildings etc etc. I also took the added step of colour coding everything so it was easier to read visually in Houdini. Once I had the filters, I created groups of primitives for each filter. All the geo from each .osm got merged together, as did the groups, and I saved it all off as Houdini's native geo format before I continued working.
TAGGING WITH ATTRIBUTES:
The buildings in the .osm data only exist as a basic outline shape describing the basic perimeter of each building. Some are tagged with extra info like how many stories the building has, or maybe if it's a landmark like a Church, but for the most part all I have to work with is each buildings size and shape. It was up to me to decide what kind of building I would place in the location of each building footprint. I would be replacing the many thousands of real world buildings with a library of maybe 20-50 simplified structures, copied over and over again.
So I came up with some pretty simple rules like how big, how square or long, how densely surrounded by other buildings etc etc, and then assigned a simple number index to each building stored as an attribute. These numbers would later correspond to a building type I would assign to each building footprint later on.
One more thing I did at this point was augment some of the data. For example, I knew I wanted lines of trees along the sides of roads, which weren't in the .osm files. So at this point I scattered points along certain sections of roadsides, and then grouped and tagged them like the rest of the incoming data.
Lastly the polygons that were created by the .osm files were often messy, so to clean things up I extruded their edges, and intersected them with my height field mesh. Using Houdini's awesome boolean tool in shatter mode, that gave me clean polygons to work with again, with all the required attributes from the .osm attached still.
This data preparation was a slow process, but when I look at the variety of detail in the files, and how natural the distribution of everything is given it comes from the real world, it makes it totally worth it. There was a lot of back and forth comparing my osm data against google maps satellite view as I tried to make sure I was getting all the key features you might see from the air.
So now the osm data was all prepared. It could now be used for creating texture masks, and later on I'll cover how I've used it for object placement.
Purple nodes are each loading in an osm data file for each area of the map. The transforms nudge them into alignment with the neigbouring regions. The blue nodes are each a sub-network that reads attribute data on the geometry, and sorts them into groups, and then colour codes each group. Finally each regions outputs are merged back together, and all regions are saved off to disk via the green nodes at the bottom.
structuring my houdini file and working in tiles
I spent some time cleaning up my houdini working file, organizing things into containers that related to the order of operations. this keeps things separated and neat. There was also some overlap between steps. For example, I had aligned the osm data to the height field. Then I went back a step to the heightfield processing, I loaded in the roads, and waterways data into the heightfield, and used them as masks to influence features. The roads were used as a blur mask, smoothing the height field where roads exist, and a similar thing was done for waterways, leveling off areas of water.
Instead of working on the whole data set all the time, I broke it down into a grid of 8x8 tiles, which matched the number of landscape components I was using in Unreal engine. This made things much easier to work on as I could focus on one tile at a time.
This is all pretty dry stuff, but it's the only way I could stay on top of so much data, but just as crucially, make sure the output for each regions was consistent.
My Houdini working fle - The red CONTROLS node is my main switch that toggles which tile I'm currently working from (0-64), covering the full 200 km x 200 km area I'm working with. The nodes that follow each cover a particular step in the process, and contain their own graphs inside. There was so much data to crunch and keep organised that trying to be as meticulous as I could was crucial. Each step often required rendering from a particular camera for that step, so the camera nodes on the left correspond with the process on the right.
TEXTURING
Starting fresh with substance designer
I wasn't entirely happy with my previous method of randomly applying tiling textures to each field. Instead, I wondered if there was a way I could procedurally create a texture for each field, one that reacted to the unique shape, size and coverage of objects that any one field may contain. It seemed like the perfect excuse to pick up and learn Substance Designer.
Using the .osm data, I set up a very simple render scene in Houdini. It moved the camera to the middle of each field, and rendered it in isolation. Various data from the .osm file was colour coded and used as a component of the output image. Each colour could then be used as a mask in substance. I ended up creating 2 mask images, the first with the following:
- RED = roads
- YELLOW = buildings
- GREEN = broadleaf forest
- BLUE-GREEN = forest
- WHITE = roadside trees
- BLUE = waterways
- CYAN = wetland
- PINK = railways
- VIOLET = railyards
- LIME = urban area
- PURPLE = industrial area
- ROSE = splitters (additional boundaries needed to get around some substance flood fill issues....)
In a second image, I rendered out the field itself, just the main shape, without any of the detail features from the first image.
This gave me 2 input mask images for every single field, that I could feed into Substance Designer. I then UV mapped each field with a UV projection from the houdini camera view, so when I later reapply the generated texture, it fits perfectly to the field. I didn't scale fields to fill the camera view, wasted space didn't matter as it basically gets cut out by the shape of the fields polygon. Also, by not scaling, I was sure that each field was receiving the same texel density.
One optimisation I made was to filter out very small fields, and very small urban fields (fields in close proximity to buildings) deciding that they could just get a generic field texture later on. This cut down the number of fields from around 900K, to 40K.
THE SUBSTANCE
This was my first real go at making a substance. I gathered a great big pile of screenshots from Google maps, giving me a good guide on what kinds of different crop field and farmland patterns I would need to make. I tried to boil down the key features to as few controls as possible. I then created master parameters for all the things I would want to adjust, set lower and upper limits for all the values and then exposed them so they could be manipulated. I won't go into a huge amount of detail on the substance itself here (maybe in a future post), but basically I broke it down into 3 possible types of fields (plowed fields, field with crops, and natural untouched fields). I then created a bunch of other controls that did fun things using the colour mask info from the input images.
There is some method to this madness, the two input mask images are loaded in on the left, then they are split into their colour coded components. Most of the complex stuff happens up the top of the graph where the farmland patterns are generated. Each blue box contains the main cluster of nodes to generate the texture for each element, and then the output results for each element are gathered in the green boxes. The 3 diagonal clusters of nodes on the right are where all the elements are merged together for the final output as colour, roughness and normal maps.
DRIVING SUBSTANCE WITH HOUDINI + Substance automation toolkit
Like many other 3D apps Houdini has a Substance plugin, so you can load and manipulate controls of a Substance file. I set up a Houdini graph that would feed the substance file with the correct input mask images for each field, along with randomised values for all the other input control parameters I had exposed on the Substance. At first I thought I would just be able to then run a render, getting maps exported for every single field. It turns out there isn't a way to do that natively in houdini for substances - so this was a bit of a dead end.
Instead, I used the automation toolkit for Substance, which among other things, uses python scripts to automate batch jobs for Substance. They have an example on how to run out variations of a substance file and write out all the export maps here. So after hacking my way through the example script, I was able to make a script that could chew through all the input mask maps I had rendered from Houdini, and generate a substance for each field, rendering out base colour, roughness and a normal map for each field. For each field the script would pick randomised values for the parameters I had defined, creating a huge amount of variety. It took roughly 3 or 4 hours to render out the approximately 1000 fields that would make up a tile, so I would just let it run overnight. The end result of this step was another folder of renders for each tile, but this time textured with substance.
The usual suspects - as you can see not all the main features come from the OSM data directly. The substance did a bunch of subdivision of fields into smaller fields, and then assigned different criteria to each sub field which defined whether it was planted with crops, or maybe it was plowed dirt, or perhaps it was left natural and wasn't farmland at all.
Stitching it all together
Once all the individual maps were rendered, I applied a material to each field primitive in Houdini. Houdini has a concept of local material overrides, so I could define the 3 input maps as a local override for each field. For example, the field with the primitive number 287, would us the col/rough/normal textures numbered 287 for the given tile...
So now every single field had a mapping to its own unique set of substance maps, I then just had to render them all together as series of textures, one colour/roughness/normal for each tile respectively.
From here I was free to do whatever touch ups, filtering and processing that I wanted in Photoshop for each tile map. I saved them off as 4K textures, ready for import into UE4.
Tuning the look
While I was putting together the substance, I was only ever looking at 1 field at a time. It wasn't until I had all the fields for a given tile together that I could get a good sense of how it looked at a macro level. Focusing on 1 tile, I went through 10 or so iterations tweaking all sorts of values, constantly comparing it against screenshots from google maps, until I had everything in the right ranges.
Bit by bit, it started to look more like the reference screenshots I gathered from google maps. Getting an exact match was never the goal, instead I was trying to get the right balance of distributions at a macro level
A full tile all together!
A closer look at a smaller area
The real location for comparison - The town of Doullens, north of Amiens, France.
The texture itself still needs a bit of dialing in, but for the most part the core process is in place.
One last thing that caught me out was how to make sure each tile joined seamlessly with its neighbour tiles. There was a problem in that fields near the edge of a tile would overlap into the next tile. However, Substance was being told to randomise all of it's input parameters, so the same field polygon would get a completely differnt look applied each time it was used across tiles. My solution in the end was to always render the first version of any one field, by just copying the geometry from a tile and rendering it slightly in front..
- So I might render tile 35 first, with tile 35s folder of substance textures....
- Lets say tile 35 has a field polygon numbered 666, that crosses over into tile 36....
- Then I might render tile 36, but patch it with field 666 from tile 35 also using all the other overlap fields from tile 35...
It sounds convoluted, maybe it is, but it was all setup to happen automatically in the end and was a non issue.
NO MANS LAND
This of course, is only the first part of the texturing process. I'm creating this area as it was in the middle of World War 1, which means a giant strip of war torn and heavily cratered no mans land is going to cut right through the middle of it. I'm still working out a few possible ways to create that and I'll cover it here when the time comes.
The main limitation of my current workflow is that once I decide upon a particular range of settings for the substance, there is a huge amount of processing to get all the renders out for all the tiles. I'm currently just working on a single i5, 32GBram pc with a 1060 GPU. There is however no reason this could not be automated more and then sent to the cloud to be processed.
I plan to implement the no mans land battle damage textures and objects in a way that leaves them to be paintable and editable once in Unreal Engine.
An early Quixel mixer test for a no mans land texture
SUMMARY
That covers the key aspects of the new texturing workflow. It turned into a bit of a science project, but it was really interesting to see how far I could push things, at such a scale. I think it's pretty cool that there is not a single repeating texture on the entire terrain. Something else that was interesting was that I was trying to design a texture as it would be seen from no closer than 500m-1km away. It was all about the macro details, not the micro details. Each single tile is a 25kmx25km area, and there are 64 of them, all entirely unique.
As I was working through all of this I kept wondering if I was spending far too much time on it. I possibly was, worrying about details that might not be very important. It's not even an area of the game that the player will directly interact with and you could argue that it's just the basis of an elaborate skybox. However, it was a great test of perseverance. There were really not that many issues that cropped up that I didn't see ahead of time which was also reassuring. Lastly, when you combine this with Truesky, the plugin I'm using for clouds and dynamic sky lighting, I now have a huge portion of my entire games environment completed. I have a space that I can use as a sandbox for fleshing out more gameplay stuff.
KEEP AN EYE OUT FOR PART 2 VERY SOON, WHERE I'LL SHOW HOW IT ALL COMES TOGETHER IN ENGINE.