Here's some amusement I whomped up over a couple of weekends, using a free implementation of the Renderman standard plus some ad hoc C++ code.
The idea was to play with the idea of hierarchical b-splines as a modelling mechanism. (If you have a copy of Computer Graphics: Principles and Practice by Foley, van Dam, Feiner & Hughes, Plate IVa in the back shows a nice dragon head modelled by David Forsey of UWaterloo using hierarchical b-splines.)
Since it is always easiest and most amusing to start with
something working, I grabbed a teapots example:
.
(If you're on an X-based system and the image looks dark, you may need to do 'xgamma -gamma 2.2'.)
As usual, click on the thumbnails to see the images at full resolution.
To start off with, I just wanted to make naturalistic,
biological sorts of random blob shapes by layering bicubic
spline patch meshes one upon the next, each one at twice
the linear spatial resolution and introducing new "random"
offsets at each control point, perpendicular to the
underlying surface, producing something like this:
.
(The commandline to produce that using my little C++
hack is ./glob --greenmarble.)
Here's how that works.
We start by approximating a sphere using a 4x4
bicubic spline mesh. I'm using catmull-rom splines
here because they interpolate -- they run through
the control points -- which makes me feel good,
but it doesn't much matter:
.
(./glob --lumpy1=0.0 --plastic --checker --layers=1.)
Not a great approximation, as you can tell from the
squarish shadow, but I'm not much interested in spheres
here anyhow.
Now we perturb the control points randomly a bit
perpendicular to the underlying surface. That's
the lumpy1 parameter:
.
(./glob --lumpy1=0.3 --plastic --checker --layers=1.)
Now we overlay the above surface with one of twice
the resolution, and offset it in turn normal to the
underlying surface. (You can think of this as being a bit like simple
wavelets, or any other divide-and-conquer powers-of-two
style decomposition.) The perturbation distance is
given by the --lump2 parameter:
.
(./glob --lumpy1=0.3 --lumpy2=0.3 --plastic --checker --layers=2.)
Now it really doesn't look much like a sphere. :)
And so it goes. The great thing about computers is that
if you can do it once, you can overdo it indefinitely:
.
(./glob --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --plastic --checker --layers=3.)
This, obviously, also quite similar in spirit to the
classical midpoint-displacement triangle-subdivision
algorithm for making fractal mountains:
.
(./glob --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --plastic --checker --layers=4.)
This approach will obviously never appeal to control
freaks who want direct control of every detail and nuance,
but I think it has great promise as a substrate for
procedurally generated worlds:
.
(./glob --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --lumpy5=0.3 --plastic --checker --layers=5.)
.
(./glob --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --lumpy5=0.3 --lumpy6=0.3 --plastic --checker --layers=6.)
And so forth -- you get the idea, and bicubic patches of pixel size or below aren't too interesting. In general, I find three or four layer models most interesting to play with.
Now, the
plastic look and checkerboard pattern are of course only
for debugging and didactic purposes: The fun thing about
Renderman is the exquisite control it gives you of (in
particular) the surfaces being rendered. Here's the
four-layer model above done with a glass surface:
.
(./glob --glass --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --layers=4.)
Or a screen-door pattern:
.
(./glob --screen --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --layers=4.)
Or clay:
.
(./glob --clay --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --layers=4.)
We can't omit classic veined marble:
.
(./glob --veinedmarble --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --layers=4.)
I'm particularly fond of David Griz' "superplank" solid texture:
.
(./glob --superplank --lumpy1=0.3 --lumpy2=0.3 --lumpy3=0.3 --lumpy4=0.3 --layers=4.)
A classic fractal is by definition self-similar on all scales,
but having separate -lumpyN parameters for each scale lets us
control the statistics of different spatial scales independently.
I find I actually like having the most detail on the second scale.
The shapes that emerge remind me of the wooden "handy" sculptures
which I used to make in Jr High, which were intended to be held in
the palm of the hand and experienced at least as much by touch as
by vision:
.
(./glob --superplank --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
The cool thing about procedural models generally, of course,
is that once you've built one, you can make an unlimited number
of similar things just by turning the crank. Here's the same
settings, but a different seed value:
.
(./glob --seed=sculpture --superplank --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
And yet a third object from the same family -- same spatial
statistics, different seed value:
.
(./glob --seed=sculpture2 --superplank --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
The human eye loves symmetry, so I've hacked in some options for
introducing various sorts of symmetries. Here are the same spatial
statistics with a mirror symmetry on the X axis:
.
(./glob --symmetry=x --seed=sculpture2 --greenmarble --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
Same spatial statitics, but with mirror rotational symmetry. Note
how the 'organic' feel of the earlier shapes has turned mechanical
with the increasing degree of symmetry:
.
(./glob --symmetry=t --seed=sculpture2 --greenmarble --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
... and now with axial symmetry. Oddly, this one looks
"organic" again despite having just as low a shape information
content as the preceding shape:
.
(./glob --symmetry=s --seed=sculpture2 --greenmarble --lumpy1=0.2 --lumpy2=0.7 --lumpy3=0.2 --lumpy4=0.2 --layers=4.)
That's probably enough for now!
Overall, the results were just about what I'd hoped, which is always a nice, if improbable, result.
One of the things I'd like to try next is using simulated annealing to automatically fit models of this class to, say, a picture of a face, somewhat in the spirit of the radial constraint models invented by my old boss Jim Brinkley at the UW. (His Digital Anatomist project there has earlier examples of my biological 3D graphics software efforts...)
If you'd like to play with this without re-doing the grunge hacking
involved, here's the source.
Binaries for the Linux
renderer I used are here.
Other Renderman implementations are listed here.
The Renderman FAQ is here.
Back to Cynbe's "Silly RenderMan Tricks" Page.