C++ 为什么在梯度噪声发生器中从Mersenne捻线器切换到其他PRNG会产生不好的结果?

C++ 为什么在梯度噪声发生器中从Mersenne捻线器切换到其他PRNG会产生不好的结果?,c++,random,noise,perlin-noise,procedural-generation,C++,Random,Noise,Perlin Noise,Procedural Generation,我一直在尝试创建一个通用的梯度噪声生成器(它不使用哈希方法来获取梯度)。代码如下: class GradientNoise { std::uint64_t m_seed; std::uniform_int_distribution<std::uint8_t> distribution; const std::array<glm::vec2, 4> vector_choice = {glm::vec2(1.0, 1.0), glm::vec2(-1.

我一直在尝试创建一个通用的梯度噪声生成器(它不使用哈希方法来获取梯度)。代码如下:

class GradientNoise {
    std::uint64_t m_seed;
    std::uniform_int_distribution<std::uint8_t> distribution;
    const std::array<glm::vec2, 4> vector_choice = {glm::vec2(1.0, 1.0), glm::vec2(-1.0, 1.0), glm::vec2(1.0, -1.0),
                                                    glm::vec2(-1.0, -1.0)};

public:
    GradientNoise(uint64_t seed) {
        m_seed = seed;
        distribution = std::uniform_int_distribution<std::uint8_t>(0, 3);
    }

    // 0 -> 1
    // just passes the value through, origionally was perlin noise activation
    double nonLinearActivationFunction(double value) {
        //return value * value * value * (value * (value * 6.0 - 15.0) + 10.0);
        return value;
    }

    // 0 -> 1
    //cosine interpolation
    double interpolate(double a, double b, double t) {
        double mu2 = (1 - cos(t * M_PI)) / 2;
        return (a * (1 - mu2) + b * mu2);
    }

    double noise(double x, double y) {
        std::mt19937_64 rng;
        //first get the bottom left corner associated
        // with these coordinates
        int corner_x = std::floor(x);
        int corner_y = std::floor(y);

        // then get the respective distance from that corner
        double dist_x = x - corner_x;
        double dist_y = y - corner_y;

        double corner_0_contrib; // bottom left
        double corner_1_contrib; // top left
        double corner_2_contrib; // top right
        double corner_3_contrib; // bottom right

        std::uint64_t s1 = ((std::uint64_t(corner_x) << 32) + std::uint64_t(corner_y) + m_seed);
        std::uint64_t s2 = ((std::uint64_t(corner_x) << 32) + std::uint64_t(corner_y + 1) + m_seed);
        std::uint64_t s3 = ((std::uint64_t(corner_x + 1) << 32) + std::uint64_t(corner_y + 1) + m_seed);
        std::uint64_t s4 = ((std::uint64_t(corner_x + 1) << 32) + std::uint64_t(corner_y) + m_seed);


        // each xy pair turns into distance vector from respective corner, corner zero is our starting corner (bottom
        // left)
        rng.seed(s1);
        corner_0_contrib = glm::dot(vector_choice[distribution(rng)], {dist_x, dist_y});

        rng.seed(s2);
        corner_1_contrib = glm::dot(vector_choice[distribution(rng)], {dist_x, dist_y - 1});


        rng.seed(s3);
        corner_2_contrib = glm::dot(vector_choice[distribution(rng)], {dist_x - 1, dist_y - 1});


        rng.seed(s4);
        corner_3_contrib = glm::dot(vector_choice[distribution(rng)], {dist_x - 1, dist_y});


        double u = nonLinearActivationFunction(dist_x);
        double v = nonLinearActivationFunction(dist_y);


        double x_bottom = interpolate(corner_0_contrib, corner_3_contrib, u);
        double x_top = interpolate(corner_1_contrib, corner_2_contrib, u);
        double total_xy = interpolate(x_bottom, x_top, v);
        return total_xy;
    }
};
类梯度噪声{
标准:uint64_t m_种子;
标准:均匀分布;
const std::array vector_choice={glm::vec2(1.0,1.0),glm::vec2(-1.0,1.0),glm::vec2(1.0,-1.0),
glm::vec2(-1.0,-1.0)};
公众:
梯度噪声(uint64_t种子){
m_seed=种子;
分布=标准:均匀分布(0,3);
}
// 0 -> 1
//只是通过传递值,最初是柏林噪声激活
双非线性函数(双值){
//返回值*值*值*(值*(值*6.0-15.0)+10.0);
返回值;
}
// 0 -> 1
//余弦插值
双插值(双a、双b、双t){
双mu2=(1-cos(t*M_PI))/2;
返回(a*(1-mu2)+b*mu2);
}
双噪声(双x,双y){
标准:mt19937\U 64 rng;
//首先将左下角关联起来
//用这些坐标
内角x=标准::地板(x);
内角y=std::楼层(y);
//然后得到从该角的相应距离
双距离x=x-角点x;
双距离y=y-转角y;
双角_0_contrib;//左下角
双角_1_contrib;//左上角
双角_2_contrib;//右上角
双角_3_contrib;//右下角

std::uint64_t s1=((std::uint64_t(拐角处)您看到的是PRNG质量的实际演示。Mersenne Twister是性能最好的PRNG之一,它通过了DIEHARD测试。您应该知道生成随机数不是一项简单的计算任务,因此寻找更好的性能将不可避免地导致质量差。LCG已知o最简单和最差的PRNG,它清楚地显示了二维相关性,如图所示。Xorshift生成器的质量在很大程度上取决于比特度和参数。它们肯定比Mersenne捻线器差,但有些(Xorshift 128+)可能工作良好,可以通过TestU01的BigCrush电池测试

换句话说,如果你正在做一个重要的物理模型数值实验,你最好继续使用Mersenne Twister,因为它是速度和质量之间的一个很好的折衷方案,并且在许多标准库中都有。在一个不太重要的情况下,你可以尝试使用xorshift128+生成器。为了获得最终结果,你需要使用cryptog图形质量PRNG(此处提及的任何一个都不能用于加密目的)。

您(不知不觉地?)实现了PRNG非随机模式的可视化。这看起来非常酷


除Mersenne捻线机外,所有经过测试的PRNG似乎都不适合您的用途。由于我自己没有做进一步的测试,我只能建议您尝试并测量进一步的PRNG。

已知LCG不适合您的用途

Xorshift128+的结果很糟糕,因为它需要良好的种子设定。而提供良好的种子设定违背了使用它的全部目的。我不推荐这样做

但是,我建议使用整数散列

这是该页面第一次散列的结果,在我看来还行,而且速度很快(我认为它比Mersenne Twister快得多):

以下是我编写的代码,用于生成:

#include <cmath>
#include <stdio.h>

unsigned int hash(unsigned int a) {
    a = (a ^ 61) ^ (a >> 16);
    a = a + (a << 3);
    a = a ^ (a >> 4);
    a = a * 0x27d4eb2d;
    a = a ^ (a >> 15);
    return a;
}

unsigned int ivalue(int x, int y) {
    return hash(y<<16|x)&0xff;
}

float smooth(float x) {
    return 6*x*x*x*x*x - 15*x*x*x*x + 10*x*x*x;
}

float value(float x, float y) {
    int ix = floor(x);
    int iy = floor(y);
    float fx = smooth(x-ix);
    float fy = smooth(y-iy);

    int v00 = ivalue(iy+0, ix+0);
    int v01 = ivalue(iy+0, ix+1);
    int v10 = ivalue(iy+1, ix+0);
    int v11 = ivalue(iy+1, ix+1);
    float v0 = v00*(1-fx) + v01*fx;
    float v1 = v10*(1-fx) + v11*fx;
    return v0*(1-fy) + v1*fy;
}

unsigned char pic[1024*1024];

int main() {
    for (int y=0; y<1024; y++) {
        for (int x=0; x<1024; x++) {
            float v = 0;

            for (int o=0; o<=9; o++) {
                v += value(x/64.0f*(1<<o), y/64.0f*(1<<o))/(1<<o);
            }

            int r = rint(v*0.5f);

            pic[y*1024+x] = r;
        }
    }

    FILE *f = fopen("x.pnm", "wb");
    fprintf(f, "P5\n1024 1024\n255\n");
    fwrite(pic, 1, 1024*1024, f);
    fclose(f);
}
#包括
#包括
无符号整数散列(无符号整数a){
a=(a^61)^(a>>16);
a=a+(a>4);
a=a*0x27d4eb2d;
a=a^(a>>15);
返回a;
}
无符号整数(整数x,整数y){

返回散列(y已知LCG的随机性对其参数的选择非常敏感。特别是,它与m参数相关-最多它将是m(你的主因子)&对于许多值,它可能更小

同样,需要仔细选择参数以获得PRNGs

您已经注意到,一些PRNG提供了很好的程序生成结果,而另一些则没有。为了找出原因,我将排除proc gen内容并直接检查PRNG输出。可视化数据的一个简单方法是构建灰度图像,其中每个像素值都是一个(可能是缩放的)随机值。对于基于图像的内容,我发现这是一种查找可能导致视觉伪影的内容的简单方法。您看到的任何伪影都可能导致proc gen输出出现问题


另一种选择是尝试类似的方法。如果前面提到的图像测试没有发现任何问题,我可能会使用它来确保我的PRNG技术是可信的。

请注意,您的代码为PRNG种子,然后从PRNG生成一个伪随机数。
xorshift128+
中的非随机性原因如下:所发现的是,在更改种子状态(查看其状态)之前,
xorshift128+
只需将种子的两半相加(并使用结果mod 264作为生成的数字)。这使得PRNG与散列函数有很大的不同。

顺便说一句,为什么rng是类成员而不是自动变量?您正在使用PRNG作为一个非常昂贵的散列函数。尝试使用实际(加密?)散列函数来代替。@yurikilochek我该怎么做?@snb什么不清楚?请传递您的种子(或直接使用坐标)通过哈希函数,从结果中选择两位来选择您的vectors@snb:您的最后一次迭代是什么(10000次迭代)图片展示?你的意思是,你播种了xorshift,然后你生成并忽略了10000个数字,然后你使用了10001?然后,你甚至用这些模式得到了这张图片?不要夸大你的情况,LCG并不是有史以来设计的最简单和最差的PRNG。冯·诺依曼r
#include <cmath>
#include <stdio.h>

unsigned int hash(unsigned int a) {
    a = (a ^ 61) ^ (a >> 16);
    a = a + (a << 3);
    a = a ^ (a >> 4);
    a = a * 0x27d4eb2d;
    a = a ^ (a >> 15);
    return a;
}

unsigned int ivalue(int x, int y) {
    return hash(y<<16|x)&0xff;
}

float smooth(float x) {
    return 6*x*x*x*x*x - 15*x*x*x*x + 10*x*x*x;
}

float value(float x, float y) {
    int ix = floor(x);
    int iy = floor(y);
    float fx = smooth(x-ix);
    float fy = smooth(y-iy);

    int v00 = ivalue(iy+0, ix+0);
    int v01 = ivalue(iy+0, ix+1);
    int v10 = ivalue(iy+1, ix+0);
    int v11 = ivalue(iy+1, ix+1);
    float v0 = v00*(1-fx) + v01*fx;
    float v1 = v10*(1-fx) + v11*fx;
    return v0*(1-fy) + v1*fy;
}

unsigned char pic[1024*1024];

int main() {
    for (int y=0; y<1024; y++) {
        for (int x=0; x<1024; x++) {
            float v = 0;

            for (int o=0; o<=9; o++) {
                v += value(x/64.0f*(1<<o), y/64.0f*(1<<o))/(1<<o);
            }

            int r = rint(v*0.5f);

            pic[y*1024+x] = r;
        }
    }

    FILE *f = fopen("x.pnm", "wb");
    fprintf(f, "P5\n1024 1024\n255\n");
    fwrite(pic, 1, 1024*1024, f);
    fclose(f);
}