Friday, August 27, 2010

Terrain - Multi-texturing

Alright, what we have now is directional light lit terrain with sharp textures and no distortion. Although green is my favorite color it is obvious that real terrain is not consistent regarding its surface, its texture. We can enhance the visual appearance of our terrain by using multiple textures.

We will cover different parts of our terrain with different textures and we will have intermediate parts where two textures should blend to each other, that is more than one texture will be applied to the same fragment which is called multi-texturing.

We will use three textures instead of one. To present the terrain with these different textures we use following basic concept:

  1. we create our own vertex format that contains a new Vector3 (float3) which contains weight values of textures for every vertex. 
  2. we define "somehow" those weights for every vertex at terrain initialization time
    (in LoadContent()) 
  3. in the pixel shader we sample all three textures and we use the weights to set final color i.e. color equals to weight.x*text1Color + weight.y*text2Color + weight.z*text3Color.
What we need is to figure out that "somehow" in step 2. i.e. to figure out how to determine which texture(s) to "use" at a vertex.

As a naive approach let's derive weights based on the y-coordinate of a vertex, that is, based on its height and define partitions where only one of the textures have one as weight and all the others have zero. Take a look at the picture to see the result.

Height-based textures with discreet weights

We can see that different textures are used at different heights but the result is clearly not very appealing because partitions are to edgy as texture changes without any transition.

Let's add some transition by converting partition border points to intervals and interpolating color values in that interval. For instance, if the border point was 50 (texture1 was applied if y<50 and texture2 was applied if y>=50) then convert it to an interval of [40,60] (texture1 is applied if y<40, texture2 is applied if y>=60 and for heights in between weights are linearly interpolated). Check out the result:

Height-based textures with interpolated weights
It's already quite neat but it's not perfect. Weights are interpolated now thus texture colors smoothly interpolate at texture changes. Few places look unreal, though. Look at the picture below for an example: there shouldn't be any grass on that steep cliff.

Result of pure height-based textures

To solve this we shouldn't consider only height. We should take the steepness in consideration as well and create weights following way (pseudo code):

for every vertex do {
     if (vertex is on a steep slope){
             use "ground" texture
     } else {
             choose between "grass" textures based on height

That is, when selecting a texture steepness has first and height second priority. To determine steepness at a vertex we ask our best friend for help: the normal of our vertex (its y-component) tells exactly how steep slope we are on. Check out the result on pictures below.

Note: we still blend between texture changes (in this case interpolating is a bit more trickier).

Steepness and height-based textures with interpolated weights (pic. 1)

Steepness and height-based textures with interpolated weights (pic. 2)

This is it. A terrain that is textured with multiple textures in a steepness and height sensitive way that gives a quite good looking and realistic texturing result.

As a bonus check out the video and the color-mapped terrain which visualizes more clearly how textures are selected. This time each color represents a different texture.

Color-mapped terrain
(shows how steepness and height influences texture selection)

Note: we have a noticeable, dropped frame rate. This is because texture samples are quite expensive and we take 9 per-pixel: 3 different textures * 3 different planes (triplanar mapping). We'll address this performance problem later and try not to optimize now anything. I want to have some frustum and/or occlusion based clipping system later anyway (my current 375x375 height-map ends up in a terrain consisting of 140.625 vertices and 280.498 triangles). Let's wait with any optimization attempt or forced design change (e.g. disabled triplanar mapping) until that.

1 comment:

  1. Hello, do you also offer the source code? Greetings from Austria