whn*_*whn 5 c++ random procedural-generation noise perlin-noise
我一直在尝试创建一个广义的渐变噪声生成器(它不使用散列方法来获得渐变).代码如下:
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;
}
};
Run Code Online (Sandbox Code Playgroud)
然后我生成一个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);
}
}
Run Code Online (Sandbox Code Playgroud)
哪个效果很好:
但gprof告诉我,梅森捻线机占用了62.4%的时间并且随着纹理的增加而增长.没有其他任何人可以接近任何时间.虽然Mersenne twister在初始化后很快,但每次使用它时我初始化它的事实似乎使它变得非常慢.
此初始化是100%所需的,以确保相同的x和y在每个整数点生成相同的渐变(因此您需要每次都使用散列函数或种子RNG).
我试图将PRNG更改为线性同余生成器和Xorshiftplus,虽然两者都运行速度快了几个数组,但它们给出了奇怪的结果:
Xorshiftplus
我试过了:
在使用输出之前运行发生器几次,这导致执行缓慢或仅仅是不同的伪像.
在初始种子之后使用两次连续运行的输出再次播种PRNG并使用该值之后的值.结果没有区别.
怎么了?我能做些什么来获得与mersenne twister相同质量的更快结果?
OK BIG UPDATE:
我不知道为什么会这样,我知道它与使用的素数有关,但是在稍微弄乱之后,似乎以下工作:
步骤1,将x和y值分别合并为种子(并将其他一些偏移值或其他种子值与它们结合起来,这个数字应该是一个主要/非平凡的因素)
步骤2,使用这两个种子结果再次将发生器播种回功能(所以就像geza说的那样,制作的种子很糟糕)
步骤3,当得到结果时,不是使用模数(4)试图获得,或者&3,先用素数对结果进行模数,然后应用&3.我不确定素数是否为mersenne主要与否.
这是使用prime = 257和xorshiftplus的结果!(注意我在2048年使用了2048这个,其他的是256乘256)
已知 LCG 不足以满足您的目的。
Xorshift128+ 的结果很糟糕,因为它需要良好的播种。提供良好的播种违背了使用它的全部目的。我不推荐这个。
但是,我建议使用整数哈希。例如,来自Bob 页面的一个。
这是该页面的第一个哈希的结果,对我来说看起来不错,而且速度很快(我认为它比 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);
}
Run Code Online (Sandbox Code Playgroud)
如果您想了解哈希函数如何工作(或者更好的是,一个好的哈希具有哪些属性),请查看 Bob 的页面,例如this。