RSL

Directional Light Source Shaders


main index


Topics

Introduction

This tutorial focuses on some of the issues relating to the writing of directional light source shaders ie. those that use the solar() shadeop. Some of the shaders presented here demonstrate how data can be passed to custom surface shaders for the purpose of producing secondary images - AOV's. The tutorial, "RenderMan: AOVs - Secondary Images", provides an overview of this topic.

Before continuing the reader should refer to the tutorial "Cutter Setup: RenderMan Shader Writing". It explains how to configure Cutter for shader writing. In particular, the section, "Cutter's Open Shader Source Facility", should be reviewed because it explains how Cutter can quickly access the source code of the shaders that ship with different RenderMan compliant rendering systems.

For a brief description of how to use a custom light shader with RenderMan Studio and Maya refer to the tutorial "RMS:Using Custom Light Source Shaders".


Parallel Illumination

Listing 1 shows a slightly modified version of Pixar's classic "distantlight" shader. It is an example of a light source intended to mimic sunlight.


Listing 1 (basic_distant.sl)


light
basic_distant(float  intensity = 1;
              color  lightcolor = 1;
              point  from = point "shader" (0,0,0);
              point  to = point "shader" (0,0,1);
)
{
vector direction = to - from;
solar(direction, 0.0) {
    Cl = intensity * lightcolor;
    }
}

A rib file that implements a simple scene (figure 1) that can be used to test the shader is given in listing 2.



Figure 1


The reader should ensure the paths shown in red "point" to the directories in which they store their custom shaders, textures and archive (pre-baked) rib files.


Listing 2 (basic_distant.rib)


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
  
Display "distant_test" "framebuffer" "rgba"
Format 400 250 1
Projection "perspective" "fov" 17
ShadingRate 2
  
Translate  0 -0.05 3
Rotate -30  1 0 0
Rotate  20  0 1 0
Scale 1 1 -1
WorldBegin
    TransformBegin
        LightSource "basic_distant" 2 "intensity" 1
                    "from" [0 1 0] "to" [0 0 0]
    TransformEnd
    
    Surface "plastic"
    AttributeBegin
        Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                "st" [0 0  0 1  1 1  1 0]
        Translate 0 0.35 0
        Sphere 0.15 -0.15 0.15 360
    AttributeEnd
WorldEnd


Setting a Fixed Direction

The first thing to note about the basic_distant shader is that it has two parameters that enable it's direction to be adjusted. Modern directional lights are written on the assumption they "face" toward negative Z. For example, the default direction of a Maya "directional" light is shown in figure 2.



Figure 2


In the case of Maya, the light's transform node (figure 3) enable its orientation to be adjusted. A modified version of the basic_distant shader is shown below. Note the "from" and "to" parameters have been removed and it's direction has been "hard wired" to be negative Z.


Listing 3


light
basic_distant(float  intensity = 1;
              color  lightcolor = 1;
            )
{
vector direction = vector "shader"(0, 0, -1);
solar(direction, 0.0) {
    Cl = intensity * lightcolor;
    }
}

The rib file, basic_distant.rib, should be edited so that two transformations provide the rib "equivalent" of the transformations shown in figure 3.

    TransformBegin
        Translate 0 1 0
        Rotate -90 1 0 0
        LightSource "basic_distant" 2 "intensity" 1
    TransformEnd


Figure 3


The "shader" Coordinate System

Note that in listings 1 & 2 the direction variable has been assigned xyz values in a pre-defined coordinate system named "shader" ie.

    vector direction = vector "shader" (0,0,-1);

However, had the vector been declared as,

    vector direction = vector(0,0,-1);

it would would have been, by default, defined in the camera coordinate system ie. "camera" space. In other words, the xyz values would be distances meaasured from the origin of the camera. So, what is "shader" space and why has it been used?

The "shader" coordinate system is identical to the coordinate system that was active when the light was instanced in the Rib file. Using "shader" space to define the direction vector, in effect, "parents" the light to the coordinate system that was created by the TransformBegin statement, and subsequently transformed by the Rotate and Translate commands that appear immediately before the LightSource statement. Consequently, only by defining the direction vector within the "shader" coordinate system will the light "respond" to the transformations intended to orientate it. As an experiment, the reader should reset the direction to,

    vector direction = vector(0,0,1);
or
    vector direction = vector "camera" (0,0,1);

The shader should be re-compiled and the scene re-rendered. Notice that no matter what values are specified for the Rotate and Translate statements in the Rib file the illumination of the scene remains constant. In particular, note the specular hilite on the sphere (figure 4) indicates the illumination is coming from the camera.



Figure 4


But it Does Not Work in Maya & Houdini!

If the reader assigns the shader from listing 3 to a directional light source in Maya, using RenderMan Studio, or in Houdini they will see the illumination is reversed - a downward facing light will illuminate the scene as if it were pointing upward! So why does the shader behave correctly when tested with the rib file shown in listing 2 but not when used in Maya or Houdini?

For reasons known only to the software engineers at Pixar and Side Effects their products insert, into the rib stream, a negative scaling on the z-axis immediately prior to instancing the light source shader. For example, the snippet shown below is from a rib file generated by RenderMan for Maya Pro (RenderMan Studio).

    TransformBegin 
        Attribute "identifier" "string name" ["directionalLightShape1"]
        Transform [ 1 0 -0 0  -0 0 -1 0  0 1 0 0  0 0 0 1 ]
        Scale 1 1 -1   # ???
        LightSource "basic_distant" "RenderManLight2" 
                    "float intensity" [1] "color lightcolor" [1 1 1]
    TransformEnd

As a consequence, for the shader to behave correcly with Maya or Houdini it is necessary to declare the direction vector as pointing toward positive Z.

    vector direction = vector "shader" (0,0,1);

This means the transform block containing the light in listing 2 must also apply a negative Z scaling.

    TransformBegin
        Rotate -90 1 0 0
        Scale 1 1 -1
        LightSource "basic_distant" 2 "intensity" 1
    TransformEnd

Thus proving that "two wrongs can make a right"!


The solar() Function

At the heart of a directional light source shader is the solar() function. The syntax of the function is rather strange because it has a code block and as such it looks more like a a looping statement, such while() or for(), rather than a "regular" RSL shadeop.

    solar(direction, 0.0) {
        Cl = intensity * lightcolor;
        }

There are two special global variables that can be accessed within the function's block of code. It is the responsibility of the shader to assign a color to Cl - the output light color. The second global, L, is a vector that specifies the direction from the light source to the surface it is illuminating - the input light direction. However, L is not used in the code sample shown above.

The value of the second parameter to solar is usually zero. The parameter specifies the deviation angle between the rays. Hence, a value of zero indicates the rays of light are parallel.


Listing 4


light
basic_distant(float  intensity = 1;
              color  lightcolor = 1;
              float  angle = 0;
            )
{
vector direction = vector "shader"(0,0,1);
solar(direction, radians(angle)) {
    Cl = intensity * lightcolor;
    }
}

In listing 4 an "angle" parameter has been added to the shader. The effect of different values of "angle" are shown in figure 5. For moderate values of "angle" the visual effect is to wrap the illumination around the sphere. However, for large values of "angle" the wrapping becomes noticably "distorted".



Figure 5
"angle" from left to right: 0, 20, 40, 60 and 90 degrees


Shadows

Listing 5 demonstrates the use of the shadow() function to read a value from a shadow map. Because the function returns 1.0 when the surface point is "in shadow" and 0.0 when "not in shadow" the return value is subtracted from 1.0 before modifying the output light color.


Listing 5 (shd_distant.sl)


light
shd_distant(float  intensity = 1;
            color  lightcolor = color(1,1,1);
            string shadowname = "";
            float  width = 1;
            float  samples = 16 )
{
vector direction = vector "shader" (0,0,1);
solar(direction, 0.0) {
    Cl = intensity * lightcolor;
    // Attenuate the output light color by the value
    // returned from a shadow (texture) map 
    if(shadowname != "")
        Cl *= 1 - shadow(shadowname, Ps, "samples", samples, "width", width);
    }
}

The rib file shown below can be used to generate the two depth maps required for the beauty pass render - listing 7.


Listing 6 (shadow_pass.rib)


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
  
PixelSamples 1 1
PixelFilter "box" 1 1
Hider "hidden" "jitter" [0]
Clipping 1 20
Format 512 512 1
Projection "orthographic"
ShadingRate 1
  
FrameBegin 1    
    Display "./shd_map_80.tex" "shadow" "z"    
    Translate  0 0 5
    Rotate -80 1 0 0
    Scale 1 1 -1
    WorldBegin
        AttributeBegin
            Translate 0 0.5 0
            Sphere 0.15 -0.15 0.15 360
        AttributeEnd
        AttributeBegin
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                    "st" [0 0  0 1  1 1  1 0]
        AttributeEnd
    WorldEnd
FrameEnd
FrameBegin 2    
    Display "./shd_map_100.tex" "shadow" "z"    
    Translate  0 0 5
    Rotate -100 1 0 0
    Scale 1 1 -1
    WorldBegin
        AttributeBegin
            Translate 0 0.35 0
            Sphere 0.15 -0.15 0.15 360
        AttributeEnd
        AttributeBegin
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                    "st" [0 0  0 1  1 1  1 0]
        AttributeEnd
    WorldEnd
FrameEnd


Listing 7 (beauty_pass.rib)


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
  
PixelSamples 8 8
Display "./shadow_test" "framebuffer" "rgba"
Format 400 250 1
Projection "perspective" "fov" 17
ShadingRate 1
  
Translate  0 -0.05 3
Rotate -30  1 0 0
Rotate  20  0 1 0
Scale 1 1 -1
WorldBegin
    TransformBegin
        Rotate -85 1 0 0
        Scale 1 1 -1
        LightSource "shd_distant" 1 "string shadowname" ["./shd_map_80.tex"]
                    "float intensity" 0.5 "float width" 4 "float samples" 32
    TransformEnd
    TransformBegin
        Rotate -95 1 0 0
        Scale 1 1 -1
        LightSource "shd_distant" 2 "string shadowname" ["./shd_map_100.tex"]
                    "float intensity" 0.5 "float width" 4 "float samples" 32
    TransformEnd
    
    Surface "plastic"
    AttributeBegin
        Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                "st" [0 0  0 1  1 1  1 0]
    AttributeEnd
    AttributeBegin
        Translate 0 0.35 0
        Sphere 0.15 -0.15 0.15 360
    AttributeEnd
WorldEnd

Listing 7 will generate the image shown below.



Figure 6


Rendering a Shadow AOV

The shd_distant shader provides an opportunity to explore how data calculated by a light source shader can be rendered into a secondary image. Although light shaders can have parameters declared as output variables they cannot be used directly as a source of data for secondary images. The data assigned to a light's output variables can, however, be read by a surface shader and then assigned to its output variables. Such outputs are known as AOVs - arbitrary output variables. The reader should refer to the tutorial "RenderMan: AOVs - Secondary Images" for background information on this topic.

Listing 8 is a slightly modified version of shd_distant. It has an extra parameter to enable the color of the shadow to be adjusted. It also has an output parameter that is assigned a value that represents the extent to which each micropolygon is in shadow.


Listing 8


light
shd_distant(    float  intensity = 1;
                color  lightcolor = color(1,1,1);
                color  shadowcolor = color(0,0,0);
               string  shadowname = "";
                float  width = 1;
                float  samples = 16;
 output varying float  __inshadow = 0)
{
vector direction = vector "shader" (0,0,1);
solar(direction, 0.0) {
    Cl = intensity * lightcolor;
    if(shadowname != "") {
        __inshadow = shadow(shadowname, Ps, "samples", samples, "width", width);
        Cl = mix(Cl, shadowcolor, __inshadow);
        }
    }
}

Because the shadow() function returns values in the range 0.0 to 1.0 it offers a convenient way to use the mix() function to set the output color of the light source. Listing 9 gives the source of a surface shader that queries the light source output variable __inshadow it, in turen, assigns the value to its own (AOV) output variable. Unlike the output variables of the light source, those of the surface shader can be used as sources of data (AOVs) for secondary images.


Listing 9 (diffuseAOV.sl)


surface
diffuseAOV(float Kd = 0.5;
 output varying float  _inshadow = 0)
{
normal  n = normalize(N);
color   diffusecolor = 0;
float   accum_inshadow = 0, inshadow;
color   accum_raw_Cl = 0, raw_Cl;
  
illuminance( P, n, PI/2 ) {
    if(lightsource("__inshadow", inshadow) == 1)
        accum_inshadow += inshadow;
    diffusecolor += Cl * normalize(L).n;
    }
// Clamp the _inshadow output
_inshadow = mix(0.0, 1.0, accum_inshadow);
Oi = Os;    
Ci = Oi * Cs * diffusecolor * Kd;
}

The illuminance() loop

Although the diffuseAOV surface shader calculates the diffuse (Lambertian) response of a surface to illumination, for simplicity, it ignores the ambient and specular components. Unlike the the sample surface shaders in the tutorial, "RSL: Writing Surface Shaders", it does not use the diffuse() function. Instead, it makes use of the illuminance() function to accumulate its shading effect.

The illuminance() function executes its block of code for each light source in a scene, if and only if, it determines the light is capable of contributing an illumination value. That determination is based on the surface position (P), surface orientation (N) and a cone within which it samples in-coming light - like a spot light but in "reverse". A cone angle of PI/2 ensures that each micropolygon samples a 180 degree hemisphere.


Figure 7 (shadow_test._inshadow.tif)


Within the illuminance block, the lightsource() function is used to query each light. If a light has a __inshadow variable its value is acculumated and finally assigned to the surface shaders own output variable. Listing 10 will render the secondary image shown in figure 7. The image contains an inverted mask of the overlapping shadows.


Listing 10


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
PixelSamples 8 8
DisplayChannel "float _inshadow" "quantize" [0 255 0 255] "dither" [0.5]
  
Display "./shadow_test.tif" "framebuffer" "rgba"
Display "+./shadow_test._inshadow.tif" "tiff" "_inshadow"
Format 400 250 1
Projection "perspective" "fov" 17
ShadingRate 1
  
Translate  0 -0.05 3
Rotate -30  1 0 0
Rotate  20  0 1 0
Scale 1 1 -1
WorldBegin
    TransformBegin
        Rotate -80 1 0 0
        Scale 1 1 -1
        LightSource "shd_distant" 1 "string shadowname" ["./shd_map_80.tex"]
                    "float intensity" 0.5 "float width" 4 "float samples" 32
    TransformEnd
    TransformBegin
        Rotate -100 1 0 0
        Scale 1 1 -1
        LightSource "shd_distant" 2 "string shadowname" ["./shd_map_100.tex"]
                    "float intensity" 0.5 "float width" 4 "float samples" 32
    TransformEnd
    
    Surface "diffuseAOV"
    AttributeBegin
        Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                "st" [0 0  0 1  1 1  1 0]
    AttributeEnd
    AttributeBegin
        Translate 0 0.35 0
        Sphere 0.15 -0.15 0.15 360
    AttributeEnd
WorldEnd

If the reader has Pixar's RenderMan Pro-Server and RenderMan Studio installed on their computer they can render both the beauty pass and the secondary images directly to an "it" (Image Tool) catalog. For example,

    Display "./shadow_test.tif" "it" "rgba"
    Display "+./shadow_test._inshadow" "it" "_inshadow"


Figure 8

Otherwise, it is necessary to render the secondary image as a image file, for example,

    Display "./shadow_test" "framebuffer" "rgba"
    Display "+./shadow_test._inshadow.tif" "tiff" "_inshadow"

Occlusion Directional Light

The next illustration shows the "soft shadow" effect of a directional light that outputs a color based on the use of the occlusion() shadeop. The QuickTime movie shown below consists of a 60 frame animation of an occlusion light source, inclined at an angle of 60 degrees, rotating 360 degrees around the Y-axis. The flickering of the occlusion is caused by the compression of the QuickTime movie.



Figure 9


The source code of the light source shader, and a rib file suitable for testing it, are shown in listings 11 and 12.


Listing 11 (occlusionlight.sl)


light
occlusionlight(float   intensity = 1,
                       samples = 64,
                       multiplier = 2,
                       coneangle = 90;
               color   lightcolor = 1)
{
vector direction = vector "shader"(0,0,1);
float  occ;
solar(direction, 0.0) {
    occ = 1 - occlusion(Ps, -direction, samples,
                              "coneangle", radians(coneangle));
    occ = pow(occ, multiplier);
    Cl = occ * intensity * lightcolor;
    }
}

The multiplier and coneangle parameters control the density and distribution of the occlusion. However, to avoid artifacts it may be necessary to adjust the trace "bias" in the rib file.


Listing 12 (occlusionlight.rib)


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
  
Display "occlusionlight" "it" "rgba"
Format 400 240 1
Projection "perspective" "fov" 15
ShadingRate 1
  
Translate  0 0 3
Rotate -30 1 0 0
Rotate   0 0 1 0
Scale 1 1 -1
WorldBegin
    Attribute "visibility" "trace" [1]
    LightSource "ambientlight" 1 "intensity" 0.15
    TransformBegin
        Rotate 0 0 1 0
        Rotate -60 1 0 0
        Scale 1 1 -1
        LightSource "occlusionlight" 2 "intensity" 0.8 
                    "multiplier" 3 "samples" 1024 "coneangle" 90
    TransformEnd    
    Surface "plastic" "Kd" 0.9 "Ks" 0    
    AttributeBegin
        Attribute "trace" "float bias" [0.001]
        Scale 0.3 0.3 0.3
        ReadArchive "pCube.rib"
    AttributeEnd    
    AttributeBegin
        Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
                "st" [0 0  0 1  1 1  1 0]
    AttributeEnd
WorldEnd


Rim Lighting

The tutorial "RSL:Edge Effects" demonstrated the use of the dot product to create fake rim lighting. Incidently, the same vector arithmetic is used by Maya's (HyperGraph) "facing ratio" node. Listing 9 (diffuseAOV.sl) also uses the dot product to scale the apparent intensity of the (Lambertan or diffuse) illumination received by a surface. However, the two vectors it uses are not those of the (reversed) view vector and the surface normal but the (reversed) light direction and the surface normal. In other words, what is referred to as Lambertian or diffuse illumination is the result of mulitplying the facing ratio of the light source by its color. Perhaps the illuminance loop used by the diffuseAOV shader should be re-written to make the use of the dot product more apparent ie.

    illuminance( P, n, PI/2 ) {
        float facing_ratio = normalize(L).n;
        diffusecolor += Cl * facing_ratio;
        }

The next shader (listing 13) modifies the output color of a light by the dot product (aka facing ratio) of the surface normal and the view vector. However, because this is implemented by a directional light source the rim effect can be controlled by a lighting artist rather than a shading artist!


Figure 10


Listing 13 (distantrimlight.sl)


light
distantrimlight(float   intensity = 1,
                        width = 0.6; 
              color     lightcolor = 1)
{
vector   direction = vector "shader"(0,0,1);
vector   i = normalize(-I);
normal   n = normalize(N);
normal   nf = faceforward(n, I, n);
float    dot = nf.i;
float    rim = smoothstep(1.0 - width, 1.0, 1 - dot);
  
solar(direction, 0.0) {
    Cl = intensity * lightcolor * rim;
    }
}


Light Wrapping

This section is based on the Siggraph 2002 paper, "Renderman on Film" by Rob Bredow (Sony Pictures Imageworks). In his paper the author demonstrates the principles of a shading technique they used in the production of the movie "Stuart Little 2". Their system gave the illusion that illumination from a light source appeared to wrap around the curved surfaces of their characters as if they were lit by an area light source.



Figure 11
Standard Illumination


Figure 12
Wrapped Illumination


Although the Siggraph paper explained their light-wrapping technique it did not provide the source code of their custom shaders. Consequently, the shaders given in listings 14 and 15 are, almost certainly, substantially different to those developed at Sony.


The Maths

Figure 13 shows four micropolygons, labeled a, b, c and d, on a sphere that is illuminated by a downward pointing directional light. The red lines represent vectors pointing toward the light source while the green lines represent the surface normals of each micropolygon. The dot product of the vectors and the normals at each of the sampled points is also given along with a "reminder" that the dot product represents the cosine of the angle between the vectors. For convenience, the angles are shown in degrees rather than in radians.


Figure 13


A "regular" surface shader ie. no light wrapping, would assign maximum illumination to the micropolygon at point a and minimum illumination to point b. The terminator between light and dark would occur along the "equator" of the sphere. However, a surface shader that wraps by, say, 45 degrees would "push" the terminator to point c.


The Role of the Surface Shader

Diffuse illumination is calculated by a surface shader either indirectly using the diffuse() shadeop or directly within an illuminance loop. For example,

    color diffusecolor = 0;
    illuminance( P, n, PI/2 ) {
        diffusecolor += Cl * normalize(L).n;
        }

In figure 13 the "light" vector (L) is represented by the normalized l vector. An illuminance loop usually samples a hemisphere (PI/2) and, as such, the loop skips those micropolygons that "face away" from a light source. Consequently, negative values of the dot product are avoided. To implement light wrapping, the illuminance loop must perform full spherical sampling (PI not PI/2). Figure 13 shows that for a wrap angle of 45 degrees the illumination must drop to 0.0 not at 'b' but instead at 'c'. The values, derived from the dot product, in the range 1.0 to -0.707, must be remapped to the range 1.0 to 0.0. The illuminance loop shown below performs the required remapping.

illuminance("wrapper", P, n, PI ) {
        lightsource("wrapAngle", wrap_angle);
        l = normalize(L);
        float  dot = n.l;
        float  minDot = cos(radians(90 + wrap_angle));
        float  clamped = clamp(dot, minDot , 1.0);
        float  illum = (clamped - minDot)/(1.0 - minDot);
        diffuseColor += Cl * illum;
        }
The Role of the Light Source Shader

Although light wrapping must be applied by a surface shader, it makes sense in a studio for the wrap_angle to be determined by the lighting artists ie. controlled by the light sources in a scene. Note that in listing 14 the illuminance loop queries the __category name of each light. Only those lights tagged as "wrapper" will be processed within the loop. The light shader in listing 15 has a "dummy" parameter named "wrapAngle". While it is not used by the shader it is present in the parameter list of the light so that it can be queried within the illuminance loop of the surface shader. This enables multiple instances of the wrapperlight to apply different "wrappings".


Listing 14 (wrappersurf.sl)


surface
wrappersurf(float Ka = 1,
                  Kd = 0.6,
                  Ks = 0.8,
                  roughness = 0.1)
{
normal  n = normalize(N);
vector  l, i = normalize(-I);
color   ambientcolor = ambient() * Ka;
color   diffuseColor = 0, specColor = 0;
float   wrap_angle, dot, minDot, clampedDot, illum;
  
illuminance("wrapper", P, n, PI ) {
        lightsource("wrapAngle", wrap_angle);
        l = normalize(L);
        dot = n.l;
        minDot = cos(radians(90 + wrap_angle));
        clampedDot = clamp(dot, minDot , 1.0);
        illum = (clampedDot - minDot)/(1.0 - minDot);
        diffuseColor += Cl * illum;
        }
        
// Handle the non-wrapping lights
illuminance("-wrapper", P, n, PI/2 ) {
       vector l = normalize(L);
       diffuseColor += Cl * l.n;
       specColor += Cl * specularbrdf(l, n, i, roughness);
       }
diffuseColor *= Kd;
specColor *= Ks;
  
Oi = Os;
Ci = Oi * Cs * (ambientcolor + diffuseColor + specColor);
}


Listing 15 (wrapperlight.sl)


light
wrapperlight(float  intensity = 1;
             color  lightcolor = 1;
             string  __category = "wrapper";
             float  wrapAngle = 0)
{
vector direction = vector "shader"(0,0,1);
solar(direction, 0.0) {
   Cl = intensity * lightcolor;
   }
}


Listing 16 (wrapper.rib)


Option "searchpath" "shader" "@:../shaders"
Option "searchpath" "texture" "../textures"
Option "searchpath" "archive" "../archives:Cutter_Help/templates/Rib"
  
Display "wrap_test" "framebuffer" "rgba"
Format 400 250 1
Projection "perspective" "fov" 17
ShadingRate 1
  
Translate  0 0 7
Rotate 0  1 0 0
Rotate 0  0 1 0
Scale 1 1 -1
Imager "background" "background" [1 1 1]
WorldBegin
    TransformBegin
        Rotate -90 1 0 0
        Scale 1 1 -1
        LightSource "wrapperlight" 1 "wrapAngle" 45
    TransformEnd
    TransformBegin
        LightSource "spotlight" 2 "intensity" 5 "from" [1 5 2] "to" [0 0 0]
    TransformEnd
    AttributeBegin
        Surface "wrappersurf" 
        Sphere 1 -1 1 360
    AttributeEnd
WorldEnd


Volumetric Fog Effects

This section introduces the use of a very simple volume shader that works in conjunction with a "tagged" custom directional light source. Strickly speaking, the volumetric fog effects shown in figure 14 do not require a custom light. However, assigning a __category parameter to the custom light enables instances of the shader to control the effects created by the volume shader. Some what like the techniques used in the previous section, the light source could pass (noise) values to the volume shader for the purpose, say, of making the fog look more interesting.



Figure 14


Immediately after a surface shader has determined the apparent color and opacity of a micropolygon a volume shader can modify the micropolygon's color in order to create the illusion that a scene is partially or fully filled with a medium such as water, fog or smoke. For example, the volume shader shown in the next listing steps along the viewing vector (I) from point P ie. the location of the micropolygon currently being shaded, towards the camera. At each step along the vector it calculates its current position (currP) so that the illuminance function can sample the light arriving at that location. An average of the total illumination accumulated along the I vector is then added to Ci.



Figure 15


Listing 17 (mkfog.sl)


volume mkfog (float step = 0.025)
{
float    totalDist = length(I);
point    currP;
float    stepCount = 1;
color    totalC = 0;
  
// Jitter the starting point
float     currDist = random() * step;
vector    deltaI = 1.0/totalDist * I;
  
while(currDist <= totalDist) {
    currP = P - currDist * deltaI;
    illuminance ("foglight", currP) {
        totalC += Cl;
        }
    currDist += step;
    stepCount += 1;
    }
Ci = Ci + totalC/stepCount;
}


The code for a light source that works in conjunction with the volume shader is shown next.


Listing 18 (distantFoglight.sl)


light distantFoglight( 
    string   shadowname = "";
    float    intensity = 1;
    color    lightcolor = 1; 
    uniform string __category = "foglight")
{
vector direction = vector "shader" (0,0,1);
  
solar(direction, 0.0) {
    Cl = intensity * lightcolor;
    if (shadowname != "")
        Cl *= (1 - shadow(shadowname, Ps, "samples", 64, 
                                        "swidth", 1, "twidth", 1));
    }
}


The two frame rib file that can be used to test the shaders is given next.


Listing 19


FrameBegin 1
    PixelSamples 1 1
    Hider "hidden" "jitter" [0]   
    Display "null" "null" "z"
    Display "+./distantshadow2.tex" "deepshad" "deepopacity" "string filter" ["box"] 
                                    "float[2] filterwidth" [1 1]
    Format 512 512 1
    Projection "orthographic"
    ShadingRate 1
  
    Translate  0 0 5
    Rotate -90 1 0 0
    Scale 1 1 -1
    WorldBegin
        AttributeBegin
            Translate 0 0.5 -0.5
            Rotate 90 1 0 0
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
        AttributeEnd
        AttributeBegin
            Translate 0 0.5 0
            Sphere 0.15 -0.15 0.15 360
        AttributeEnd
        AttributeBegin
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
        AttributeEnd
    WorldEnd
FrameEnd
FrameBegin 2
    Option "shadow" "float bias" [0.1]
    
    Display "./volume_test" "it" "rgba"
    Format 350 350 1
    Projection "perspective" "fov" 17
    ShadingRate 1
      
    Translate  0 -0.5 4
    Rotate -0.5  1 0 0
    Rotate  0  0 1 0
    Scale 1 1 -1
    WorldBegin
        Atmosphere "mkfog" "float step" 0.025
        TransformBegin
            Translate  0 0 0
            Rotate -90 1 0 0
            Scale 1 1 -1
            LightSource "distantFoglight" 2 "intensity" 0.35
                        "string shadowname" ["./distantshadow2.tex"]
        TransformEnd
        Surface "plastic" "Kd" 0.6
        AttributeBegin
            Translate 0 0.5 -0.5
            Rotate 90 1 0 0
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
        AttributeEnd        
        AttributeBegin
            Translate 0 0.5 0
            Sphere 0.15 -0.15 0.15 360
        AttributeEnd
        AttributeBegin
            Polygon "P" [-0.5 0 -0.5  -0.5 0 0.5  0.5 0 0.5  0.5 0 -0.5]
        AttributeEnd
    WorldEnd
FrameEnd



© 2002- Malcolm Kesson. All rights reserved.