c++particle-systemperlin-noise

How do I correctly use 3D Perlin Noise as turbulence for my particle system?


So I am working on a particle system, mainly as a learning exercise on the CPU, using Visual Studio C++. It's looking pretty neat!

The latest thing I'm attempting is to add turbulence using 3D perlin noise. I found this fellow's code: https://blog.kazade.co.uk/2014/05/a-public-domain-c11-1d2d3d-perlin-noise.html

I implemented it correctly. I know this, because I can draw solid, working Perlin noise within my app, and it draws it correctly, also taking into account octaves, amplitude and frequency, which I added access to. So far, so good.

The problem is that I don't know how to use this correctly for displacing particle motion. This is currently my implementation (px0, py0, pz0 are my particle positions in -1.0 to 1.0 screen-space range. 0.1 is just to scale values down to a usable amount):

//Initialize octaves, seed, amplitude, frequency
noise::PerlinOctave perlin(octaves, seed, amplitude, frequency);

// Call the noise function
float n = perlin.noise(px0,py0,pz0) * 0.1;
px0 += n;
py0 += n;
pz0 += n;

This produces ok results but when I adjust the amplitude, my particles move diagonally. This is usually due to using addition which makes me think perhaps I shouldn't be adding noise to the particle positions but rather multiplying. However, I haven't had any success trying that.

I also tried unsuccessfully assigning 1D Perlin noise to every axis like this:

px0 += perlin.noise(px0) * 0.1;
py0 += perlin.noise(py0) * 0.1;
pz0 += perlin.noise(pz0) * 0.1;

The noise function returns a value of -1.0 to 1.0 (I believe) and my particle screen-space also works that same range so there should be no need to remap the noise to 0.0-1.0.

So I can't think of anything else to try. Any ideas where I'm going wrong?

Thank you all in advance! -Richard


Solution

  • I think you may have a few things backwards here.

    3D Perlin noise defines a noise function that takes a 3D input, to provide you with a 1D output. What you need is something that takes a 3D input, and gives you a 3D output.

    You could achieve this by having 3x3D noises....

    noise::PerlinOctave3D perlinX(octaves, seedX, amplitudeX, frequencyX);
    noise::PerlinOctave3D perlinY(octaves, seedY, amplitudeY, frequencyY);
    noise::PerlinOctave3D perlinZ(octaves, seedZ, amplitudeZ, frequencyZ);
    
    // Call the noise function(s)
    float nx = perlinX.noise(px0,py0,pz0) * 0.1;
    float ny = perlinY.noise(px0,py0,pz0) * 0.1;
    float nz = perlinZ.noise(px0,py0,pz0) * 0.1;
    px0 += nx;
    py0 += ny;
    pz0 += nz;
    

    Using 3x1D noise funcs would also work (maybe not that well, but it will work)

    noise::PerlinOctave1D perlinX(octaves, seedX, amplitudeX, frequencyX);
    noise::PerlinOctave1D perlinY(octaves, seedY, amplitudeY, frequencyY);
    noise::PerlinOctave1D perlinZ(octaves, seedZ, amplitudeZ, frequencyZ);
    
    // Call the noise function(s)
    float nx = perlinX.noise(px0) * 0.1;
    float ny = perlinY.noise(py0) * 0.1;
    float nz = perlinZ.noise(pz0) * 0.1;
    px0 += nx;
    py0 += ny;
    pz0 += nz;
    

    Even using 2D funcs would work (and be a bit cheaper than 3x3D funcs).

    noise::PerlinOctave2D perlinX(octaves, seedX, amplitudeX, frequencyX);
    noise::PerlinOctave2D perlinY(octaves, seedY, amplitudeY, frequencyY);
    noise::PerlinOctave2D perlinZ(octaves, seedZ, amplitudeZ, frequencyZ);
    
    // Call the noise function(s)
    float nx = perlinX.noise(px0, py0) * 0.1;
    float ny = perlinY.noise(py0, pz0) * 0.1;
    float nz = perlinZ.noise(pz0, px0) * 0.1;
    px0 += nx;
    py0 += ny;
    pz0 += nz;
    

    If you are modelling turbulence though, you probably want to be extracting the noise to be used as a force to apply to the particles, rather than simply using the result to directly modify the position.