Displacement Shader

Displacement mapping is an alternative computer graphics technique in contrast to bump mapping, normal mapping, and parallax mapping, using a (procedural-) texture- or height map to cause an effect where the actual geometric position of points over the textured surface are displaced, often along the local surface normal, according to the value the texture function evaluates to at each point on the surface. It gives surfaces a great sense of depth and detail, permitting in particular self-occlusion, self-shadowing and silhouettes. (wikipedia)

Implementation in Renderman:


 displacement
 displace_test(float Km = 0.1,
                     freq1 = 5,
                     freq2 = 5,
                     phase1 = 0,
                     phase2 = 0)
 {
 // Initialization ---------------------------

 float	hump = 0;
 normal	n = normalize(N);

 // Calculating Displacement -----------------
 float	d = sqrt(pow(0.5-s,2)+pow(0.5-t,2));
 float	ang = (atan((t-0.5),(s-0.5)))/(PI);
 hump += sin (2 * PI * d * freq1 + phase1) 
       * sin (2* PI * freq2 * ang + phase2);
 P = P - n * hump * Km;

 // Recalcilating Normals --------------------

 N = calculatenormal(P);
 }

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, they are "pushed" or "pulled" in a direction that is parallel to the original surface normal of the micro-polygon. After displacing the micro-polygon the orientation of the local surface normal(N) is recalculated. (fundza)
Creating a displacement shader usually takes place in these steps:
1-Normalizing the Normal vector of each point and storing it in     a new variable.
2-Calculating the amount of displacement of each point.
3-Moving each point to its proper displaced position.
4-Recalculating the normals to get a proper lighting result.

Result:



This is the result of applying the above shader "displace_test" to a simple plygon.


Displacement shaders cannot accept any lighting or coloring information. But there are a few methods to use displacement shader in conjunction with a surface shader.


Displacement Hack!

 
 surface
 hack_test(float Ka = 1,
                 Kd = 0.5,        
                 Ks = 0.7,        
                 Km = 0.1,
                 foamMin = 0.4,
                 foamMax = 0.6,
                 roughness = 0.1;
           color hilitecolor = 1 )
 {
 // Calculating Displacement -----------------

 normal    n = normalize(N);
 float    hump = sin(s * 2 * PI * 3);
 P = P + n * hump * Km; 
 N = calculatenormal(P);
  
 // Coloration -------------------------------

 color surfcolor = color(0.686,0.701,0.976);
 float blend = smoothstep(foamMin, foamMax, hump);
 surfcolor = mix(surfcolor, color(1,1,1), blend);
   
 // Lighting ---------------------------------

 color    ambientcolor, diffusecolor, speccolor;
   
 n = normalize(N);
 normal    nf = faceforward(n, I);
 ambientcolor = Ka * ambient();
 diffusecolor = Kd * diffuse(nf);
 vector    i = normalize(-I);
 speccolor = Ks * specular(nf, i, roughness) 
                * hilitecolor;
   
 // Outputs ----------------------------------

 Oi = Os;
 Ci = Oi * Cs * surfcolor 
     * (ambientcolor + diffusecolor + speccolor);
 }

In this method (which is more like a hack) the displacing is done "inside" the body of a surface shader. This method is not suggested though due to the problems it might cause. In this code there is a block for displacement before calculating coloration and lighting.

Shader Messaging


 displacement 
 fBmDisp (float   Km = 0.1,
                  freq = 5,
                  oct = 5,
                  lac = 5,
                  gain = 0.5;
          output varying float hump = 0)
 {
 normal   n = normalize(N);
 float    amp=1;
 point    pp = transform("shader", P);
 point    ps = pp * freq;
 uniform float i;
 
 for( i = 0; i < oct; i += 1 )
     {
     hump += amp * (2*(float noise(ps))-1);
     ps *= lac;
     amp *= gain;
     }
 P = P + n * hump * Km;
 N = calculatenormal(P);
 }


In this method the surface shader "imports" certain parameters sent out by the displacement shader and calculates lighting (and/or coloration) based on those parameters.
The output parameters should be declared in the initial declaration part of the displacement code:

output varying float hump = 0;


 surface 
 snow (float  Ka = 0.2,
              Kd = 0.8,
              Ks = 0.2,
              roughness = 0.1,
              snowLine = 0.5,
              snowBlend = 0.2,
              snowNoise = 0.1,
              snowFreq = 10;
       color  mountColor = 1,
              snowColor = 0,
              hilitecolor = 1)
 {
 // Initialization -----------------------------

 color    surfcolor = mountColor, ambientcolor, diffusecolor, speccolor;
 normal   n = normalize(N);
 normal   nf =faceforward(n,I;
   
 // Coloration ---------------------------------

 float    bumpiness;
 float    NewSnowLine = 2 * (snowLine - 0.5);
 if(displacement ("hump", bumpiness)==1){
   
     float jitter = snowNoise * (noise (P * snowFreq) - 0.5);
     if(bumpiness > (NewSnowLine-snowBlend-jitter)){
         float blend = smoothstep(NewSnowLine-snowBlend, NewSnowLine+snowBlend, bumpiness + jitter);
         surfcolor = mix(mountColor, snowColor, blend);
         }    
     }
 // Lighting -----------------------------------

 ambientcolor = Ka * ambient();
 diffusecolor = Kd * diffuse(nf);
 vector    i = normalize(-I);
 speccolor = Ks * specular(nf, i, roughness)* hilitecolor;
   
 // Output -------------------------------------

 Oi = Os;
 Ci = Oi * Cs * surfcolor * (ambientcolor + diffusecolor + speccolor);
 }

In the surface shader code, there should be an "if" statement to explore the existance of such parameter in the displacement shader; then assinging its value to another variable to do the rest of calculation:

if(displacement ("hump", bumpiness)==1){
...
...
}