Volume shaders


Global Variable Type Represents
E point Camera position R
P point Ray origin R
I vector Ray direction R
Ci color Ray color RW
Oi color Ray opacity RW


A volume shader is the last shader called by the renderer for a cast camera ray. In it, you can change the output color and opacity of the last called surface shader, before they get imprinted on the pixel. Take note that in order to see a volume shader, you must put a surface in the background, and attach the volume shader to it via the RIB call Atmosphere [volume shader].

volume fog(
    float distance = 100,
          thickness = 1;
    color fogcolor = 1
)
{
    float findex = thickness * (1 - pow(3,-length(I) /
                   distance));

    Ci = mix( Ci, fogcolor, findex );
}


Ray marching

A widely used method of volume shading is the so-called ray marcher, in which you move along the camera ray direction in small steps and collect atmospheric texture values along the way. Naturally, the smaller your step distance, the finer and slower your rendering will be.

volume litsmoke(
    float stepsize = 0.2,
          thickness = 0.02;
    color smokecolor = 1
)
{
    point pos;
    vector Inorm;
    float Ilen, opa, tracklen;

    Ilen = length(I);
    Inorm = I/Ilen;
    opa = 0;
    tracklen = stepsize;

    while( (opa < 1) && (tracklen < Ilen) ) {
        pos = E + tracklen*Inorm;

        illuminance( pos, vector (0,1,0), PI )
            opa += comp(Cl,0) * thickness * stepsize;

        tracklen += stepsize;
    }

    Ci = mix( Ci, smokecolor, opa );
}

The illuminance function samples the lights that shine on the position pos, 180° (PI radians) around the direction (0, 1, 0); we use it here to look around in every step for light rays.

Next, a slightly more complex use of this technique to create a smoky region:

/* Note: This shader only works properly in BMRT. It should be
   applied to an inside-facing, single-sided sphere placed at
   the origin. You should also synchronize vradius with the
   sphere's radius */

volume volsmoke(
    float stepsize = 0.05,
          thickness = 0.7,
          texscale = 4,
          iteration = 7,
          vradius = 10;
    color cloudcolor = color (0.75, 0.8, 0.85)
)
{
    point pos;
    vector Inorm;
    float Pang, inlen, outlen, Ilen, opa, smk, count, clips[2];
    uniform float i, j;

    Ilen = length(I);

    if( length(vector E) > vradius ) /*camera outside sphere*/
    {
        Pang = acos(normalize(vector P) . normalize(I));
        /* sine rule */
        inlen = vradius * sin(PI - 2*Pang) / sin(Pang);
        outlen = Ilen - inlen;
    }
    else /*camera inside sphere*/
    {
        option( "Clipping", clips );
        outlen = clips[0];
    }

    opa = 0;
    Inorm = I/Ilen;
    count = 1;

    while( (opa < 1) && (outlen < Ilen) ) {
        pos = E + outlen*Inorm;
        smk = 0;
        for( i=1; i<iteration; i+=1 )
            smk += (0.5 - abs( float noise((pos/texscale + vector
                   (45.6, 100.3, 12.4)) * i) - 0.5 )) / (2 * i);
        smk = clamp( smk - 0.47, 0, 1 );
        opa += smk;

        outlen += stepsize;
        count += 1;
    }
    opa = thickness * opa / count;

    Oi = opa;
    Ci = mix( Ci, cloudcolor, opa );
}

As you might gather, ray-marching is slow. If the volume is the only thing you're rendering, you could tighten the rendering time by reducing PixelSamples to 1 (no antialiasing) and increasing the volume object's ShadingRate. Also, since the smoke is blurry, you might consider rendering the image in a smaller resolution, and then blowing it up later.

It's possible to extend the above shader to create self-shadowing. The idea is to trace a ray from the current sampling point to every light in the scene, and then ray-march along that ray to compute how much light penetrates the smoke to reach that point. You could also solidify the smoke into a virtual surface called a hypertexture. Both of these effects are very expensive to compute, and are somewhat advanced in flavour. So we'll leave them out for this class. If you're interested in these and other volume rendering techniques, you should check out the book Advanced Renderman – Creating CGI for Motion Pictures by Anthony Apodaca and Larry Gritz (pub. Morgan Kaufmann Publishers, ISBN 1-55860-618-1).