Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced_Renderman_Book[torrents.ru]

.pdf
Скачиваний:
1741
Добавлен:
30.05.2015
Размер:
38.84 Mб
Скачать

12 A Gallery of Procedural Shaders

Listing 12.4 (continued)

float plankindex = swhichplank + 13*twhichplank;

point Ppat = point(splank-0.5,height-0.01`tplank,tplank)

+ vector(1,5,10)* (vector cellnoise(swhichplank,twhichplank) - 0.5);

float wood = oaktexture (Ppat, dPshad, ringfreq, ringunevenness, grainfreq, ringnoise, ringnoisefreq,

trunkwobble, trunkwobblefreq, angularwobble, angularwobblefreq, ringy, grainy);

color Cwood = mix (Clightwood, Cdarkwood, wood);

Cwood = varyEach (Cwood, plankindex, varyhue, varysat, varylum); Cwood = mix (Cgroove, Cwood, inplank);

/* Displacement: the edges of the planks displace down a bit, as do * the grooves between planks.

*/

float edgedisp = smoothpulse (0, edgewidth, plankwidth-edgewidth, plankwidth, splank);

edgedisp *= smoothpulse (0, edgewidth, planklength-edgewidth, planklength, tplank);

normal Nf = faceforward (normalize(N), I);

float disp = -wood*divotdepth + groovedepth*(edgedisp-1); disp += varnishlump * filteredsnoise (Pshad*varnishlumpfreq,

dPshad*varnishlumpfreq); Nf = faceforward(Displace (Nf, "shader", disp, truedisp), I);

/* Illumination model

*Less specular in the grooves, more specular in the dark wood.

*/

float specadjusted = 1 + .3*wood - 0.8*(1-inplank);

Ci = MaterialShinyPlastic (Nf, Cwood, Ka, Kd, specadjusted*Ks, roughness, specadjusted*Kr, blur, eta, ENVPARAMS);

Oi = Ds; Ci *= Oi;

}

12.6 Smoke: A Volume Shader

There is only one important shader type that we have not yet covered: volume shaders. Just as each geometric primitive may have a surface and a displacement shader bound to it, so too may each primitive have an "atmosphere." Because atmosphere shaders bind to geometric primitives, just like surface or displacement shaders do, you must have an object in the background for the atmosphere shader to run. In other words, pixels with no geometry at all "behind" them will not run any atmosphere shaders.

315

12.6Smoke: A Volume Shader

2.1Global variables available inside volume shaders

point P

Position on the surface we are shading (at one end of

 

the incident ray).

vector I

The incident vector through the volume. For atmosphere

 

Shaders, I points from the camera (or viewing position) Toward P.

color Ci, Oi

The surface color and opacity of the viewing ray. Modifying

 

these two variables is the primary goal of a volume shader.

Listing 12.5 The fog volume shader implements a simple exponential fading with distance.

volume

fog (float distance = 1; color background = color(.5,.5,.5);)

{

float d = 1 - exp (-length(I)/distance); Ci = mix (Ci, background, d);

Oi = mix (Oi, color(1,1,1), d);

}

Such an atmosphere, implemented by a volume shader, may modify Ci and Oi due to the volume between the camera position and P. Table 12.1 shows the limited global variables available inside volume shaders.

A simple volume shader suitable to being an atmosphere is the fog shader shown in Listing 12.5. This shader implements a simple exponential absorption model of atmosphere by fading to a background color with distance.

The fog shader has a major deficiency: it does not take into consideration the actual light scattered by the volume into the camera (which should be affected by light intensities and shadows). More sophisticated atmospheric effects are possible if we ray march along the incident ray I, sampling illumination and accounting for atmospheric extinction. Typically, this is done with the following algorithm:

Choose an appropriate step size for marching along the ray.

len = length (I) Pcur = P-I;

while (len > 0) {

sample the smoke density and light at Pcur

Cvol += (1 - Ovol) + stepsize* (scattered light); Ovol += (1 - Ovol) + stepsize* (local density) ; Pour += stepsize * normalize(I) ;

len - stepsize ;

}

316 12 A Gallery of Procedural Shaders

Figure 12.12 Volumetric smoke using the smoke shader. See also color plate 12.12.

Thus, we are taking equal steps through the volume along the viewing ray. At each step, we sample the density of the smoke and the illumination arriving from the light sources at that point. Some light reflects from the smoke particles and scatters toward the viewer. Also, the density of the volume obscures the light coming from behind the volume (including other parts of the volume behind the current point).

Listing 12.6 is a shader that implements such a ray marching algorithm to simulate smoke. If the light sources in the scene cast shadows, you should be able to see the shadows in the smoke. (See Figure 12.12.)

Careful examination of the smoke shader will reveal several useful subtleties and areas for improvement (left as potential exercises):

Trapezoidal integration is used. The volume is divided into segments, and for each segment the beginning and ending densities and light values are averaged to compute a mean density and lighting value for the segment.

Separate parameters scale the density of the smoke as it applies to its tendency to obscure things behind it, versus the ability of the dense smoke to scatter light to the viewer. This allows you to, for example, make the volume scatter much light without blocking light behind it, or to obscure the background without introducing any newly scattered light.

12.6 Smoke: A Volume Shader

Listing 12.6 smoke.sl: a volume shader for smoky atmosphere.

/****************************************************************************

*Description:

*This is a volume shader for smoke. Trapezoidal integration is

*used to find scattering and extinction.

*Parameters:

*opacdensity - overall smoke density control as it affects its ability

*to block light from behind it.

*lightdensity - smoke density control as it affects light scattering

*toward the viewer.

*integstart, integend - bounds along the viewing ray direction of the

*integration of atmospheric effects.

*stepsize - step size for integration

*smokefreq, smokeoctaves, smokevary - control the fBm of the noisy smoke.

*If either smokeoctaves or smokevary is 0, there is no noise to

*the smoke.

*scatter - when non-1, can be used to give wavelength-dependent

*extinction coefficients.

***************************************************************************/

#include "noises.h"

/* For point P (we are passed both the current and shader space

*coordinates), gather illumination from the light sources and

*compute the smoke density at that point. Only count lights tagged

*with the "__foglight" parameter.

void

smokedensity (point Pcur, Pshad;

uniform float smokevary, smokefreq, smokeoctaves; output color Lscatter; output float smoke)

{

Lscatter = 0; illuminance (Pcur) {

extern color Cl; float foglight = 1;

lightsource("__foglight",foglight); if (foglight > 0)

Lscatter += Cl;

}

if (smokeoctaves > 0 && smokevary > 0) { point Psmoke = Pshad * smokefreq; smoke = snoise (Psmoke);

/* Optimize: one octave only if not lit */

if (comp(Lscatter,0)+comp(Lscatter,l)+comp(Lscatter,2) > 0.01) smoke += 0.5 * fBm (Psmoke*2, 0, smokeoctaves-1, 2, 0.5);

318 12 A Gallery of Procedural Shaders

Listing 12.6 (continued)

smoke = smoothstep(-l,l,smokevary*smoke); } else {

smoke = 0.5;

}

}

/* Return a component-by-component exp( ) of a color */ color colorexp (color C)

{

return color (exp(comp(C,0)), exp(comp(C,1)), exp(comp(C,2)));

}

volume

smoke (float opacdensity = 1, lightdensity = 1; float integstart = 0, integend = 100; float stepsize = 0.1, maxsteps = 100;

color scatter = 1; /* for sky, try (1, 2.25, 21) */ float smokeoctaves = 0, smokefreq = 1, smokevary = 1;)

{

point Worigin = P - I; /* Origin of volume ray */ point origin = transform ("shader", Worigin);

float dtau, last_dtau; color li, last li;

/* Integrate forwards from the start point */ float d = integstart + random( )*stepsize; vector IN = normalize (vtransform ("shader", I));

vector WIN = vtransform ("shader", "current", IN);

/* Calculate a reasonable step size */

float end = min (length (I), integend) - 0.0001; float ss = min (stepsize, end-d);

/ * Get the in-scattered light and the local smoke density for the * beginning of the ray

*/

smokedensity (Worigin + d*WIN, origin + d*IN,

smokevary, smokefreq, smokeoctaves, last_li, last dtau); color Cv = 0, Ov = 0; /* color & opacity of volume that we accumulate */ while (d <= end) {

/* Take a step and get the local scattered light and smoke density */ ss = clamp (ss, 0.005, end-d);

d += ss; smokedensity (Worigin + d*WIN, origin + d*IN,

smokevary, smokefreq, smokeoctaves, li, dtau);

319

12.6 Smoke: A Volume Shader

Listing 12.6 (continued)

/* Find the blocking and light-scattering contribution of the

*portion of the volume covered by this step.

*/

float tau = opacdensity * ss/2 * (dtau + last dtau); color lighttau = lightdensity * ss/2 * (li*dtau +

last_li*last dtau);

/* Composite with exponential extinction of background light */ Cv += (1-Ov) * lighttau;

Ov += (1-Ov) * (1 - colorexp (-tau*scatter)); last_dtau = dtau;

last_li = li;

}

/* Ci & Oi are the color and opacity of the background element.

*Now Cv is the light contributed by the volume itself, and Ov is the

*opacity of the volume, i.e. (1-Ov)*Ci is the light from the

*background that makes it through the volume. So just composite!

*/

Ci = Cv + (1-Ov)*Ci;

Oi = Ov + (1-Ov)*Oi;

}

The smokedensity function makes two important choices. First is the scattering function. Here we just sum the Cl contributions from each light. Thus, we are using a simple isotropic scattering function for the volume. For a different, and possibly more physically correct, appearance, we could use a different scattering function that depended on the particle size and angle between the light and viewing directions. Second, we compute a simple fBm function for the smoke. Obviously, we could get a very different appearance to the smoke density by substituting a different density function.

Setting either smokeoctaves or smokevary to 0 results in the smoke being homogeneous rather than varying spatially.

Volume shaders of this type can be very expensive. The computational expense is proportional to the number of iterations of the while loop, which is determined by the step size and the length of I. Therefore, it is important to choose your step size carefully-too large a step size will result in banding and quantization artifacts, while too small a step size results in very long render times. You will probably need to tune the step size carefully on a scene-by-scene basis.

The smoke shader is reasonably robust, and is used for all of the figures in Chapter 14. Chapter 15 discusses even more uses of volume shaders of this type for special effects.

32012 A Gallery of Procedural Shaders

12.7Lens Flare and "Clipping Plane" Shaders

People ask all the time how to do "lens flare"-those artifacts that occur when you point a real camera at a bright light source and you can see all sorts of interreflections from inside the lens system. This is an artifact that real-life photographers and cinematographers try hard to avoid, though very occasionally they can be used to give a mood of extreme brightness or heat (a scene from Lawrence of Arabia comes to mind). For some reason, many CG artists purposely add these artifacts to many of their scenes.

If you look at real lens flare, or some of the more low-key, tasteful CG lens flare, you will find several common features:

There will be a bright glow, or "bloom" effect, around the directly visible light source. This is assumed to be related to overexposure or saturation of the film.

There is a "starburst" pattern and a faint "rainbow" surrounding the image of the light. In real light, these are due to diffraction effects of the camera mechanism or even your eyelashes.

There are a number of faint circles or blotches distributed about the axis joining the center of the image and the position of the light source. These are presumably out-of-focus glints of light reflecting off of lens elements or other surfaces inside the camera itself.

Ordinary light sources don't have geometry associated with them, and even area light sources (in renderers that support them) can't account for these effects "where the lights aren't." So how do you get these effects in the lens, if there is no obvious shader attached to the light? The answer is surprisingly simple: we place a flat patch right in front of the camera, but closer than any other object-in other words, right next to the near clipping plane. This is very easy in PRMan, using the CoordSysTransform command:

AttributeBegin

CoordSysTransform "screen"

Surface "lensflare"

Patch "bilinear" "P" [ -2 2 0 2 2 0 -2 -2 0 2 -2 0]

AttributeEnd

This is of course in the World block. The CoordSysTransform command sets the active transformation to the one that matches the name of the argument, in this case "screen" space. Screen space is postperspective, so a z = 0 in "screen" space corresponds to being right on the near clipping plane. This postperspective space is not a big problem for PRMan, which works by projecting geometry into "camera" space, then eventually into "screen". This is especially convenient, since the preceding fragment places the patch right on the near clipping plane without even knowing where the near clip plane is!

Not all renderers can do this. In particular, ray tracers typically work by transforming the ray origin and direction into object space by using the inverse trans-

321

12.7 Lens Flare and "Clipping Plane" Shaders

formation, then testing the ray intersection against an object, doing calculations in object space. But in this case the ray origin is at the origin of "camera" space, and "object" space is "screen" space. Thus, we run right up against the singularity in the perspective transformation. So using CoordSysTransform with coordinate systems that contain perspective transformation is difficult for many renderers, including BMRT.

But all is not lost. We can still CoordSysTransform to "camera" space, then simply translate to be right on the other side of the clipping plane. For example, suppose that the near clipping plane was known to be at a distance of 0.1 from the camera. Then the following fragment would place the patch right on the other side of the plane:

AttributeBegin

 

CoordSysTransform "camera"

 

Translate 0 0 0.1001

 

Surface "lensflare"

 

Patch "bilinear" "P" [ -2 2 0

2 2 0 -2 -2 0 2 -2 0]

AttributeEnd

 

Notice that this is slightly less convenient than going straight to "screen" space, because you will have to know the exact value of the near clipping plane distance.

So now that we know how to place a patch in front of all other geometry, how do we write a shader to compute the lens flare but keep what's behind it visible? The basic outline of our shader is straightforward:

Ci = 0;

Oi = 0;

illuminance (P, vector "camera" (0,0,1), PI/2)

/ * calculate the contribution of each source, for * bloom, starburst, rainbow, and spots

*/

Ci += /* this light's contribution */

}

Thus, we are gathering from each light source that is visible from the camera's origin, E, and that is in the general direction that the camera is looking. We set Oi =0 because the lens flare merely adds light to the pixels without blocking the geometry that is behind the lens flare patch.

Because most of the effects in the shader are really happening in the 2D viewing plane rather than in 3D, we will transform both the position on the lens plane and the light positions into a common screen-like space:

point Pndc = (transform("NDC", P) - vector (.5, .5, 0))*2; Pndc *= vector(aspect, 1, 0);

float dPndc = filterwidthp(Pndc);

322 12 A Gallery of Procedural Shaders

The aspect is a quantity we compute from the image aspect ratio (see the final program listing for implementation). Similarly, we project the light position into the same space and also compute an attenuation based on how far outside the lens field of view the light is, the overall brightness of the light, and the angle and distance between the shading point and the light:

illuminance (P, vector "camera" (0,0,1), PI/2) {

float atten = acos(zcomp(normalize(vector transform("camera", P+L))));

atten = 1 - smoothstep(1, 2, abs(atten)/(lensfov/2)); float brightness = atten * intensity

* (comp(C1,0)+comp(Cl,l)+comp(C1,2))/3; point Plight = (transform("NDC", E+L) - vector (.5, .5, 0))*2; Plight *= vector(aspect, 1, 0);

vector Lvec = Plight - Pndc;

float angle = atan (ycomp(Lvec), xcomp(Lvec)) + PI; float dist = length(Lvec);

.

.

.

The bloom effect is achieved by making a pleasing amount of light that falls off as we get farther from the light's projection position:

float radius = sqrt(brightness)*5*mix(.2, bloomradius, urand<)); float bloom = pnoise (bloomnpoints*angle/(2°tPI), bloomnpoints); bloom = mix (0.5, bloom, bloomstarry);

bloom = mix (1, bloom, smoothstep(0, 0.5, dist/radius));

bloom = pow(1-smoothstep(0.0, radius*bloom, dist),bloomfalloff); Cflare += bloom * (bloomintensity / intensity) / brightness;

We use pnoise to help give a wobbly outline to the glowing bloom. The starburst effect is just an exaggerated pnoise calculation much like we did for bloom:

float radius = sqrt(brightness)*5*mix(.2, starburstradius, urand()); float star = float pnoise (starburstnpoints'angle/(2°`PI),

starburstnpoints);

star = pow(1-smoothstep(0.0, radius*star, dist), starburstfalloff); Cflare += star * (starburstintensity / intensity) / brightness;

The bloom and starburst effects can be seen individually in the top of Figure 12.13. Notice that both use a function called a urand. This is a function we have written that uses cel 1 noise to provide a repeatable random sequence. The implementation can be inspected at the top of the body of the final shader.

The rainbow effect is also not difficult, and simply uses a spline call to generate the colors (see example in Figure 12.13):

color rainbow (float x, dx) {

return filteredpulse (0, 1, x, dx)

323

12.7 Lens Flare and "Clipping Plane" Shaders

Figure 12.13 The major components of the lens flare effect: bloom (upper left), starburst (upper right), rainbow (lower left), and spots (lower right).

* spline (x, color(.5,0,.5),color(.5,0,.5),color(.375,0,0.75), color(0,0,1),color(0,1,0),color(1,1,0), color(1,.5,0),color(1,0,0),color(1,0,0));

}

Cflare += brightness*(rainbowintensity / intensity)

* rainbow((dist/rainbowradius-1)/rainbowwidth, (dPndc/rainbowradius)/rainbowwidth);

Finally, we must make the spots. We continue using our cellnoise-based random number generator to lay out a large number of locations along the axis joining the image center with the light source position. For each location, we see where it is and then if we are within the spot radius we add light to make the effect. We actually implemented four kinds of spots, chosen randomly: a perfectly flat "disk," a "ring,"

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]