Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Gamasutra: The Art & Business of Making Gamesspacer
Procedural Rendering on Playstation 2
View All     RSS
May 18, 2021
arrowPress Releases
May 18, 2021
Games Press
View All     RSS

If you enjoy reading this site, you might also want to check out these UBM Tech sites:


Procedural Rendering on Playstation 2

September 26, 2001 Article Start Previous Page 2 of 7 Next

The Transformation List
To render a single rib we need to know where to render the inform, at what size and at what orientation. To use this information we need to be able to generate a 4x4 matrix at any point along the parametric line.

Remembering that matrices concatenate right to left, the horn we defined earlier generates this list of operations:

T = bend * (stack * (twist (grow * identity)))
= bend * stack * twist * grow * identity

(As a side note, one oddity of the horn generation algorithm presented in the book “Evolutionary Art” is that it seems the programmers have altered the order of scale operations to always prepend the transformation queue. Every other transformation is appended to the left hand end of a transformation queue, e.g.

M = rotate * M

whereas all scale operations (the grow function) are prepended on the right hand side:

M = M * scale

This detail is never discussed in the book but turns out to be necessary to get the diagrams to match the declarations in the book.)

For simplicity’s sake we’ll leave off the identity matrix from the far right and just assume it’s there. If we parameterize this list of transforms w.r.t. the iterator m, the equation expands to this:

T(m) = bend(80*m) * stack(8.0*m) * twist(40*m, 1) * grow(0.9^m)

Each of the transforms has to generate a 4x4 matrix which again expands the equation into the more familiar matrix form (ignoring details of the coefficients):

T(m) = B * S * T * G

= |c s 0 0|
|s c 0 0|
|0 0 1 0|
|0 0 0 1|
|1 0 0 0|
* |0 1 0 0|
|0 0 1 0|
|0 t 0 1|
|c 0 s 0|
* |0 1 0 0|
|s 0 c 0|
|t 0 t 1|
|s 0 0 0|
* |0 s 0 0|
|0 0 s 0|
|0 0 0 1|

We are going to be wanting to move and orient the whole horn to some position in world space to allow us to construct composite objects, so we will have to prepend a modeling transformation we shall call the “root” transform (positioned after the identity):

T(m) = B * S * T * G * root

And finally we will have to transform the objects from world to screen space to generate the final rendering matrix M:

M = world_to_screen * T(m) * root

This description is slightly simplified as we have to take into consideration transforming lighting normals for the primitives. Normals are direction vectors transformed by the transpose of the inverse of a matrix, but as every matrix in this list is simple rotation, translation or scale then this matrix is the same as the modeling matrix T(m) except for a scale factor. We can use the transpose of the adjoint (the inverse not yet divided by the determinant) and renormalize each normal vector after transformation or build a special lighting matrix that ignores translations and scales, retaining only the orthogonal operations that change orientation (i.e. record only rotations and reflections).

void Horn::render(const Matrix &root,
const Matrix &world_to_screen);

Chaining Horns and Build
One operation we’re going to want to do is to chain horns together to allow us to generate long strings of primitives to model the backbones and tentacles.

A backbone made from chained horns.

This is where the root transformation comes into play. To chain horns together we need to transform the root of a horn N+1 by the end transformation of the previous horn N. We do this by getting the horn generation function to return the final matrix of the current horn for use later:

void Horn::render(Matrix &newroot,
const Matrix &root,
const Matrix &world_to_screen);

Another detail of horn generation arises in animations. As the number of ribs increases all that happens is that ribs become more closely packed – if you want the horn to appear to grow in length by adding horns you will have to re-specify the stack command in the transformation list to correctly extend your horn for more ribs. The way around this problem is to tell the iterator how many ribs this list of transforms was designed for, allowing it to generate the correct transformations for more ribs. This is done using the build command:

myhorn := horn
ribs (9)
build (5)
torus (2.1, 0.4)
stack (11);

This declaration declares a horn where the first 5 ribs are stacked 11 units high, but the horn is designed to transform 9 ribs -the remaining four ribs will be generated with interpolation values greater than 1.0.

Initially the horn has a build member variable set to 0.0, telling the interpolator to generate interpolation values of 0..1 using ribs steps. If the build variable is not equal to 0.0, the interpolator will map 0..1 using build steps, allowing requests for fewer or greater ribs to work with the correct spacing:

Matrix T;
float base = (build == 0.0) ? ribs : build;
for(int i=0; i
float m = i / base;
calc_matrix(T, m);

Branch and Ribcage
In order to construct the Lobster there are two more structures to generate – the head and the ribcage.

The head is an instance of a branch object – horns are transformed so that their near end is at the origin and their far ends spread out over the surface of a sphere using a spiral pattern along the sphere’s surface. You can control the pitch and angles of the spiral to produce a wide range of interesting patterns.

Examples of a branch structure.

The branch algorithm just produces a bunch of root transformations by:

  • Generate a point on a unit sphere.
  • Calculating the vector from the origin of the sphere to that point on the surface.
  • Return a 4x4 transform matrix that rotates the Object space y-axis to point in that direction.

To correctly orient horns twist could be taken into consideration by rotating horns so that the x-axis lies along the tangent to the spiral, but this is unimportant for the look of most Lifeforms. (To do branches correctly we could use a quaternion slerp along the spiral, but we’ll leave that as an exercise). The function declaration looks like this:

branch(const Matrix &root,
const Horn &horn, // inform to instance
const int number, // number of informs to instance
const float angle, // solid angle of the sphere to use
const float pitch); // pitch of the spiral

The ribcage algorithm takes two inputs – a horn for the shape of the rib and a second horn to use as the backbone. To place a horn on the ribcage the ribcage algorithm queries the backbone horn for transformations at specific points along it’s length and uses those transforms as the root for instancing each ribcage horn.

Assuming for the moment that the backbone is a simple stack up the y-axis. Ribs must stick out at 90 degrees from the backbone so we introduce a 90 degree rotation about the z-axis, followed by a 90 degree rotation about the y-axis. Also ribs come in pairs so the second rib must be a reflection of the first about the y-z plane. This can all be done by adding in a single reflection matrix. Think of the resulting composite transform as repositioning the rib flat onto the x-z plane pointing in the correct direction before moving it along the backbone:

rootrib(m) = Tbackbone(m) * reflection * root


reflection = |-1 0 0 0 |
|0 0 –1 0 |
|0 –1 0 0 |
| 0 0 0 1 |
|-1 0 0 0 |
and |0 0 –1 0 |
| 0 1 0 0 |
| 0 0 0 1 |

Note that this reflection must be applied to the shading normals too otherwise lighting for the ribcage will be wrong.

Examples of a ribcage (with backbone).

To help this process we must be able to randomly access transformations along a horn, hence the generalised calc_transform() member function in the Horn class.

The Basic Rendering Process Here, finally, is an outline of the basic rendering process for a Lobster. We will take this as the starting point for our investigation of procedural rendering and start focussing on how to implement this process more efficiently on the Playstation 2 using the EE-Core, vector units and GS together.

ribcage(const Matrix &root,
const Horn &horn, // inform to use as ribs
const Horn &backbone, // inform to query as the backbone
const int number); // number of pairs of ribs to generate

The Basic Rendering Process
Here, finally, is an outline of the basic rendering process for a Lobster. We will take this as the starting point for our investigation of procedural rendering and start focussing on how to implement this process more efficiently on the Playstation 2 using the EE-Core, vector units and GS together.


Article Start Previous Page 2 of 7 Next

Related Jobs

Remedy Entertainment
Remedy Entertainment — Espoo, Finland

Outsourcing Manager
Square Enix Co., Ltd.
Square Enix Co., Ltd. — Tokyo, Japan

Experienced Game Developer
Sunday GmbH
Sunday GmbH — Hamburg, Germany

Game Lead (m/w/d)
Deep Silver Volition
Deep Silver Volition — Champaign, Illinois, United States

Senior Project Manager

Loading Comments

loader image