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