Блочный шум Перлина

Недавно я пытался реализовать генератор шума Perlin на C (на основе веб-сайта Кена Перлина, используя библиотеки SDL в качестве вывода на экран), но вывод показывает, что границы между блоками интерполяции не являются непрерывными или гладкими - блоки интерполяции действительно проявляются как блоки.

Я пробовал четыре вида интерполяции, и все «гладкие» выглядят примерно одинаково; только косинус выглядит (очень) немного лучше, а прямолинейный - ужасно по сравнению. (ниже косинусный и линейный) Шум с косинусной интерполяцией Шум с линейной интерполяцией

По иронии судьбы, если создать фрактальную сумму шумов (моя конечная цель в этом), linear сдувает плавные интерполяции с точки зрения «блочности» и на самом деле выглядит почти нормально. Фрактальная сумма, косинусная интерполяция Фрактальная сумма, линейная интерполяция

Я почти уверен, что в моем коде чего-то не хватает или я что-то делаю неправильно, но я не могу этого найти.

Любые предложения относительно того, что (или какие условия) могут вызывать эти артефакты блока?

Для справки, мой текущий код ниже:

#include<stdio.h>
#include<math.h>
#include<SDL/SDL.h>

void normalize3(float *vec3){
    float distX=0,distY=0,distZ=0;
    distX=vec3[0];
    distX*=distX;
    distY=vec3[1];
    distY*=distY;
    distZ=vec3[2];
    distZ*=distZ;
    float dist=sqrtf(distX+distY+distZ);
    vec3[0]/=dist;
    vec3[1]/=dist;
    vec3[2]/=dist;
}

float sinterpolate(float scale){
    //return scale*scale*(3.0-2*scale); //Classic 3*t^2-2*t^3

    /*float t=scale*scale;
    float u=t*t;
    return (6.0*u*scale-15.0*u+10.0*t*scale);*/ //Improved 6*t^5-15*t^4+10*t^3

    return (0.5-cosf(scale*M_PI)/2.0); //Straight cosine interpolation
}

float linterpolate(float a,float b,float scale){
    return a+scale*(b-a);
}

float noise3(float *vec3,float *grads,Uint8 *perms){
    vec3[0]=fmodf(vec3[0],256.0);
    vec3[1]=fmodf(vec3[1],256.0);
    vec3[2]=fmodf(vec3[2],256.0);
    Uint8 ivec3[3];

    float relPos[3],temp;
    float cube[2][2][2];
    Uint8 index;

    //One loop for each dimension of noise.
    for(int x=0;x<2;x++){
        ivec3[0]=vec3[0];
        ivec3[0]+=x;
        relPos[0]=vec3[0]-ivec3[0];
        for(int y=0;y<2;y++){
            ivec3[1]=vec3[1];
            ivec3[1]+=y;
            relPos[1]=vec3[1]-ivec3[1];
            for(int z=0;z<2;z++){
                ivec3[2]=vec3[2];
                ivec3[2]+=z;
                relPos[2]=vec3[2]-ivec3[2];

                index=ivec3[0]+perms[ivec3[1]+perms[ivec3[2]]];

                temp=relPos[0]*grads[3*index];
                temp+=relPos[1]*grads[3*index+1];
                temp+=relPos[2]*grads[3*index+2]; //The gradient's dot product
                                                  //with respect to the point
                                                  //being analyzed

                cube[x][y][z]=temp;
            }
        }
    }

    ivec3[0]--;
    ivec3[1]--;
    ivec3[2]--;
    relPos[0]=vec3[0]-ivec3[0];
    relPos[1]=vec3[1]-ivec3[1];
    relPos[2]=vec3[2]-ivec3[2];
    relPos[0]=sinterpolate(relPos[0]);  //Comment these
    relPos[1]=sinterpolate(relPos[1]);  //if you want
    relPos[2]=sinterpolate(relPos[2]);  //Linear Interpolation.


    return linterpolate(linterpolate(linterpolate(cube[0][0][0],cube[0][0][1],relPos[2]),linterpolate(cube[0][8][0], cube[0][9][1],relPos[2]),relPos[1]),linterpolate(linterpolate(cube[1][0][0],cube[1][0][1],relPos[2]),linterpolate(cube[1][10][0], cube[1][11][1],relPos[2]),relPos[1]),relPos[0]);
}

int main(int argc,char **args){
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Surface *screen=SDL_SetVideoMode(512,512,32,SDL_SWSURFACE);
    srandom(SDL_GetTicks());  //If not on OSX/BSD, use srand()
    Uint32 *pixels;
    Uint32 grays[256];
    for(int x=0;x<256;x++){
        grays[x]=SDL_MapRGB(screen->format,x,x,x);
    }


    float grads[768];
    Uint8 perms[256];
    //First, generate the gradients and populate the permutation indexes.
    for(int x=0;x<256;x++){
        grads[3*x]=random();    //If not on OSX/BSD, use rand()
        grads[3*x+1]=random();
        grads[3*x+2]=random();
        normalize3(grads+3*x);

        perms[x]=x;
    }

    //Let's scramble those indexes!
    for(int x=0;x<256;x++){
        Uint8 temp=perms[x];
        Uint8 index=random();
        perms[x]=perms[index];
        perms[index]=temp;
    }

    printf("Permutation Indexes: ");
    for(int x=0;x<256;x++){
        printf("%hhu, ",perms[x]);
    }
    putchar('\n');

    Uint32 timer=SDL_GetTicks(),frameDelta;
    SDL_Event eventos;
    float zoom=-5.0;
    eventos.type=SDL_NOEVENT;
    while(eventos.type!=SDL_QUIT){
        SDL_PollEvent(&eventos);
        if(SDL_GetKeyState(NULL)[SDLK_UP]){
            zoom-=0.001*frameDelta;
        }
        else if(SDL_GetKeyState(NULL)[SDLK_DOWN]){
            zoom+=0.001*frameDelta;
        }
        float scale=expf(zoom);
        pixels=screen->pixels;
        float pos[3];
        pos[2]=SDL_GetTicks()/3000.0;
        for(int y=0;y<512;y++){
            pos[1]=y*scale;
            for(int x=0;x<512;x++){
                pos[0]=x*scale;
                float fracPos[3];
                fracPos[0]=pos[0];
                fracPos[1]=pos[1];
                fracPos[2]=pos[2];
                float color=noise3(fracPos,grads,perms);

                //Fractal sums of noise, if desired
                /*fracPos[0]*=2.0;
                fracPos[1]*=2.0;
                fracPos[2]*=2.0;
                color+=noise3(fracPos,grads,perms)/2.0;

                fracPos[0]*=2.0;
                fracPos[1]*=2.0;
                fracPos[2]*=2.0;
                color+=noise3(fracPos,grads,perms)/4.0;

                fracPos[0]*=2.0;
                fracPos[1]*=2.0;
                fracPos[2]*=2.0;
                color+=noise3(fracPos,grads,perms)/8.0;

                fracPos[0]*=2.0;
                fracPos[1]*=2.0;
                fracPos[2]*=2.0;
                color+=noise3(fracPos,grads,perms)/16.0;

                */

                *pixels++=grays[127+(Sint8)(256.0*color)];
            }
        }

        SDL_Flip(screen);
        frameDelta=SDL_GetTicks()-timer;
        printf("Running @ %.3f FPS!\n",1000.0/frameDelta);
        if(frameDelta<16){
            SDL_Delay(16-frameDelta);
        }
        timer=SDL_GetTicks();
    }

    return 0;
}

Использование: во время бега нажмите и удерживайте кнопку «Вверх» или «Вниз», чтобы увеличить или уменьшить сетку шума.


person MVittiS    schedule 08.01.2013    source источник
comment
Я предлагаю вам сформулировать вопрос более четко, возможно, используя заголовок «уровня 2» ## Question непосредственно перед ним. Вероятно, вам следует включить ссылку на веб-сайт Кена Перлина (предпочтительно на конкретные страницы, которые вы используете). Конечно, его, вероятно, нетрудно найти, но это улучшит ваш вопрос, если людям не нужно заходить в выбранную ими поисковую систему.   -  person Jonathan Leffler    schedule 08.01.2013
comment
Отредактировано и связано. Я очень признателен за ваши предложения по улучшению вопроса.   -  person MVittiS    schedule 08.01.2013
comment
Есть ли причина, по которой вы хотите реализовать генератор шума самостоятельно, вместо использования уже существующих, таких как превосходный и модульный libnoise?   -  person Some programmer dude    schedule 08.01.2013
comment
Просто ради обучения. Я обязательно проверю исходный код этой библиотеки позже.   -  person MVittiS    schedule 08.01.2013


Ответы (2)


Я наконец нашел проблему: генератор градиента.

Я предполагал, что функция random () передаст свое двоичное значение в массив grads [], попутно охватывая весь диапазон чисел с плавающей запятой. К сожалению, это было не так: его возвращаемое значение сначала преобразовывалось в число с плавающей запятой, а затем сохранялось в массиве. Моя самая большая проблема заключалась в том, что все сгенерированные векторы имели положительные значения членов.

Это оправдало артефакты блоков: было много «холмов» (высокие значения), генерируемых рядом друг с другом, но не было «долин» (низкие значения), и два соседних холма в конечном итоге столкнулись бы и генерировали линии вдоль целочисленных значений.

Осознав это, я попытался немного манипулировать указателями и сохранять значения непосредственно в форме Uint32, но значения в градиентах стали дурацкими (infs, NaNs, 1.0s и 0.0s полностью), поэтому я вернулся к исходному route и отменил числа в самом коде.

Этот 7-лайнер решил всю проблему:

int y=random()&7;
if(y&1)
    grads[3*x]*=-1.0f;
if(y&2)
    grads[3*x+1]*=-1.0f;
if(y&4)
    grads[3*x+2]*=-1.0f;

Просто поместите его до или после функции нормализации и готово.

Теперь он выглядит как шум Перлина: Шум Перлина, по крайней мере.

И фрактальная сумма тоже выглядит немного лучше: Фрактальная сумма улучшена

@DiJuMx: Я видел статью «Улучшение шума» раньше, но не понимал, насколько градиенты повлияют на появление шума. Кроме того, попытка изменить пространство координат с 0 ~ 256 на 0 ~ 1 привело к тому, что фрактальная сумма больше не работала, и в результирующем изображении были те же блочные артефакты.

person MVittiS    schedule 15.01.2013
comment
Спасибо за продолжение. Мне удалось совершить ту же ошибку (создать градиенты только с положительными векторными компонентами), и теперь я чувствую себя немного глупо. - person Kalle; 04.03.2014

Это проблема исходной реализации шума Перлина.

У него есть статья об этом здесь

Во время вычисления градиента в целочисленных координатах один или несколько используемых векторов будут равны 0, следовательно, общий градиент будет равен 0. В результате вы получите сетку линий в целочисленных координатах.

Один из способов исправить это - изменить пространство координат от 0 до 1, а не от 0 до 512.

Другой способ - реализовать исправление как описатель в его статье.

Или, наконец, не используйте исходный шум Перлина, вместо этого переключитесь на симплексный шум, который он также разработал, paper здесь, а объяснение здесь .

person DiJuMx    schedule 12.01.2013
comment
Я понимаю, что исходный шум имеет некоторые ограничения, такие как несовершенная изотропия, но он не должен быть таким очевидным, как краевые блоки в моем случае. Даже на его веб-сайте (noisemachine.com) изображения шума недостаточно блочны, чтобы сказать, что это общий дефект классического шума Перлина. По крайней мере, его статья о симплексном шуме содержит эталонную реализацию на Java. В будущем я постараюсь переписать его. - person MVittiS; 14.01.2013