Thilo Seifert explained how he simulated a landscape with a multi-channel river in Houdini and UE4.
In case you missed it
Learn more about river simulation
Hi, my name is Thilo Seifert, I'm a 3D Artist.
My first contact with 3D occurred when I was 11 or 12 and since then I've been fascinated by the boundless possibilities of creation and limitless depth of technology to dive in. Due to the lack of the universities that offered education in 3D/VFX fields, I studied Interaction Design which covered the interface design. This turned out very helpful for me because I have learned not only the design fundamentals but also the UX rules and methodology that were useful in my later career as a VR developer. During my studies, I also had the first contact with game engines, especially Unreal Engine.
For my bachelor thesis, my team and I made an Archviz tool prototype called “Consight” for which we got an Unreal Dev Grant. It also brought me a job in the Architecture Visualization industry where I worked for three years.
Now I work as a Technical Artist at TimeRide. I've been with the team for three years.
During our last project at TimeRide, I was in charge of creating landscapes, especially unregulated and braided rivers. From there, my personal project Braided started - I wanted to know how far I could go in terms of realism without the technical boundaries of VR. What I tried to create was a believable landscape made with the procedural techniques I have learned before.
Riverbed & Banks
Because of the huge territory (32 km²), I decided to work as procedurally as possible. I wanted to find a fast iterative approach for the landscapes and the rivers in particular.
Houdini has a feature called HeightFields that allows using 2D volumes for the creation of terrain. I had some experience with it previously, but unlike with the other projects I did before, I didn't want to split the landscape into multiple objects. Splitting landscapes up into different tiles is a clever way to save on simulation time, but it's a bit of a struggle to get all that heightmaps together, especially if you are working on the project in spare time. Like other terrain generation tools, Houdini also allows you to simulate erosion which is great, but for my case, I wanted a more direct method without spending hours on simulation. Luckily, I found a way to generate main rivers with a spline and “auto-creation” of side channels in Houdini.
To explain the main approach, I created a simplified river:
I created a simple spline with a sweep for the main river.
Then, I took the previous mask, distorted it multiple times and recombined the results into one mask. Because all the side channels are issued from the main river, we can rearrange the spline and the channels will follow. This is the base of the whole process.
Now, we are going to build initial masks for riverbed and banks. The recently created mask (see the previous picture) is the riverbed; it will later have the textures for the underwater surface. For the sandbanks, I distorted, blurred, and expanded the riverbed masks a bit. At this point, the whole surface is still flat, but with the two generated masks we can remap the landscape in its height.
In this step, I used HeightField's “FlowField” function to generate additional tiny arms of water. For this, a bit of erosion is helpful (only 3 frames) to let the algorithm flow. In the next step, I remapped the whole landscape to 0 and only used the gained flowmap for the next steps. This sounds a bit weird, but this was the best way for me to control the height/depth of the river that was added later. It was really important because the mesh I used for the water is just a simple plane.
Finally, I recreated the masks for the riverbed and sandbanks. The result is similar to the one without the FlowField but with a lot more details.
Thanks to the procedural setup, it's very easy to change the whole river with just a few clicks.
For the placement of the trees, I tried different techniques and ended up with a mixture of Procedural Foliage Spawner in UE4 and manual placement by hand (foliage painting). The Procedural Foliage Spawner delivers good results, but it's a bit of trial and error. For me, it was the best way to keep the volume very small at the beginning in order to reduce the simulation time and then increase it when the result met my needs.
For the landscape, I made a slope-dependent material that blends between the layers of grass, mud, rock, and snow. To have more control, I added a function that allows me to paint in the individual layers. I used five layers via the landscape layer function:
- Grass (this layer is fully procedural and blends all materials via the slope of the landscape)
- Dirt (this layer is for painting in additional dirt)
- Sandbank (the information for this layer comes from Houdini mask)
- Riverbed (the information for this layer comes from Houdini mask)
- NoSnow (with this layer, I was able to paint some areas without snow)
For the base layer, I blended the Rock, Mud, and Grass materials via the “WorldAlignedBlend” function which works pretty well. In my landscape, however, I had a lot of areas without a large slope variety which resulted in all the flat areas being grass. To solve this, I made an extra layer with the blended rock and mud functions as input to paint them independently.
The snow also blends on top of the base layer depending on the slope, but I only blended the base color and roughness values to get the Normal variation from the underlying materials. I used 2 different noise masks to break up the edge of the slope masks to make the snow transition more believable. Snow is the only material that got displacement because of the large camera distance in my shots. The mask for the displacement is the same as for other parameters but I used the Alpha output of the WorldAlignedBlend function since vertexNormalWS doesn’t work with displacement.
This is an example of the functions for the individual materials (in this case, Rock). I also used WorldAlingedBlend combined with noise to blend the different rock textures.
The mesh of the river is just a huge plane with a Translucent material for which I used different techniques to fake the Volume, Depth, Foam, and movement of the water.
The material is based on the capability to blend between the shallow and the deep waters implemented by the use of the Scene Depth and the Pixel Depth. I also used this method for the opacity to smoothly blend the edges of the river. Since I had translucency only for the edges, I needed a way to make the rest of the river translucent, especially for the shallow parts. For this, I used SceneTexture_Basecolor to blend the underlying landscape with distorted UVs by two-wave normal maps, to fake the refraction of the light.
To get more depth in the water, I simulated foggy or deeper parts in the river. I simply added a third color without the SceneTexture blend with a combination of the depth fade and a noise to some of the deep water. I called the parameter that controls the depth fade of the fog color “sss” (SubSurfaceScatter) - it is technically wrong, but it describes the look I wanted to achieve.
For the frozen border parts of the water, I used the same method. Here, I used two noise blended colors for ice and snow.
I added an animated foam texture to the deep water that supports the look of a big-size river and added some detail.
Lighting & Atmospheric Effects
For the sky, I used the trueSky plugin which delivers very good-looking out-of-the-box results that are easy to control. The lighting is completely dynamic. For the shadows, I used a combination of Cascade shadow maps and distance field shadows.
I used a lot of basic fog planes in this project for the ground fog, distance fog, and windblown snow on the edges of the rocks. Since I also wanted to get some fog in the areas the camera interacts with, I created multiple particle-based volume fog spawners. The particle system is very basic because all of the motion comes from the shader.
The main part here is to get the particle location and size and blend it with the SphereMask. I used a spherical texture subtracted by a fog/noise texture to break it up.
This particle-based fog looked good and fit my needs, but I had a huge landscape to fill, so I decided to make a custom “Heightfog” with the same technique.
I mapped a fog/noise texture in the red and green channels of the world position to determine where the fog should be in the horizontal space. I added the blue color of the world position to change the values in the vertical space separately.