Domain warping
What helped me wrap my head around the problem you describe is the following. I used to think of this problem as:
How do I move pixels around in my texture to warp it?
Instead, try thinking of it like this:
How do I pick samples from my original texture in a warped way?
This concept is known as domain warping. The procedure to do domain warping is as follows:
- Start with your original image, in your case the
float original[width, height]
- Create a new empty array of the same size (just to keep things simple), let's call it
float warped[width, height]
. We now want to fill this new array with a distorted version of the original image.
- To copy the image identically, you would iterate over all pixels and do:
distorted[x, y] = original[x, y]
. Instead of doing this, let's warp the domain by doing this: distorted[x, y] = original[(x + 16) % width, (y + 16) % height]
. You'll notice, after looping over all pixels, distorted
now contains a shifted version of the original image. This is because we warped the domain. Instead of doing [x, y] -> [x, y]
, we warped out coordinates as [x, y] -> [x + 16, y + 16]
(the modulo makes sure we don't go out of bounds of the array).
Position-variant distortion
In the problem you describe, you probably don't want to shift every pixel equally. When you apply the same function to the coordinates, in every position, you'd call it a position-invariant distortion. Let's make the way we distort our pixels depends on the position we are sampling for (i.e. position-variant distortion).
Repeat the procedure above, but use the following transfer function: distorted[x, y] = original[x + 10 * sin(x / 20), y + 10 * cos(y / 20)]
. Now, the offset in sampling (10 * sin(x / 20)
for x
and 10 * cos(y / 20)
for y
) depends on the position of the pixel! As you'll see, you now end up with a wavy version of your image. Parts of it seem stretched, and parts of it seem squashed. The big clue here is that you are not just linearly shifting all pixels in the same way, you are varying your transfer function based on the actual pixel coordinate.
(Note: don't forget to add rounding code to sample on integer indices, e.g. by wrapping your indices in round( ... )
)
Solution to your problem: lookup of the distortion
Now to solve your problem, you want to use a procedurally generated pattern to distort your image. To do so, you could follow the procedure below:
- Start with your original image, in your case the
float original[width, height]
, and create two images of the same size and will them with procedural values, let's call them procedural_x[width, height]
and procedural_y[width, height]
.
- Instead of using a function like
sin
or cos
to calculate our sampling offset, we will use our procedurally generated values. First normalize your procedural values to be in the range of 0.0
to 1.0
. Depending on the algorithm you use, they may already be in that range. If not, normalize using the following function: procedural_x_normalized[x, y] = (procedural_x[x, y] - min_value) / (max_value - min_value)
Now, fill your distorted image by iterating over all pixels and sampling using the following function:
distorted[x, y] = original[
x + 10 * (procedural_x_normnalized[x, y] - 0.5),
y + 10 * (procedural_y_normnalized[x, y] - 0.5)
]
Here, you are using your procedural patterns, as lookup tables for the sampling offsets. In other words, when you are sampling a pixel, you check your procedural arrays to determine how many pixels to shift horizontally and vertically. (Note: don't forget to add rounding code to sample on integer indices, e.g. by wrapping your indices in round( ... )
)
In summary, to achieve distortion using procedural values, use those values as offsets when sampling your image. Instead of thinking of the problem as How do I distort my original pixture?
, think of it as How do I samples values from my original picture in a distorted way?
.
I highly recommend reading this article by Inigo Quilez: http://www.iquilezles.org/www/articles/warp/warp.htm .