Writing Displacement Shaders


return to main index


contents

related pages


DISPLACEMENT SHADER ALGORITHM

Displacement shaders alter the smoothness of a surface, however, unlike bump mapping which mimics the appearance of bumpiness by reorientating surface normals, displacement shading genuinly effects the geometry of a surface. In the case of Pixars prman renderer, each object in a 3D scene is sub-divided into a fine mesh of micro-polygons after which, if a displacement shader has been assigned to an object, each micro-polygon is "pushed" or "pulled" in a direction that is parallel to the original surface normal of the micro-polygon.

After figuring out how much it should move a micro-polygon, a displacement shader concludes its part of the rendering process by recalculating the orientation of the local surface normal(N).





The following algorithm lists the four basic steps that a displacement shader must follow in order to set the new location(P) and orientation(N) of the point on the surface being shaded.


1

Make a copy of the surface normal(N) ensuring it is one unit in length.

2

Calculate an appropriate value for the displacement - what will be referred to in these notes as the "hump" factor!

3

Calculate a new position of the surface point "P" by moving it "along" the copy of the surface normal by an amount equal to "hump" scaled by the value of the instance variable "Km".

4

Recalculate the surface normal(N).


This page provides some sample code that will enable you to experiment with displacement shading techniques that use,

  • texture coordinates,
  • noise and turbulance,
  • sine values to create waves,
  • texture maps, and
  • antialiasing using the smoothstep function.

DISPLACEMENT SHADERS & GLOBAL VARIABLES

The following table lists the global variables accessible to a displacement shader. [List of global variables available to surface shaders].

VARIABLE
NAME

P
N
s, t
Ng
u,v
du, dv
dPdu,dPdv
I
E

VARIABLE
VALUE

surface position
surface geometric normal
surface texture coordinates
surface geometric normal
surface parameters
change in u, v across the surface
change in position with u and v
camera viewing direction
position of the camera


A BASIC SHADER

The experiments on displacement shading in this section are based the following "template" code. If you are using Cutter to edit your SL files you will see the syntax colored as shown below.


displacement test(float Km = 0.1)
{
float	 hump = 0;
normal	n;
  
/* STEP 1 - make a copy of the surface normal */
n = normalize(N);
  
/* STEP 2 - calculate the displacement */
hump = 0;
  
/* STEP 3 - assign the displacement to P */
 P = P - n * hump * Km;
  
/* STEP 4 - recalculate the surface normal */
N = calculatenormal(P);
}

As each shader is introduced new or modified code will be shown underlined.


USING TEXTURE COORDINATES

Although each part of a 3D object has an x, y and z coordinate, the surface of the object is two dimensional - in the same way that an atlas of the world is a 'flattened' 2D version of the curved surface of the planet. Locations on the surface of a globe or atlas are uniquely specified by their coordinates in latitude and longitude. In 3D computer graphics the position of a point on the surface of an object(not just a sphere) can be specified using its s and t texture coordinates.

In texture mapping the surface 's' and 't' coordinates of an object are used to find the color of the corresponding pixel in a texture image. The diagram below illustrates the relationship between the default texture coordinates of a cylinder and an image used for texture mapping.


When a shader is called(instanced) the global variables s and t store the specific texture coordinates of the surface point being shaded. The values of s and t can be used for a variety of purposes. For example, the following shader makes the displacement only when t is greater than 0.5.


displacement tstep(float Km = 0.1)
{
float	 hump = 0;
normal	n = normalize(N);
  
/* Displace the surface only when 
   't' is greater than 0.5 */
if(t > 0.5)
    hump = 1.0;
  
 P = P + n * hump * Km;
N = calculatenormal(P);
}

Modify the Displacement statement in the RIB file ie.

Displacement "tstep"


tstep Shader




IMAGE EMBOSSING

The function texture() enables a texture file to be read. The function can return either a float, a color or a point value. In the example shown below because its return value is assigned to a floating point variable the function is effectively reading the texture file as a series of grayscale values.

Note: explicitly specifying s and t as parameters to the 'texture()' function is optional.


displacement tex1(float Km = 0.1; 
                  string texname = "")
{
float	hump = 0;
normal	n = normalize(N);
  
/* set the displacement to the grayscale 
   value of the texture map */
if(texname != "")
    hump = float texture(texname, s, t);
  
 P = P - n * hump * Km;
N = calculatenormal(P);
}

As an experiment change the data type of 'hump' from a float to a point.

Modify the Displacement statement in the RIB file ie.

Displacement "tex1" "texname" ["your_image.tx"]

tex1 Shader




NOISE

For information about the use of the noise() function refer to,

using noise for surface shaders
shader tutorial - noise

displacement noise(float Km = 0.1)
{
float	hump;
normal	n = normalize(N);

/* Set the displacement using the noise function */
hump = noise(P);

 P = P - n * hump * Km;
N = calculatenormal(P);
}

Because the input value "P" references an xyz location on the surface of an object the noise function will generate 3D noise values. The "bumpiness" of the surface of an object to which this shader is applied depends on where in a 3D scene the object is located. The xyz coordinates of "P" are measured from the center of the camera - in other words "P" is defined in the camera coordinate system. Therefore, if the camera is moved the bumps will appear to "crawl" across the surface of the object!

To ensure the bumps move with the object ie. they are not effected by the camera, it is necessary to convert the "raw" xyz values of "P" into the coordinate system in which the object is located. Refer to Sticky 3D Noise to see how this is achieved.

Modify the Displacement statement in the RIB file ie.

Displacement "noise"

noise Shader



TURBULANCE

If noise() is used several times but with P multiplied by an ever increasing value the function will effectively generate higher and higher frequency noise. If the results of repeated calls to noise() are added together very interesting patterns can be created.

In the shader shown below a loop adds two "levels" of noise together. By changing the loop counter we could add any number of frequencies. The thing to notice about the loop is that P is multiplied (ie. scaled) by an increasing value ("totalFrequency") and the resulting noise value is likewise divided by an increasing value. The overall result is that the looping gives us noise values of increasing frequency but with decreasing amplitude.

But why does the abs() function subtract 0.5?

Subtracting the noise value from 0.5 ensures the displaced surface remains, on average, in its original location. The abs() function ensures that negative displacements are "converted" into positive displacements. Apply this shader to a surface but omit the abs() function and you will easily see the effect of forcing all the noisy displacements to be positive.


displacement turb (float  Km = 0.1)
{
float   hump = 0, totalFrequency = 1.2,
        crinckle = 4, i;
normal	n = normalize(N); 
   
for(i = 0; i < 2; i = i + 1)
    {
    hump = hump + abs(0.5 - noise(crinckle *
         totalFrequency * P))/totalFrequency;
    totalFrequency *= 1.2;
    }
	
 P = P - n * hump * Km;
N = calculatenormal(P);
}

Try using different values for those shown below in red. For example, reduce the increment for 'totalFrequency' to 1.1.

crinckle = 4;
for(i = 0; i < 2; i = i + 1)
    {
     hump = hump + abs(0.5 - noise(crinckle * 
         totalFrequency * P))/totalFrequency;
    totalFrequency *= 1.2;
    }

Modify the Displacement statement in the RIB file ie.

Displacement "turb"



turb Shader




MAKING WAVES

The following source code shows 'hump' being set to the value returned by the sine function. The function will generate one cycle of a sine wave when its parameter varies from 0 to 2PI(ie. 6.283185). Since 's' will be in the range 0 to 1 by factoring in 'numwaves' this shader can create any number of waves in the 's' direction.


displacement swave(float Km = 0.1, numwaves = 3)
{
 float   hump = sin(s  * 2 * PI * numwaves);
normal	n = normalize(N);
  
 P = P - n * hump * Km;
N = calculatenormal(P);
}

Several examples of the way the sine function can be used can be found at Tutorial: Expressions and Expressions Quick Reference

Modify the Displacement statement in the RIB file ie.Displacement "swave".



swave Shader




MAKING RIPPLES

The following diagram and source code show a sine wave being calculated according to the distance from a point on a surface, specified by 'a' and 'b'.




displacement ripple(float Km = 0.1, 
                          numwaves = 8,
                          a = 0.3, 
                          b = 0.25)
{ 
float sdist = s - a,
      tdist = t - b,
       dist = sqrt(sdist * sdist + tdist * tdist),
       hump = sin(dist  * 2 * PI * numwaves); 
normal	n = normalize(N);
    
 P = P - n * hump * Km;
N = calculatenormal(P);
}

Modify the Displacement statement in the RIB file ie.

Displacement "ripple"


ripple Shader




The second code sample allows the specification of the centers of two ripples. While it is possible to add more centers its clearly going to become increasingly tedious - suppose we require 50 separate ripple centers!


displacement tworipple(float Km = 0.1, 
                             numwaves = 8, 
                             a = 0.3, 
                             b = 0.25, 
                             c = 0.6, 
                             d = 0.6)
{ 
float sdist = s - a, 
      tdist = t - b,
       dist = sqrt(sdist * sdist + tdist * tdist),
       hump = sin(dist  * 2 * PI * numwaves); 
normal	n = normalize(N);
    
/* add the second ripple */
sdist = s - c;
tdist = t - d;
 dist = sqrt(sdist * sdist + tdist * tdist);
 hump = hump + sin(dist  * 2 * PI * numwaves);
  
 P = P - n * hump * Km;
N = calculatenormal(P);
}

tworipple Shader



© 2002-4 Malcolm Kesson. All rights reserved.