I've been playing around with Procedurally Generated World. I've so far managed to create the Height Map, and a translate map, but now i'm attempting to create Caves,

The main idea is to use another Noise map, with values being either 0 or 1, and multiply it with the Height Map.

I've managed to achieve this:

enter image description here

This is currently described with a float[Width, Height], which has value 0 for those points (x,y) such that (x,y) is black, or 1, for those which (x,y) is white.

I want to apply some fractal to this to achieve this:

enter image description here

However, i have no idea how to do this!

(I also apologize for any grammatical errors. I haven't slept in like 18 hours and i can't think straight)


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:

  1. Start with your original image, in your case the float original[width, height]
  2. 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.
  3. 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:

  1. 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].
  2. 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)
  3. 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 .

  • \$\begingroup\$ this should be doable in place with somethin like noise2(x + scale*noise1(x), y + scale*noise1(y)) \$\endgroup\$
    – Sopel
    Aug 29 '17 at 14:10

To create a fractal like noise, you need to take your noise, create some values with it with decreasing frequency and weight, then divide the value with the total weight, in pseudo code (the base frequency and sample count can be changed):

value = 0, weight = 1, totalWeight = 0, frequency = 0.5
for (i = 0; i < 8; i++) {
    value += noise(x, y, frequency) * weight;
    totalWeight += weight;
    weight /= 2;
value /= totalWeight;
  • \$\begingroup\$ What you've described is self-similar noise. Most fractals are not self-similar. \$\endgroup\$
    – MickLH
    Aug 24 '17 at 19:04
  • \$\begingroup\$ I apologize, but i'm not sure i understand. I want to create a fractal which is somewhat similar to the first image, i.e. Just mess the borders a little bit. So, should i apply to the values (x,y) of my plane such that they are part of the cave (i.e. they are black points in the first image) and to no other point? Wouldn't that preserve the borders, and make it too soft? \$\endgroup\$ Aug 24 '17 at 19:09
  • 1
    \$\begingroup\$ @MickLH it won't be self similar \$\endgroup\$
    – Bálint
    Aug 24 '17 at 19:17
  • \$\begingroup\$ @VictorChavauty i don't understand what you're trying to say. I will do the exact opposite of what you claim, it will make the border rougher. This maybe could help you understand the method cmaher.github.io/posts/working-with-simplex-noise \$\endgroup\$
    – Sopel
    Aug 29 '17 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.