How I made a landscape material

Here’s what I needed my landscape material to be:

  1. Procedurally paint itself (in my case - stone on the slopes, grass on the plains, and pebble on the shoreline).
  2. Still allow some handpainting (for riverbeds and around roads).
  3. Allow grass output.
  4. Deal with texture tiling.

[HR][/HR]

[HR][/HR]

Let’s take a look at the result before we go on analyzing the material itself:

Bird’s eye view:

Mind you, the material is pretty straightforward and how good it looks is going to depend on the heightmap you’re using to generate terrain. I made mine in WorldMachine, rendered it at 4k**+1** resolution (4097x4097), saved as R16 and imported to Unreal Engine.

Next post - analysis of the material.

When I said my material was pretty straightforward, I wasn’t joking. I decided to mask the slopes (R), plains (G), shores (B), so I could reuse that information later (gives most control).

I masked the slopes with the same method I used in the waterfall shader I’m working on:

636c6870746c8f16ffc236bc7b9b57edb1d57bdd.jpeg

Used DOT function to compare the VertexNormalWS to the Z vector (Constant3Vector 0,0,1). This paints your vector in a gradient representing the angle of the vector compared to Z axis. So, the rest of the math is to confine the gradient to the transition from slope to plain (slope starts at .7). Lerp and clamp it (turns out I was right to clamp it in the end, as no clamp would add weird colors to your textures.) Oneminus node is to reverse the gradient (from “not exactly logical, but seems to work better that way” things).

Now let’s mask the height. It’s even easier:

fa7519acf2b96d80c216f26a05ef6eae900f43a1.jpeg

We take the Z (B) component from the AbsoluteWorldPosition, divide by a number I found to be working, 128 in my case, sharpen the transition with CheapContrast, invert the gradient and clamp it between 0 and 1 (weird colors).

Now let’s blend the masks together:

04f8266c4956afd3def48b09c84c6b122a214baa.jpeg

The order is important. See, the slopes on the shoreline should still be Rock. So first we blend the grass with pebble (G with B via HeightBlend), then blend the result with R (SlopeBlend).

You can already plug the result of the final lerp to the BaseColor input of your material to see it colorize your landscape properly. Now let’s extract the masks for easier usability:

All the inputs are plugged to the output of the final lerp of the previous screenshot.

At this point the material is already functional; the masks we got can be used to paint the material with your textures. Next post - fine-tuning the material.

Before we move forward, here’s a couple of things you should know about texturing a landscape. Landscapes do not use the common coordinate system we use for meshes (TextureCoordinates). So, to use a non-tiling texture on a landscape, we need the LandscapeCoordinates node. Set the mapping scale to the resolution of your texture, leave the rest of the controls alone.

As for tiling textures, it’s better to use WorldSpace coordinates. The node to achieve this is WorldAlignedTexture. It uses a Texture Object (can convert your texture samples to Texture Object right-clicking the TextureSample node and selecting Convert to TextureObject) as an input. Also, you need to use the TextureSize input to specify the size of your texture in WorldSpace units. And the Export Float4 input if you’re using the texture Alpha channel for Roughness.

To blend the normal maps, you’ll need to use the WorldAlignedNormal node. It’s similar to WorldAlignedTexture, except you only need the TextureObject and TextureSize inputs.

To clean up my material, I used Material Functions for my textures. The function structure is similar for all 3 layers, so I’ll post only one. The difference is - I divided the TextureSize inputs by 1.3 (LargeSize) and 2 (SmallSize) for Grass and Pebble materials, and didn’t divide for the Rock, because I needed smaller textures for the first two. So I’ll post only the grass Material Function. Here’s the outputs for the texture:

b4057c20e11d720ef55863ca8ce3bbed26f8d388.jpeg

And the normals:

37522a70a97a6117ab514184d32f0f9b91574db8.jpeg

I did not do the blending inside the material function, because the Macro Variation mask was the same for all of them and I did not want to add the material texture count.

Next post - getting rid of the tiling.

I learned the four methods used together to get rid of the tiling from the materials provided by Epic Games (starter content). First I’ll show the macro variation mask I used in my material, then describe the methods:

b1ed7afb69c6a6ae01636852c2867e5aa5acae0f.jpeg

So, the four methods are:

  1. Multiply BaseColor after blending the layers with lowered contrast MacroVariation mask.
  2. Blend LargeSize and SmallSize textures using the higher contrast MacroVariation mask.
  3. Fade to an average color in distance (the result of the first step is preserved, as it is done later)
  4. Fade the normal to a flat normal map (Constant3Vector 0,0,1) in the distance.

For the last two steps, here’s a gradient mask to fade the texture to flat color/normal map to flat normal:

51cd37c5618da085201665a0d063b7fe30a4caa2.jpeg

The perlin noise texture is used not to get a too uniform fade.

How do we get the flat color we need to fade into? Here’s an online tool for that, you upload your texture and it analyzes the colors used in it, also gives you the average and medium colors for your texture in RGB:

http://mkweb.bcgsc.ca/color_summarizer/

Next step - implementing these methods and finalizing the material.

Ok, there’s some point at which a complex material’s shader network starts looking really ugly. In our case, this is that point. Warning - the incoming screenshots will be VERY, VERY messy. I’ll do my best to explain what’s going on there - not really complicated, actually. I’ll post the Grass layer again; we get the other layers in a similar way:

We had 6 outputs in our material function: Albedo Small/Large, Roughness Small/Large, Normal Small/Large. So we just lerp our Small/Large pairs together using the high contrast macro variation mask. Now we connect the Albedo’s lerp output to another lerp node, connect the alpha to the distance fade mask, the B output - to the average color. I used the last lerp (and plugged the Base Color into Specular) to match the colors of the landscape and the grass. If you don’t have the same problem, you can skip it.

Repeat for the other two layers, reuse parametric nodes. We’re not fading the normals here as, unlike the average color, the flat normal is the same for all of them and can be done after blending.

Not to overburden the screenshot with text, I’ll color code the inputs - R for Rock, G for Grass, B for Pebble:

106e85ae8e420880979dacfbfafd6ee1ad01bbd4.jpeg

So, first we blend the Grass (G) maps with the Pebble (B) maps using the Dirt (B) mask we got in the beginning. Then, we blend the result with the Cliff (R) maps using the Cliff (R) mask. We still need the Grass (G) mask for grass output.

Now, let’s finalize the texture outputs and plug them into our material inputs:

3ee18b335676523f28485f8fb658a04e02871532.jpeg

The LayerBlend nodes are not used right now; I’ll write about using them when I learn how to remove foliage when painting, in our case, the Road layer. The Global Normal is a big 4k normal map I got from WorldMachine; I’m using it to make the HeightMap look nicer.

So, all we have to do to wrap up our material is to get the grass output working!

470b3c5bdee5d87efd3c5cc43328ba4a9a6fb329.jpeg

You have to specify the Landscape Grass Type asset to be used with the Grass layer. It’s easy to create and edit one - if you need a tutorial on that one too, ask in comments.

Thanks for reading this tutorial, hope it helped you. If you have requests/comments/additions to the tutorial, you’re welcome to add them in comments. If you use the material in your project, a screenshot added to this topic would be appreciated. Have fun.

nice material. i make mine with 2/3 diffuse and normals. then connect them with landscape coords and landscape blend. :wink:

Care to share a screenshot?

Hey I’m struggling a bit with finding some of these nodes, is there an upload to the finished file? especially the outside nodes that do the in/out from this section…

The nods you can’t find are either Function Input of Function Output. I just named them.

Got it thanks dude, working great!

Been making one of these myself lately. It’s a lot of fun! Thanks for the share - will cross reference it with my own and see what I can improve on :slight_smile:

Me and joplin66 highly improved on the original idea, sadly I caught a cold. When I feel better, I’ll update the tutorial with the new material.

Looking forward for the updated version! Hope you get better soon! @. Going to look in to this from the start now :slight_smile:

This is very cool and exactly what I was trying to achieve. It took a while to put together; even reassembled your screenshots into one large image :D. Some things I have noticed though: all my textures have been washed out, more resembling the macro texture than the actual textures. Also, it seems the culling on the grass doesn’t change regardless of adjustments. Maybe I still have something wrong. Really looking forward to an update on this!

Very nice tutorial

Very cool tutorial. Just starting out with Unreal and this is just great.

Hey, thank for this tutorial, but i’m very noob with materials and i have a problem with material function, can you explain me why my foliage MF output is “full color” (sorry, i don’t know how to explain it in english) and not like yours?

SOLVED:

I was set preview value of input nodes to (1,1,1,1), tried with a value like (1000,1000,1000,1) and is better.

[spoiler]


[/spoiler]

Sorry to semi necro this but Does anyone have an over view of the material graph? Having a hard time figuring out where all the lines go.

Everyone thanks for the interest. Sorry for no updates for a while; it was because of a medical condition.

I promise to revisit this tutorial soon in larger detail, most likely with a video version of it. I will try to answer all the questions asked here.

I hope you’ve fully recovered!