Search code examples
c++sfmlnoiseperlin-noiseprocedural-generation

Wrote some perlin noise kind of code, it looks blocky


The previous answered question doesn't seem to answer my problem "Blocky" Perlin noise

I tried to simplify the most I could to make my code readable and understandable.

I don't use the permutation table, instead I use the mt19937 generator.

I use SFML

using namespace std;
using namespace sf;
typedef Vector2f Vec2;
Sprite spr;
Texture tx;
// dot product
float        prod(Vec2 a, Vec2 b)       { return a.x*b.x + a.y*b.y; }
// linear interpolation
float         interp(float start,float end,float coef){return coef*(end-start)+start;}
// get the noise of a certain pixel, giving its relative value vector in the square with [0.0 1.0] values
float getnoise(Vec2&A, Vec2&B, Vec2&C, Vec2&D, Vec2 rel){
    float
    dot_a=prod(A ,Vec2(rel.x   ,rel.y)),
    dot_b=prod(B ,Vec2(rel.x-1 ,rel.y)),
    dot_c=prod(C ,Vec2(rel.x   ,rel.y-1)),
    dot_d=prod(D ,Vec2(rel.x-1 ,rel.y-1));
    return interp
    (interp(dot_a,dot_b,rel.x),interp(dot_c,dot_d,rel.x),rel.y);
//    return interp
//    (interp(da,db,rel.x),interp(dc,dd,rel.x),rel.y);
}
// calculate the [0.0 1.0] relative value of a pixel
Vec2 getrel(int i, int j, float cellsize){
    return Vec2
    (float
     (i // which pixel
      -(i/int(cellsize))//which cell
      *cellsize)// floor() equivalent
      /cellsize,// [0,1] range
     float(j-(j/int(cellsize))*cellsize)/cellsize
     );
}
// generates an array of random float values
vector<float> seeded_rand_float(unsigned int seed, int many){
    vector<float> ret;
    std::mt19937 rr;
    std::uniform_real_distribution<float> dist(0, 1.0);

    rr.seed(seed);

    for(int j = 0 ; j < many; ++j)
        ret.push_back(dist(rr));
    return ret;
}
// use above function to generate an array of random vectors with [0.0 1.0] values
vector<Vec2>seeded_rand_vec2(unsigned int seed, int many){
    auto coeffs1 = seeded_rand_float(seed, many*2);
//    auto coeffs2 = seeded_rand_float(seed+1, many); //bad choice !
    vector<Vec2> pushere;
    for(int i = 0; i < many; ++i)
        pushere.push_back(Vec2(coeffs1[2*i],coeffs1[2*i+1]));
//    pushere.push_back(Vec2(coeffs1[i],coeffs2[i]));
    return pushere;
}
// here we make the perlin noise
void make_perlin()
{
    int seed = 43;
    int pixels = 400; // how many pixels
    int divisions = 10; // cell squares
    float cellsize = float(pixels)/divisions; // size of a cell

    auto randv = seeded_rand_vec2(seed,(divisions+1)*(divisions+1));
    // makes the vectors be in [-1.0 1.0] range
    for(auto&a:randv)
        a = a*2.0f-Vec2(1.f,1.f);
    Image img;
    img.create(pixels,pixels,Color(0,0,0));

    for(int j=0;j<=pixels;++j)
    {
        for(int i=0;i<=pixels;++i)
        {
            int ii = int(i/cellsize); // cell index
            int jj = int(j/cellsize);
            // those are the nearest gradient vectors for the current pixel
            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];

            float val = getnoise(A,B,C,D,getrel(i,j,cellsize));
            val = 255.f*(.5f * val + .7f);

            img.setPixel(i,j,Color(val,val,val));
        }
    }
    tx.loadFromImage(img);
    spr.setPosition(Vec2(10,10));
    spr.setTexture(tx);
};

Here are the results, I included the resulted gradients vector (I multiplied them by cellsize/2).

result1

result2

My question is why are there white artifacts, you can somehow see the squares...

PS: it has been solved, I posted the fixed source here http://pastebin.com/XHEpV2UP

Don't make the mistake of applying a smooth interp on the result instead of the coefficient. Normalizing vectors or adding an offset to avoid zeroes doesn't seem to improve anything. Here is the colorized result: result3


Solution

  • The human eye is sensitive to discontinuities in the spatial derivative of luminance (brightness). The linear interpolation you're using here is sufficient to make brightness continuous, but it does not not make the derivative of the brightness continuous.

    Perlin recommends using eased interpolation to get smoother results. You could use 3*t^2 - 2*t^3 (as suggested in the linked presentation) right in your interpolation function. That should solve the immediate issue.

    That would look something like

    // interpolation
    float        linear(float start,float end,float coef){return coef*(end-start)+start;}
    float        poly(float coef){return 3*coef*coef - 2*coef*coef*coef;}
    float        interp(float start,float end,float coef){return linear(start, end, poly(coef));}
    

    But note that evaluating a polynomial for every interpolation is needlessly expensive. Usually (including here) this noise is being evaluated over a grid of pixels, with squares being some integer (or rational) number of pixels large; this means that rel.x, rel.y, rel.x-1, and rel.y-1 are quantized to particular possible values. You can make a lookup table for values of the polynomial ahead of time at those values, replacing the "poly" function in the code snippet provided. This technique lets you use even smoother (e.g. degree 5) easing functions at very little additional cost.