Our implementation is actually a big improvement over the real life version described above (mostly due to questionable details in the code, which we have deemed to be FEATURES!). Our cutting planes are completely invisible during the rendering process except to the lights we assign to see them. This means that unlike physical cutting cards, we can place the half-planes right in the middle of the scene without them showing up. It is also possible to place cutting planes through objects, which can allow for some pretty cool effects (we will leave that to your imagination... no pics of that).
At the scene creation level, the cutting cards exist as a pair of parallel lines, which represent a sort of half plane. The intensity of light falling beyond the outer edge of one line falls to 0. If light falls beyond the outer border of the other line, it's intensity remains the same. Between the two lines, the intensity of light falls off between the lights intensity at that point, and zero intensity.
include "std.m"; include "defaults.m"; include "cameras.m"; include "colors.m"; include "lib/math.m" when (!math_m); include "std_avars.m"; include "shear.m"; include "ldetail.m"; include "metam.m" when (!metam_m); include "/usr/anim/com/global/mdl/trace.m"; /* set up the coordinate system */ coordsys("worldspace"); include "mdl/Counter/counter.m"; include "mdl/Wall/wall.m"; include "mdl/Cabinets/cabinets.m"; include "mdl/Outlet/Outlet.m"; include "mdl/Kettle/kettle.m"; include "mdl/Floor/floor.m"; /* include the code for cutlights and cutcards */ include "cutlight.m" when (!cutlight_m); include "cutter.m" when (!cutter_m); /* set up the main camera */ apr_cam(); /* the cutlight associated with the cutting cards */ cutlight("oreolight", GLOBALLIGHT, APR, (1,1,1), SPOT, FULL, LIGHTFALLOFF, SHADOWENABLE, "Loreolight_sm", (0,0,0), "", "", shaderargs("cutter1", "trial1", "cutter2", "trial2")); /* instantiate 2 cutting cards */ cutter("trial1"); cutter("trial2"); /* assorted objects*/ do_Wall(); do_Counter(); do_Cabinets("_upper",1); do_Cabinets("_lower",-1); do_Outlet(); do_Kettle(); do_Floor();
This section of code sets up the scene found in
Model Universe. Cutlight() instantiates a barnlight with links to
specified cutting cards, called cutters. The other parameters of
Cutlight are generic values that all lightsources at Pixar take. This is
done so it can be used transparently with older versions of lighting code.
This macro is responsible for drawing the green outline of the barnlight that you see in Model Universe , which is the visual representation of the lightsource in the animation system, Menv. In calling the macro, the shadowmaps, illumination model, and shadow/light colors are prescribed.
Immediately following that, the two cutter() lines invoke 2 cutting cards. There can be an arbitrarily large number of cutting cards, but at this point, any given light can only look at a maximum of 3. This number can also be extended arbitrarily high, but was left at 3 for simplicity. You will notice that in the cutlight() macro the names of the cutting cards to use are listed explicitly. This is necessary because the light has to have complete knowledge of which cutting cards it is going to use.
const cutter_m = 1; /* * This is the global macro that is used for the interactive placement * cutting cards. Avars stand for "animation variables" or "articulated * variables". They can be interactively changed in the Pixar animation * system. */ global macro cutter(name) begin object cat("cutter_",name) { avar tx = 0, ty = 0, tz = 0; avar rx = 0, ry = 0, rz = 0, rspin = 0; avar rpivx = 0, rpivy = 0, rpivz = 0; avar cutx = 0, width = 5 {min = 0.001}; avar showlength = 10 {min = 0.001}; cutterNailed(name, cutx, width, tx, ty, tz, rpivx, rpivy, rpivz, rx, ry, rz, rspin, showlength); } end /* * The following is the macro that is either called with hard-wired numbers * (for attaching a cutting plane to some geometry in the scene) or called * via the cutter() macro above for interactive placement. */ global macro cutterNailed(name,cutx,width, tx, ty, tz, rpivx, rpivy, rpivz, rx, ry, rz, rspin, showlength) begin { translate(tx, ty, tz); translate(rpivx, rpivy, rpivz); object "rotpivot" { color(0, 1, 0.191); local sz = 5; ldetail(LD_guides, LD_lighting); line((-(sz, 0, 0), (sz, 0, 0))); line((-(0, sz, 0), (0, sz, 0))); line((-(0, 0, sz), (0, 0, sz))); chars(name); } rotate(rz, z); rotate(ry, y); rotate(rx, x); rotate(rspin, z); translate(-rpivx, -rpivy, -rpivz); translate(cutx, 0, 0); scale(max(width, 0.001), 1, 1); /* * this is the heart of the whole thing... this stores the * transformation matrix into "cutter space" for each individual * piece of geometry to be transformed to, allowing a very simple * hit test against the XY plane for the determination of the * amount of light intensity reduction. */ /* save coordinate system of cutting plane */ coordsys(name); { ldetail(LD_guides,LD_lighting); { color(1, 0, 0.191); chars(name); line(((0, -showlength, 0), (0, showlength, 0))); } { translate(1, 0, 0); color(0, 0.191, 1); chars(name); line(((0, -showlength, 0), (0, showlength, 0))); } } } end
The macro cutter calls cutterNailed with animation variables
being passed in, as opposed to hard coding numbers in for stationary cutting
planes. The macro cutterNailed positions the cutting plane based on
the input parameters through translates, rotates, and scales. Once
that is done, a coordsys() call is used to store the transformation
matrix into "cutter space". The coordinate is saved to avoid having to
do hit tests of rays leaving the lightsource. Instead points on the objects
can be transformed back into "cutter space" and a simple test against the XY
plane can be used to determine hit or miss. The calls to line() and
chars() are used to indicate visually to the animator where the
cutting card is located. You will notice the red and blue lines in the
Model Universe image. They represent where the
cutting card has full and no effect. These lines move interactively as the
values of translate, rotate, and scale are changed.
light cutlight( /* various parameters */) { /* various variable declerations */ /* Cutter variables */ point apexcut; /* pyramid apex in cutter space */ point Pcut; /* Surface point in cutter space */ point Lcut; /* Light vector in cutter space */ float apexcutz, apexcutx; float Pcutz, Lcutz; float tcut; /* zplane intersection param */ float intcutx; /* test for inside/outside the barndoors of the green display */ /* * This is the first of the 3 cutplanes that the light accesses. * They can be easily macro-ized into a prepackaged set of * calls that could be used to modulate intensity (like in this * case), modulate shadow intensity, shadow blur, or any other * parameter in this light shader. */ if (cutter1 != "") { apexcut = transform(cutter1, nfrom); Pcut = transform(cutter1, Ps); apexcutz = zcomp(apexcut); Pcutz = zcomp(Pcut); if ((apexcutz * Pcutz) < 0) { /* opposite sides of zplane 0 */ /* the above is dangerous if cutters actually penetrate */ /* illuminated surfaces. Likely? */ Lcut = Pcut - apexcut; Lcutz = zcomp(Lcut); tcut = -(apexcutz / Lcutz); /* could test tcut instead */ intcutx = xcomp(apexcut + tcut * Lcut); /* x coord of intersection with zplane 0 using parametric definition of a line */ atten *= (smoothstep(0, 1, intcutx) * (1 - cutint1) + cutint1); /* attenuation variable is modified by the cutting plane calculation */ } } /* test to see if we're inside the barndoors, and if yes, do the test to see if we're in shadow */ /* then do the slide projection, if there is one */ /* if we're in the barndoors, calculate the intensity of light hitting the geometry */ illuminate( from, to, angle ) { /* calculate the intensity falloff */ } /* finally, set the output color of light to be the multiplication of the basic color and the various modulating variables */ Cl = Clight * atten * intensity * falloffintensity; }
It is a simple matter to increase the number of possible cutting planes from 3 to n, and would allow for far greater precision in the shaping of light sources. Imagine trying to create a light source shaped as some convex polygon. With cutting planes it can be done lots simpler. It also isn't a difficult matter to create cutting planes of different shapes. If you create circular cutting planes, even cooler shaping can be accomplished. Another interesting aspect of this technique is that other variables like shadow intensity, shadow blur, and even things like reflection intensity can be easily modulated with other cutting planes.
This... is just the tip of the iceberg!