The book doesn't really offer much of an explanation either, but the idea of perturbation is to use a
continuous function to displace the pixels of an image somehow.
In pseudo code, this could look like:
Code:
loop x {
loop y {
offset_x = someFunction1(x, y)
offset_y = someFunction2(x, y)
value = getPixelInterpolated(original_image, x + offset_x, y + offset_y)
putPixel(new_image, x, y, value)
}
}
As the pixel offset values will be floating point numbers, it's very important to interpolate between the pixel values of the original image - simple linear interpolation will usually do.
Here is how it was done in TT:
Code:
public final Channel perturbation(Channel perturb, float magnitude) {
Channel channel = new Channel(width, height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float p = magnitude*(perturb.getPixel(x, y) - 0.5f);
float x_coord = x + width*p;
int x_coord_lo = (int)x_coord;
int x_coord_hi = x_coord_lo + 1;
float x_frac = x_coord - x_coord_lo;
float y_coord = y + height*p;
int y_coord_lo = (int)y_coord;
int y_coord_hi = y_coord_lo + 1;
float y_frac = y_coord - y_coord_lo;
float val1 = Tools.interpolateLinear(getPixelWrap(x_coord_lo, y_coord_lo), getPixelWrap(x_coord_hi, y_coord_lo), x_frac);
float val2 = Tools.interpolateLinear(getPixelWrap(x_coord_lo, y_coord_hi), getPixelWrap(x_coord_hi, y_coord_hi), x_frac);
channel.putPixel(x, y, Tools.interpolateLinear(val1, val2, y_frac));
}
}
pixels = channel.getPixels();
return this;
}
A Channel object is basically just a greyscale image using float values between 0 and 1. Instead of a function, another greyscale image ("perturb") is used as an offset map, giving both x and y coordinates the same offset for each pixel (this is a simplified way to do it, but works well enough for terrains).
Someday I'm going to release all the "procedural stuff" code as open source, but right now I simply don't have the time to clean it up and document it.
Hope this gave you a few useful hints!