Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Some simple "perlin" noise  (Read 6481 times)

0 Members and 1 Guest are viewing this topic.

jokoon

  • Newbie
  • *
  • Posts: 35
    • View Profile
Some simple "perlin" noise
« on: June 23, 2014, 12:20:18 pm »
There are many noise generators: perlin, brownian, simplex, value noise, etc.

I made this function, it's not a lot of code and the result seems somehow fine. I'm not sure if I can call this perlin noise, it just looks random. It requires C++11, especially for the random generator. I'm not using the standard rand() function since it's not implementation defined, while minstd_rand is.

Here is the result:



Here is the code, you can get different results by changing the seed.

#include <random>
using namespace std;
using namespace sf;
typedef Vector2f Vec2; // I'm lazy
VertexArray glines(Lines); // to show gradients
Sprite spr;
Texture tx;
// this allows the code to generate the same image depending on an integer, and it will still look random. I'm not using a permutation table.
int seed = 23;
// simple dot product
float        prod(Vec2 a, Vec2 b)       { return a.x*b.x + a.y*b.y; }
// linear interpolation between 2 vectors
Vec2         interp(Vec2 start,Vec2 end,float coef){return coef*(end-start)+start;}
// generates an array of float between 0 and 1, depending on a unsigned int seed
vector<float> seeded_rand_float(unsigned int seed, int many){
        vector<float> ret;
        minstd_rand rr; // C++11 template of std::linear_congruential_engine
   
    if(seed%2147483647==0) // those values won't work and will break the generator (look at the definition of minstd_rand)
    {
        seed/=2; // dirty fix
        msg("bad seed !");
    }
        rr.seed(seed);

        for(int j = 0 ; j < many; ++j)
        ret.push_back(float(rr())/0x7fffffff); // making sure values are [0,1]
        return ret;
}

// generates a vector2f, taking two consecutive random float from the function above
vector<Vec2>seeded_rand_vec2(unsigned int seed, int many){
    auto coeffs1 = seeded_rand_float(seed, many*2); // twice as many values
    vector<Vec2> pushere;
    for(int i = 0; i < many; ++i)
        pushere.push_back(Vec2(coeffs1[2*i],coeffs1[2*i+1])); // using consecutive values from the array
    return pushere;
}
void init()
{
        // this is a lambda
   // [this](){ // removed the lambda,
        glines.clear();
        int pixels = 500; // number of pixels in a row
        int divisions = 10; // number of cells in a row
        float cellsize = float(pixels)/divisions; // the size of one cell where interpolation will occur.

        //generates the gradients vectors, gradients vector are random vector place at each corner of the square cells, (they're lattice). I've added +1 because there is one more at the end
        auto randv = seeded_rand_vec2(seed,(divisions+1)*(divisions+1));
        float factor=2.f;// that's not a very relevant variable, but nothing is perfect
        for(auto&a:randv){ // rewrite vector so their range can go from [0,1] to [-1,1] instead
            a*=factor;
            a-=factor*.5f*Vec2(1.f,1.f);
        }
        Image img;
        img.create(pixels,pixels,Color(0,0,0)); // black image of size pixels*pixels
        for(int jj=0;jj<=divisions;++jj){ // this loop just help visualize the gradient vectors using simple lines
            for(int ii=0;ii<=divisions;++ii)
            {
                Vec2
                A = randv[divisions*jj      +ii],
                glines.append(Vertex(Vec2(10,10)+cellsize*Vec2(ii,jj)));
                glines.append(Vertex(Vec2(10,10)+cellsize*Vec2(ii,jj)+A*cellsize*.5f));
            }
        }
       
        for(int j=0;j<=pixels;++j)
        {
            for(int i=0;i<=pixels;++i)
            {
                int ii = int(i/cellsize);
                int jj = int(j/cellsize);
                // here we create 4 vectors, each one describing a corner of the square cell
                // the indexing use is a little painful to understand
                Vec2
                &A = randv[divisions*jj      +ii],
                &B = randv[divisions*jj      +ii+1],
                &C = randv[divisions*(jj+1)  +ii],
                &D = randv[divisions*(jj+1)  +ii+1];

                // those steps generate x,y float values in [0,1], indicating the relative position of the pixel in a square cell
                // for example if the cellsize is 50, the pixel at (142,351), (x,y) will be (float(42/50),float(1/50))
                float x = float(i-cellsize*(i/int(cellsize)))/cellsize; // converting to int is like a floor() call
                float y = float(j-cellsize*(j/int(cellsize)))/cellsize; // (I guess?)
                // interpolate the 4 gradients into one vector, depending on the relative position of the pixel in the cell
                // note that you can change the interp function for smoother results (those weird white artifacts)
                Vec2 grad = interp(interp(A,B,x),interp(C,D,x),y);
                // calculate the pixel value, it's the sum of the dot products between the interpolated gradient, and 4 vectors, which are made with the difference between the pixel relative value, and the corresponding corners.
                float bright =
                prod(grad ,Vec2(x     ,y))+
                prod(grad ,Vec2(1.f-x ,y))+
                prod(grad ,Vec2(x     ,1.f-y))+
                prod(grad ,Vec2(1.f-x ,1.f-y));
               
                bright=abs(bright);//making it positive
                bright*=.25f; // divides by four, since the value is in [0.0,4.0]

                img.setPixel(i,j,Color(255.f*bright,255.f*bright,255.f*bright)); // sets the pixel
            }
        }
        tx.loadFromImage(img);
        spr.setPosition(Vec2(10,10));// shifts the sprite
        spr.setTexture(tx);
       
    //}();
}

void draw(RenderWindow&window)
{
        window.draw(spr);
}

It's not a compile-and-work example, but I made it easier for you to test. It might require many other headers like vector.

The interpolation is linear hence the ghostly white line artifacts between some gradients.

It's not the best implementation you could find, I think there are many errors (for instance, the gradients seem to loop over, the one on the left comes back on the right), but it's short and simple, and sort of look like random noise, I commented so you can understand the steps, because I had quite a lot of trouble understanding online examples, code and explanations, especially how the dot products are calculated.

Tell me what you think and ask about !

I also intend to use this to get a contour as points to draw polygons, to generate forest/grass/lakes/mountain patterns. Noise is interesting, but it's costly.
« Last Edit: June 23, 2014, 04:53:03 pm by jokoon »

Peteck

  • Jr. Member
  • **
  • Posts: 55
    • View Profile
Re: Some simple "perlin" noise
« Reply #1 on: June 23, 2014, 02:45:11 pm »
Well, noise functions is always cool to have. But for your code it's not really a function, but just a project file.
It would be alot better if you provided us a class/function for using the noise function. If I had to use what you wrote above, I first have to learn and understand your code and then strip out the things that I actually need for making the noise.

Anyways, the noise looks great, but I would not use it as it is right now. Simple because it isn't simple for anyone besides you to use.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Some simple "perlin" noise
« Reply #2 on: June 23, 2014, 03:21:32 pm »
Looks nice, although a bit too regular because your random generator is oriented along the squares.

And I agree that your code is quite a mess. If you want others to use it, you definitely have to clean it up ;)
For example, avoid using namespace, use whitespaces, and avoid global variables.

And you're aware that this code does nothing?
void init()
{
    // this is a lambda
    [this](){
        ... // everything in here is useless
    };
}

And it took me 15 seconds to understand that this code is a declaration of 4 references, you could not make it more confusing:
                Vec2
                &A = randv[divisions*jj      +ii],
                &B = randv[divisions*jj      +ii+1],
                &C = randv[divisions*(jj+1)  +ii],
                &D = randv[divisions*(jj+1)  +ii+1];

I'm really surprised how people come up with completely unintuitive code styles that nobody else in the world uses, yet they call it simple :P
« Last Edit: June 23, 2014, 03:26:57 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

jokoon

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Some simple "perlin" noise
« Reply #3 on: June 23, 2014, 04:32:31 pm »
I meant "simple" as in "short".

I made this code so it can be read, copied and modified. I encourage people to use libnoise if they don't want to understand how it works.

Using references or not it doesn't really change anything. If it compiles, it's valid code, style or not. I used a procedural style of programming, it's a little more simple if you want to understand the algorithm.

I'll add more comments !

jokoon

  • Newbie
  • *
  • Posts: 35
    • View Profile
Re: Some simple "perlin" noise
« Reply #4 on: June 27, 2014, 01:43:29 am »
I fixed my code, it was not perlin noise as the dot product was incorrect.

Still I have issues with it, it looks blocky, I posted it on stackoverflow here http://stackoverflow.com/questions/24435064/wrote-some-perlin-noise-kind-of-code-it-looks-blocky

 

anything