|
Features

Procedural
Rendering on Playstation 2
This
paper describes the design and coding of a program to generate instances
of a procedural Lifeform, based on the work of William Latham in
the late 1980’s. The sneaky thing is that this tutorial is not really
about procedural rendering, it’s more about the real-world experience
of designing and writing programs on PS2.
Why
choose a procedural model? Nearly all of the early example code
that ships with the T10000 development kit is something along the
lines of “send this magic lump of precalculated data at this magic
bit of VU code”. It does show off the speed of the machine and shows
that techniques are possible but it’s not very useful if you want
to learn how to produce your own code. By choosing a procedural
model to program there are several benefits:
- Very
little can be precalculated. The program has to explicitly build
up all the data structures, allowing more insights into how things
are put together.
- It
can generate a lot of polygons quickly. Procedural models are
scalable to the point where they can swamp even the fastest graphics
system.
- It’s
more interesting than yet another Fractal Landscape or Particle
System. I just wanted to be a little different. The algorithm
is only marginally more complex than a particle system with emitters.
- The
algorithm has many ways it can be parallelized. The algorithm
has some interesting properties that could allow large parts of
it to be run on a VU, taking just the numerical description of
a creature as inputs and generating the polygons directly. I have
only space to explore one way of producing the models, but there
are several other equally good separation points.
- It’s
been over ten years. I thought it would be fun to try an old technique
with modern hardware. Evolutionary Art was once a painstaking,
time consuming search through the genome space, previewing and
finally rendering with raytracing. Being able to generate 60 new
Lifeforms a second opens up whole new areas of animation and interaction.
This
paper will start by assuming you are familiar with the internal
structure of the PS2 – EE Core, VIF0, VIF1, GIF, DMAC and Paths
1, 2 and 3. It will also assume you know the formats for GIF, VIF
and DMA tags as they have been discussed endlessly in previous tutorials.
Lifeforms
First,
a look at the type of model we’re going to be rendering. The ideas
for thesemodels
come from the organic artworks of William Latham.
Latham
(in the days before his amazing sideburns) was recruited in 1987
from a career lecturing in Fine Art by a team of computer graphics
engineers at the IBM UK Scientific Centre at Winchester, UK. He
was recruited because of his earlier work in “systems of art” where
he generated huge paper-based tree diagrams of forms that followed
simple grammars. Basic forms like cubes, spheres and cylinders would
have transformations applied to them – “scoop” took a chunk out
of a form, “bulge” would add a lobe to the side, etc. These painstaking
hand drawn production trees grew to fill enormous sheets of paper
and were displayed in the exhibition “The Empire of Form”.
The
team at IBM Winchester wanted to use his insights into rule based
art to generate computer graphics and so Latham was teamed with
programmer Stephen Todd in order to make some demonstrations of
his ideas. The resulting programs Form Build and Mutator were used
to create images for the exhibitions “The Conquest of Form” and
“Mutations”.
The
final system was documented in a 1989 IBM Systems Journal paper
(vol.28, no.4) and in the book “Evolutionary Art and Computers”,
from which all the technical details in this tutorial are taken.
(Latham later went on to found the company Computer Artworks who
have recently released the game Evolva).
The
aim of this tutorial is to render a class of Lifeform that Latham
called a “Lobster” as efficiently as possible on the Playstation2.
The Lobster is a useful object for exploring procedural rendering
as it is a composite object made three parts, head, backbone and
ribcage, each of which is a fully parameterized procedural object.
Definitions
In this talk I shall be using the Renderman standard names for coordinate
spaces:
- Object
space Objects are defined relative to their own origin.
For example, a sphere might be defined as having a unit radius
and centered around the origin (0,0,0).
- World
Space A modeling transformation is used to position, scaled
and orient objects into world space for lighting.
- Camera
Space All objects in the world are transformed so that
the camera is sitting at the origin looking down the positive
z-axis.
- Screen
Space A 2D space where coordinates range from 1.0..1.0
along the widest axis and takes into consideration the aspect
ratio along the smallest axis.
- Raster
Space A integer 2D space where increments of 1 map to single
pixel steps.
The
order of spaces is:
object
-> world -> camera -> screen -> raster
Defining
A Horn
The basic unit of rendering is the horn, named because of it’s similarity
to the spiral forms of animal horns. A control parameter is swept
from 0.0 to 1.0 along the horn and at fixed intervals along this
line primitives are instanced – a sphere or a torus – according
to an ordered list of parameterized transformation rules.
Here’s
an example declaration from the book:
neck
:= horn
ribs (18)
torus (2.1, 0.4)
twist (40, 1)
stack (8.0)
bend (80)
grow (0.9) ;
The
above declaration, exactly as it appears in the book, is written
in a high level functional scripting language the IBM team used
to define objects. We will have to translate this language into
a form we can program in C++, so let’s start by analyzing the declaration
line by line:
neck
:= horn
The
symbol “neck” is defined by this horn. Symbols come into play later
when we start creating linear lists of horns or use a horn as the
input primitive for other production rules like “branch” or “ribcage”.
At this point in the declaration the horn object is initialized
to zero values.
ribs
(18)
This
horn has 18 ribs along it’s length. To generate the horn, an iterator
will begin at the start
position (see later, initially 0.0) and count up to 1.0,
taking 18 steps along the way. At each step a copy of the current
inform (input form)
is generated. Internally, this interpolator value is known as m.
The number of ribs need not be an integral number – in this implementation
the first rib is always at the start position, then a fractional
rib is generated, followed by the integer ribs. In effect, the horn
grows from the root.
torus
(2.1, 0.4)
This
horn will use for it’s inform
a torus of major radius 2.1 units and minor radius 0.4 units (remember
that these values are just the radii, the bounding box size of the
object is twice these values). In the original Form
Build program this declaration can be any list of horns,
composite forms or primitives which are instanced along the horn
in the order they are declared (e.g. sphere, cube, torus, sphere,
cube, torus, etc.), but for our purposes we will allow only a single
primitive here. Extending the program to use generalized primitive
lists is quite simple. Primitives are assumed to be positioned at
the origin sitting on the x-z plane, right handed coordinate system.
twist
(40, 1)
Now
we begin the list of transformations that each primitive will undergo.
These declarations specify the transform at the far end of the horn,
so intermediate ribs will have to interpolate this transform along
the horn using the iterator value m.
twist(angle,offset)
is a compound transform that works like this:
- translate
the rib offset units along the x-axis.
- rotate
the result m*angle degrees around the y-axis.
-
translate that result –offset units along the x-axis.
The
end result, at this stage, produces a spiral shaped horn sitting
on the x-z plane with as yet no elevation.
stack
(8.0)
This
translates the rib upwards along the +y-axis. Note that although
we have 18 ribs that are 0.8 units high (18 * 0.8 = 14.4), we’re
packing them into a space 8.0 units high. Primitives will therefore
overlap making a solid, space filling form.
bend
(80)
This
rotates each rib some fraction of 80 degrees around the z-axis as
it grows. This introduces a bend to the left if you were looking
down the –z-axis (right handed system).
grow
(0.9)
This
scales the rib by a factor of 0.9, shrinking each primitive by 10
percent. This is not to say that each rib will be 90 percent of
the size of the previous rib, the 0.9 scale factor is for the whole
horn transformation. In order to interpolate the scale factor correctly
we need to calculate powf(0.9,m)
where m is our interpolator.
At
the end of this horn declaration we have defined three areas of
information:
- The
horn’s attributes (18 ribs, start counting at zero)
-
The ordered list of input forms (in this case a single torus).
- The
ordered list of transformations (twist, stack, bend, grow).
The
order of declarations inside each area is important e.g. twist comes
before stack, but between areas order is unimportant e.g. ribs(18)
can be declared anywhere and overrides the previous declaration.
This leads us to a design for a horn object, using the C++ STL,
something like this:
______________________________________________________
|