Surface Shading
Fake Rim-Lighting (& other related effects)


return to main index




Creating Fake Lighting Effects

A surface shader generally calculates the apparent color of a surface (Ci) by combining the true color (Cs) and opacity (Os) of a surface with the various effects of the illumination a surface receives. This tutorial shows how it is possible to create the illusion of soft rim lighting without the use of any light sources.



Basic Code

To keep the code developed in this tutorial as simple as possible the shader shown below does not calculate the true values of the ambient, diffuse and specular lighting components. Instead, it will apply a fake (diffuse) rim lighting effect based on the angle between the viewing vector and the surface normal.

We can begin with the minimalist source code shown below - its effect on a sphere is shown in Fig 1. The RIB file used as the basis for the experiments in this tutorial is shown in listing 2.


Listing 1

surface
rim(float	Kd = 1)
{
normal	nf, n;
color	diffusecolor;

/* STEP 1 - make a copy of the surface 
            normal one unit in length */
n = normalize(N);
nf = faceforward(n, I);

/* STEP 2 - set the apparent surface opacity */
Oi = Os;

/* STEP 3 - set the fake diffuse "lighting" */
diffusecolor = 1;

/* STEP 4 - calculate the apparent surface color */
 Ci = Oi * Cs * diffusecolor;
}


Listing 2

Display "rim_tester" "framebuffer" "rgb"
Format 240 240 1
Projection "perspective" "fov" 40
ShadingRate 1

Translate  0 0 4
Rotate -30 1 0 0
Rotate 0   0 1 0
Scale 1 1 -1

WorldBegin
    ReverseOrientation

    TransformBegin
        Surface "rim"
                "float Kd" 1
        Sphere 1 -1 1 360
    TransformEnd
WorldEnd
rim1

Fig 1



Using the Viewing Vector and Surface Normal

The following illustration shows how the angle between the surface normal and light-of-sight of the camera ie. the viewing vector, changes from 0.0 degrees at the center of the object (approximated by location A) to 90.0 degrees at the rim (location B).



dot product


The apparent brightness of a surface can be based on the way this angle (shown in blue) changes across an object. A convenient method of calculating this angle is via the vector multiplication known as the dot product (also called the scalar or inner product) ie.


vector i = normalize(-I);
diffusecolor = nf.i;

The snippet of code shown above uses normalized copies of the surface normal (nf) and the (reversed) viewing vector (i). The dot product yields the cosine of the angle between the two vectors. The shading effect is shown in fig 2. Inverting the values by subtracting the dot product from 1.0 gives the shading effect shown in fig 3.


vector i = normalize(-I);
diffusecolor = 1 - nf.i;

The important point to note is that because the normal used in these calculations has been forced to face the camera, and hence we are only dealing with angles between 0.0 and 90 degrees, the cosines of this range of angles, calculated by the dot product, is in the rangle 0.0 to 1.0. In other words we are applying a fake lighting effect.

rim lighting 1

Fig 2


rim lighting 2

Fig 3




Controlling the Width of the Rim Effect

Naturally, the shader should allow an artist to control the width of the rim effect. By providing an instance variable, say, rim_width we can use the smoothstep() function to modify the range of values across a surface. For example,


float dot = 1 - nf.i;
diffusecolor = smoothstep(1.0 - rim_width, 1.0, dot);

Using a rim_width of 0.6 gives the effect shown in fig 4.

An interesting effect, fig 5, can also be obtained by using the dot product to vary the opacity of a surface rather than it color - refer to listing 3.


float dot = 1 - nf.i;
Oi = 1 - smoothstep(1.0 - rim_width, 1.0, dot);

Fig 5


Listing 3

surface
rim(float   Kd = 1, 
            rim_width = 0.2)
{
normal	nf, n;
color	diffusecolor;
  
/* STEP 1 - make a copy of the surface 
            normal one unit in length */
n = normalize(N);
nf = faceforward(n, I);
  
/* STEP 2 - set the apparent surface opacity */
vector i = normalize(-I);
float  dot = 1 - i.nf;
Oi = smoothstep(1 - rim_width, 1.0, dot);
  
/* STEP 3 - set diffusecolor to white */
diffusecolor = 1;
   
/* STEP 4 - calculate the apparent surface color */
 Ci = Oi * Cs * diffusecolor;
}
rim lighting 3

Fig 4



Fake Lighting Direction

The chair in fig 6 has been rendered using a modified version of the shader - listing 4. Notice that step 2 consists of two parts. In step 2.1 a uniform value of Oi is obtained. In step 2.2 Oi is scaled by the dot product of the surface normal and instance variable direction - this provides an illusion of a (fake) lighting direction.


Listing 4

surface
rim(float   Kd = 1, 
            rim_width = 0.2;
     vector direction = vector(0,-1,0))
{
normal	nf, n;
color	diffusecolor;
  
/* STEP 1 - make a copy of the surface 
            normal one unit in length */
n = normalize(N);
nf = faceforward(n, I);
  
/* STEP 2.1 - set the apparent surface opacity */
vector i = normalize(-I);
float  dot = 1 - i.nf;
Oi = smoothstep(1 - rim_width, 1.0, dot);
  
/* STEP 2.2 - modify the opacity by "direction" */
dot = normalize(direction).n;
Oi *= smoothstep(1 - rim_width, 1.0, dot);
  
/* STEP 3 - set diffusecolor to white */
diffusecolor = 1;
   
/* STEP 4 - calculate the apparent surface color */
 Ci = Oi * Cs * diffusecolor;
}

chair2

Fig 6


© 2002-5 Malcolm Kesson. All rights reserved.