【问题标题】:"Blocky" Perlin noise“块状”柏林噪音
【发布时间】:2012-12-21 19:49:59
【问题描述】:

我最近一直在尝试在 C 中实现 Perlin 噪声生成器(基于 Ken Perlin's website,使用 SDL 库作为屏幕输出),但输出显示插值块之间的边缘不连续或不平滑 -插值块确实表现为块。

我尝试了四种插值,所有“平滑”的看起来都差不多;相比之下,只有余弦看起来(非常)稍微好一点,而直线看起来很可怕。 (以下是余弦和线性)

具有讽刺意味的是,如果对噪声进行分形总和(我的最终目的),线性会在“块状”方面消除平滑插值,实际上看起来几乎没有问题。

我很确定我的代码中缺少某些东西或做错了,但我似乎找不到它。

关于什么(或什么条件)可能导致这些块伪影的任何建议?

作为参考,我目前的代码如下:

#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;
}

用法:跑步时,按住向上或向下键可放大或缩小噪声网格。

【问题讨论】:

  • 我建议你更清楚地提出这个问题,也许在它之前使用一个“2级”标题## Question。您可能应该包含一个指向 Ken Perlin 网站的链接(最好是您正在使用的特定页面)。诚然,它可能并不难找到,但如果人们不必进入他们选择的搜索引擎,它会改善您的问题。
  • 已编辑和链接。非常感谢您提出的改进问题的建议。
  • 您是否有理由要自己实现噪声生成器,而不是使用已经存在的诸如优秀和模块化的libnoise
  • 只是为了学习。以后我一定会检查这个库的源代码。

标签: c perlin-noise


【解决方案1】:

我终于找到了问题所在:梯度生成器。

我假设 random() 函数会将其二进制值传递给 grads[] 数组,从而覆盖整个浮点数范围。不幸的是,情况并非如此:它的返回值首先被转换为浮点数,然后存储在数组中。我最大的问题是所有生成的向量都有正的成员值

这证明了块伪影是合理的:有很多“山”(高值)彼此相邻生成,但没有“山谷”(低值),两个相邻的山最终会发生冲突并沿着整数值。

意识到这一点后,我尝试做一些指针杂耍并直接以 Uint32 形式存储值,但是渐变中的值变得古怪(infs、NaNs、1.0s 和 0.0s 一路),所以我回来了到原始路线并否定代码本身中的数字。

这个 7-liner 解决了整个问题:

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;

只需将它放在规范化函数之前或之后,就完成了

现在看起来像 Perlin Noise:

分形和看起来也好一点:

@DiJuMx:我之前看过“提高噪声”论文,但没有意识到渐变会对噪声外观产生多大影响。此外,尝试将坐标空间从 0~256 更改为 0~1 会导致分形和不再起作用,并且生成的图像具有相同的块伪影。

【讨论】:

  • 感谢您的跟进。我设法犯了同样的错误(只用正向量分量生成渐变),现在我觉得有点傻。
【解决方案2】:

这是 Perlin 噪声的原始实现的问题。

他有一篇论文here

在整数坐标处的梯度计算过程中,使用的一个或多个向量将为 0,因此整体梯度将为 0。因此,您会在整数坐标处得到一个线网格。

解决此问题的一种方法是让坐标空间从 0 变为 1,而不是从 0 变为 512。

另一种方法是按照他的paper 中的描述实施修复。

或者最后,不要使用原始的 Perlin Noise,而是改用他还开发的 Simplex Noise,论文 here 和解释 here

【讨论】:

  • 我知道原始噪声有一些限制,例如不完美的各向同性,但在我的情况下它不应该像边缘块那样明显。即使在他的网站 (noisemachine.com) 上,噪声图像也不足以说这是经典 Perlin 噪声的常见缺陷。至少,他关于单纯形噪声的论文包含 Java 中的参考实现。我会在以后尝试重写它。
猜你喜欢
  • 2012-06-25
  • 1970-01-01
  • 2012-07-22
  • 2011-11-22
  • 2023-03-04
  • 1970-01-01
  • 2013-07-17
  • 2021-04-29
  • 2012-01-29
相关资源
最近更新 更多