Slope map tutorial

One of the most poweful texturing features of POV-Ray is normal perturbation (which is specified using the normal block of an object texture). With this feature it's possible to emulate small surface displacement in a very efficient way, without actually having to modify the actual surface (which often would increase the complexity of the object considerably, resulting in much slower renders).

Slope maps are used to define more precisely how the normal perturbation is generated from a specified pattern. Slope maps are a very powerful feature often dismissed by many.

As an example, let's create a simple scene with an object using normal perturbation:

camera { location <0, 10, -7>*1.4 look_at 0 angle 35 }
light_source
{ <100, 80, -30>, 1 area_light z*20, y*20, 12, 12 adaptive 0 }
plane { y, 0 pigment { rgb 1 } }

cylinder
{ 0, y, 4
  pigment { rgb <1, .9, .2> }
  finish { specular 1 }
  normal
  { wood 1
    rotate x*90
  }
}

Normal modifier example

By default the wood pattern uses a ramp wave (going from 0 to 1 and then back to 0) arranged in concentric circles, as we can see from the image.

By default POV-Ray simply takes the values of the pattern as they are in order to calculate the normal perturbation of the surface. However, using a slope_map we can more precisely define how these values are interpreted. For example, if we add this slope_map (the meaning of the values are explained later in this tutorial) to the normal block in the example above:

    slope_map
    { [0 <0, 0>]
      [.2 <1, 1>]
      [.2 <1, 0>]
      [.8 <1, 0>]
      [.8 <1, -1>]
      [1 <0, 0>]
    }

we get a much more interesting result:

Slope map example 1

We can also use a slope map to simply smooth out the original ramp wave pattern like this:

    slope_map
    { [0 <0, 0>]
      [.5 <.5, 1>]
      [1 <1, 0>]
    }

Slope map example 2

Slopes, what are they?

Mathematically speaking the slope of a curve (also called gradient) at a certain point is the tan() of the angle of the tangent line of that curve at that point. In other words, it's the amount of change of the vertical coordinate with respect to the change of the horizontal coordinate.

In a more colloquial way, the slope of a completely horizontal part of the curve is 0. The slope of a 45-degree line is 1 (because for each unit in the horizontal direction the line goes up by the same amount). Lines between 0 and 45 degrees have corresponding slopes between 0 and 1 (the relation between them is not linear, though, but one usually doesn't have to worry about that). Lines with an angle of over 45 degrees have correspondently slopes increasingly larger than 1 (a line of 90 degrees has an infinite slope).

Usually when defining slope maps it's enough to keep between slopes of 0 and 1, even though higher slopes are sometimes useful too to get steeper changes. Usually it's enough to think that a slope of 0 means a horizontal part of the curve while a slope of 1 means a 45-degree steep part of the curve (and slopes between 0 and 1 correspond to degrees between 0 and 45 respectively).

A slope can be negative too. A negative slope simply means that the curve is going down instead of going up.

The following figure shows some basic slopes in a curve (note that the slope values are only approximate):

Slopes in a curve

Syntax of a slope map

In the exact same way as for example a color_map assigns colors to pattern values, a slope_map assign slopes to pattern values. If you are fluent in defining color maps for a pattern, defining a slope map shouldn't be any more difficult.

Each entry in a slope map takes two values: The "displacement" of the surface (although one should remember that this displacement is only simulated, not real) and the slope of the surface at that point.

You can think of the first parameter as an "altitude" value which tells how much the surface (in relative terms) is displaced from its original location. Usually values between 0 and 1 are used for this. You can think of 0 meaning that the surface is not displaced and 1 as the surface having maximum displacement (outwards).

Let's examine the slope map we used to "smooth out" the wood pattern at the beginning of this tutorial:

    slope_map
    { [0 <0, 0>]
      [.5 <.5, 1>]
      [1 <1, 0>]
    }

This means:

When the pattern is linear (as the wood pattern is), this kind of slope map corresponds approximately to a half sine wave. Since the wood pattern uses a ramp wave (ie. after going from 0 to 1 it then goes from 1 to 0), the result is basically a complete (approximate) sine wave.

As with a color map, all the values in between are interpolated and that's why we get a smooth transition between these values.

Examples of slope maps

As we saw in the first slope map example in this tutorial, it is possible to create sharp transitions, not just smooth ones. This is achieved in the same way as how sharp transitions are achieved with color maps: By repeating the same pattern value. Here is an example:

    slope_map
    { [0 <0, 1>]
      [.5 <1, 1>]
      [.5 <1, -.3>]
      [1 <.7, -.3>]
    }

Slope map example 3

There's a sharp transition at the pattern value 0.5, where the surface goes from slope 1 to slope -0.3 (ie. from going strongly upwards to going slightly downwards). Due to how the wood pattern repeats itself, there are also sharp transitions at the pattern values 0 and 1.

We can combine sharp and smooth transitions for nice effects. For example, this simple slope map achieves a nice result:

    slope_map
    { [0 <0, 1>]
      [1 <1, 0>]
    }

Slope map example 4

One application where slope maps are really useful is when creating tiled floors. When the tiles on a floor are not too close to the camera and there is a very large amount of tiles, instead of creating hundreds or thousands of individual tile objects, it may be more efficient to simply create a normal pattern which emulates the tiles.

This example shows how to create a floor made of wooden "planks":

camera { location <2, 10, -12>*.5 look_at 0 angle 35 }
light_source
{ <100, 150, 0>, 1 area_light z*40, y*40, 12, 12 adaptive 0 }
sphere { y*.5, .5 pigment { rgb x } finish { specular .5 } }

plane
{ y, 0
  pigment
  { wood color_map { [0 rgb <.9,.7,.3>][1 rgb <.8,.5,.2>] }
    turbulence .5
    scale <1, 1, 20>*.2
  }
  finish { specular 1 }
  normal
  { gradient x 1
    slope_map
    { [0 <0, 1>] // 0 height, strong slope up
      [.05 <1, 0>] // maximum height, horizontal
      [.95 <1, 0>] // maximum height, horizontal
      [1 <0, -1>] // 0 height, strong slope down
    }
  }
}

Slope map example 5

In this case a gradient pattern was used. Since the gradient pattern goes from 0 to 1 and then immediately back to 0, we have to mirror the slope map (around 0.5) in order to get a repetitive symmetric result.

In this example the slope map starts from 0 height and a strong slope up, and goes quickly to maximum height, where the surface is horizontal. Then there's a large horizontal area (from pattern value 0.5 to 0.95) after which the slope goes rapidly back down to 0 height and a strong slope down. (After this there's a sharp transition to the beginning due to the gradient pattern starting over.)

If we want square tiles instead of just "planks", we can achieve that by eg. using an average normal map like this:

  #declare TileNormal =
    normal
    { gradient x 2 // Double the strength because of the averaging
      slope_map
      { [0 <0, 1>] // 0 height, strong slope up
        [.05 <1, 0>] // maximum height, horizontal
        [.95 <1, 0>] // maximum height, horizontal
        [1 <0, -1>] // 0 height, strong slope down
      }
    }
  normal
  { average normal_map
    { [1 TileNormal]
      [1 TileNormal rotate y*90]
    }
  }

Slope map example 6

If we change the pigment of the plane a bit, we get a nice tiled floor:

  pigment
  { checker
    pigment { granite color_map { [0 rgb 1][1 rgb .9] } }
    pigment { granite color_map { [0 rgb .9][1 rgb .7] } }
  }

Slope map example 7

As you can see in the image, close to the camera it's more evident that the tiles are not truely three-dimensional (and that only a normal perturbation trick has been used), but farther away from the camera the effect is pretty convincing.