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;
}
};
我会生成一个OpenGL纹理来展示它,就像这样:
int width = 1024;
int height = 1024;
unsigned char *temp_texture = new unsigned char[width*height * 4];
double octaves[5] = {2,4,8,16,32};
for( int i = 0; i < height; i++){
for(int j = 0; j < width; j++){
double d_noise = 0;
d_noise += temp_1.noise(j/octaves[0], i/octaves[0]);
d_noise += temp_1.noise(j/octaves[1], i/octaves[1]);
d_noise += temp_1.noise(j/octaves[2], i/octaves[2]);
d_noise += temp_1.noise(j/octaves[3], i/octaves[3]);
d_noise += temp_1.noise(j/octaves[4], i/octaves[4]);
d_noise/=5;
uint8_t noise = static_cast<uint8_t>(((d_noise * 128.0) + 128.0));
temp_texture[j*4 + (i * width * 4) + 0] = (noise);
temp_texture[j*4 + (i * width * 4) + 1] = (noise);
temp_texture[j*4 + (i * width * 4) + 2] = (noise);
temp_texture[j*4 + (i * width * 4) + 3] = (255);
}
}
哪些能够得到好的结果:
但是 gprof 告诉我 Mersenne twister 占用了 62.4% 的时间,并且随着纹理的增大而增长。没有其他单独的部分需要花费那么多时间。虽然 Mersenne twister 在初始化后很快,但每次使用它时都要初始化它,这似乎使它变得相当慢。这种初始化对于确保相同的 x 和 y 在每个整数点生成相同的渐变是 100% 必需的(因此您需要哈希函数或每次播种 RNG)。
我尝试将 PRNG 更改为线性同余发生器和 Xorshiftplus,虽然两者运行速度都快了几个数量级,但结果很奇怪:
LCG(一次,然后在使用之前运行 5 次)
![enter image description here](https://istack.dev59.com/noKdW.webp)
Xorshiftplus
我已尝试:
在使用输出之前多次运行生成器,结果执行速度较慢或不同的工件。
使用初始种子后两个连续运行的输出来重新播种PRNG并在此后使用该值。结果没有差异。
发生了什么?如何获得与mersenne twister相同质量的更快结果?
大更新:
我不知道为什么这样可以运行,我知道它与使用的质数有关,但是经过一些尝试,似乎以下方法有效:
第一步,将x和y值分别作为种子合并(并与它们结合一些其他偏移值或附加种子值,这个数应该是质数/非平凡因子)
第二步,将这两个种子结果再次用于生成器的种子中,然后再次进入函数(就像geza所说,所做的种子很糟糕)
第三步,在获取结果时,不要使用模数项数(4)尝试获取,或者& 3,首先将结果模以质数,然后应用& 3。我不确定梅森质数是否重要。
以下是使用prime = 257和xorshiftplus的结果!(请注意,我在这个示例中使用了2048 x 2048,而其他示例则为256 x 256)