Home | Works | About

Fungi Gallery Banner

Fungi Gallery


Description

The "Fungi" renderer is an ongoing project of mine, basically a framework for rendering iterative systems. This gallery is a selection of some higher-quality output from more recent versions of the renderer of various attractors. Some earlier output is displayed on the original Fungi page.

Fungi was inspired by the Spore work of (the late) "Dr." Richard Baily.

Built with Processing

600x600 139k

600x600 115k

600x600 101k

600x600 104k

600x600 98k

600x600 118k

600x600 132k

600x600 161k

600x600 176k

600x600 130k

600x600 147k

600x600 102k

600x600 104k

600x600 98k

600x600 100k

600x600 150k

600x600 126k

600x600 71k

600x600 91k

600x600 95k

600x600 62k

600x600 120k

600x600 82k

600x600 123k

600x600 66k

600x600 128k

600x600 64k

600x600 65k

600x600 102k

600x600 119k

600x600 116k

600x600 112k

600x600 46k

600x600 44k

600x600 58k

600x600 51k

600x600 54k

600x600 47k

600x600 92k

600x600 65k

600x600 119k

600x600 51k

600x600 87k

600x600 69k

 

Discussion

In an attempt to answer the "how did you make them?" questions...

First, if you just want to explore attractors with ready-made software I'd recommend Chaoscope or Apophysis or Ultra Fractal or XenoDream. Alternatively, just Google "Fractal Software" and see what else might be available.

Otherwise, start with the descriptions of...
"Iterated Function System" on Wiki
"Attractor" on Wiki
"Tone Mapping" on Wiki

In short, the images above were produced by relatively simple sets of mathematical equations. It all begins with a single point location. The equation is then applied to that point to determine its next location. The current location of the point is then recorded as a "hit" - in other words that location was "occupied" by the motion created by the equation. This process is then repeated over and over, always basing the subsequent location of the point on some mathematical relation to its previous location.

There are a wide variety of suitable equations. For example, the equation used in the applet on the Fungi page is a variation on an attractor discovered by Peter de Jong. It was chosen for the applet because it has a high likelihood of producing "interesting" images with randomly chosen paramters, so it makes for a good online demonstration. The images above were produced with a variety of different equations, some of which are much less productive than Peter de Jong, requiring considerable exploration before "interesting" parameters are discovered. In any case, the important characteristic of the chosen equation is that it exhibits a tendency to concentrate at certain locations rather than scatter all over the place or concentrate at a single location.

Below are two equations that are not suitable as attractors. The first equation "blows up" and sends points out to infinity. The second equation "collapses", sending points to the origin. Obviously neither is desirable.

1)   x', y' = x^2, y^2
2)   x', y' = x/2, y/2

Below is a better candidate as an attractor. Although still a simple set of equations, it will produce something resembling a stack of spiralling parallelograms:

x', y' = 0.1x + 0.9y, -0.7x - 0.3y

As the hits accumulate, some locations are visited more often than others. These high-density locations correspond to the "Attractor" of that equation. (here I am using the word "Attractor" in quotes to denote a non-strict definition) That is, the equation has a stronger tendency to pull any given random point towards those locations.

It can require thousands, millions, or even billions of iterations before the structure of the attractor is adequately revealed. The number of iterations required to achieve a satisfactory render is related to the density of the attractor itself. A "strong" or "tight" attractor (like images #17-20 above) may require substantially fewer iterations than a "weak" or "loose" attractor (like images #1-4 above).

In psuedo-code, the iteration loop looks something like this:

for some large number of control points
  pick a location at random
  for some large number of iterations
    apply the function to the control point
    plot the control point into the histogram
render the histogram into an output image

In this manner, a histogram of the attractor is developed. For example, in a 100x100 pixel image there are 10,000 unique locations, and each location will have accumulated some specific number of hits. The distribution of these hits could then be plotted on a scatter graph, where the x-axis represents the number of hits in a location and the y-axis represents the number of locations where that number of hits were accumulated. It is often the case, with "interesting" attractors, that the distribution of hit frequencies varies considerably thoughout the image.

The number of hits at each location is typically used to determine the brightness of that location in the output image. A significant problem in rendering such images is the issue of dynamic range. Some of the locations in the image may receive millions of hits, while others receive only a handful. Attempting to simply scale those values linearly into RGB color space will produce an unsatisfactory image. If 0 hits maps to RGB(0,0,0) and 1 million hits maps to RGB(255,255,255) then much detail will be lost:

Linear stretch: rgb_value = hit_count / max_hit_count * 255

Thus, some form of non-linear adjustment must be performed on the hit count when converting it into a brightness value. The "appropriate" adjustment depends on the nature of the attractor. It could be determined empirically by examining the histogram plot and fitting a function along an assumed line through the densest portions of the scatter plot. Though for artistic purposes it is typically sufficient to approximate the adjustment using either an exponential or logarithmic scaling:

Exponential stretch: rgb_value = hit_count^power / max_hit_count^power * 255

Logarithmic stretch: rgb_value = logbasehit_count / logbasemax_hit_count * 255

The task then is pick a suitable power or base, or just "guess" a value through experimentation, guided in part by prior experimentation. As experimentation occurs with a given set of equations, suitable density-mapping parameters tend to recur, suggesting the "right" values. Though more often than not the "right" values are simply a matter of personal choice - it's art after all, not science.

If you want to get really fancy you can low-pass filter the histogram and stretch that. Then add in the difference between the original and the filtered version, representing the high-frequency information of local detail, similarly stretched. Then normalize to desired output range. In this manner, areas of local detail are better preserved among larger areas of wider dynamic range. Though in practice such extra processing is rarely necessary.

Color is determined by noting some change in the motion of the point from one position to the next. For example, the angular difference bewteen the current location and the previous location can be mapped directly to hue. Similarly, the angular difference between current and previous velocity or accelleration can be mapped directly to hue. Essentially those three approaches (change in position, velocity or accellearation) simply have correspondingly longer "memories" of past locations (2,3 or 4). (higher-order derivatives of position in math-speak)

Position hue: hue = atan2(current_y - previous_y, current_x - previous_x) / PI * 255

Velocity hue: hue = atan2(current_dy - previous_dy, current_dx - previous_dx) / PI * 255

Accelleration hue: hue = atan2(current_ddy - previous_ddy, current_ddx - previous_ddx) / PI * 255

A favorite starting point of mine is to map hue by change in position, and map saturation by change in velocity. (brightness is still based on number of hits) In this manner the color of the final image is entirely dependent on the attractor. The function used to map between any of the above equations and hue/saturation need not be linear (as shown above) nor need it span the entire range of values - those parameters can be chosen so as to achieve the desired "spread" of color values.

To add even further interest to the coloring algorithm, any of the above equations can be used to calculate an index into a predefined palette instead of directly mapping to hue. The palette may be loaded from an image or generated in some manner. For example, using a randomly generated spline curve that weaves itself through hue values, then index into a point on that curve to retrieve the hue value. (or sets of three such curves wandering through HSB or RGB values). Again, such mapping into a palette need not be linear.

I wrote fungi with Processing because it is a productive rapid prototyping environment. I can quickly make changes to large portions of the code, deriving entirely new attractors from a generic base class, and compile and run in almost no time. I've also built a C++ version of the renderer (that I call fungicide, as in the "fungi killer") that is quicker at iteration and rendering, but more effort to rebuild for new attractors. All of the above images were generated with fungi, not fungicide.

Hope that helps.

Creative Commons License

© 2006 Dave Bollinger