terrain - Perlin generación de ruido para el terreno
perlin-noise (4)
Aquí hay un ejemplo de generación de superficie que escribí hace un tiempo en JavaScript usando 3D Perlin Noise. Como en una superficie los vóxeles están presentes o no, simplemente aplico un umbral después de calcular el cubo de Ruido Perlin. En el ejemplo, la probabilidad de ruido es igual para todas las dimensiones. Puedes obtener un paisaje más realista cuando aumentas los valores aleatorios hacia el suelo y lo reduces hacia el cielo.
http://kirox.de/test/Surface.html
WebGL debe estar habilitado. En el momento de escribir esto, recomiendo usar Chrome para obtener el mejor rendimiento.
Estoy tratando de implementar algún código fuente que encontré en online para generar un mapa de altura usando Perlin Noise. Logré obtener el mapa de altura con la función noise3, y la tercera coordenada es una "semilla" aleatoria, para permitir mapas de altura aleatorios.
Mi problema es que el terreno generado es bastante aburrido: quiero montañas y estoy rodando pastizales. He leído algo sobre Perlin Noise (principalmente here ). Debido al código fuente que he encontrado obviamente no escrito con legibilidad en mente y mi débil comprensión del concepto de Perlin Noise en general, no puedo entender qué necesito ajustar en el código (¿amplitud y frecuencia?) Para crear un terreno más drástico.
Más información sobre la generación de mapas de altura usando Perlin Noise, Perlin Noise en general, o incluso algún código más descifrable también sería bienvenido.
EDITAR: entiendo (tipo de) cómo funciona Perlin Noise, por ejemplo, con respecto a la amplitud y la frecuencia, me pregunto qué variables cambiar en el código que he vinculado anteriormente, que se utilizan para estos dos aspectos.
El ruido Perlin está completamente controlado por las diferentes variables que establece, es decir, amplitud, frecuencia y persistencia. La cantidad de octavas tiene un pequeño cambio, pero no mucho. En el código que he escrito en el pasado, acabo de jugar con el orden de magnitud de la frecuencia y la persistencia hasta que obtuve lo que necesitaba. Puedo tratar de encontrar mi fuente anterior si es necesario.
PerlinNoise.h
#pragma once
class PerlinNoise
{
public:
// Constructor
PerlinNoise();
PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);
// Get Height
double GetHeight(double x, double y) const;
// Get
double Persistence() const { return persistence; }
double Frequency() const { return frequency; }
double Amplitude() const { return amplitude; }
int Octaves() const { return octaves; }
int RandomSeed() const { return randomseed; }
// Set
void Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed);
void SetPersistence(double _persistence) { persistence = _persistence; }
void SetFrequency( double _frequency) { frequency = _frequency; }
void SetAmplitude( double _amplitude) { amplitude = _amplitude; }
void SetOctaves( int _octaves) { octaves = _octaves; }
void SetRandomSeed( int _randomseed) { randomseed = _randomseed; }
private:
double Total(double i, double j) const;
double GetValue(double x, double y) const;
double Interpolate(double x, double y, double a) const;
double Noise(int x, int y) const;
double persistence, frequency, amplitude;
int octaves, randomseed;
};
PerlinNoise.cpp
#include "PerlinNoise.h"
PerlinNoise::PerlinNoise()
{
persistence = 0;
frequency = 0;
amplitude = 0;
octaves = 0;
randomseed = 0;
}
PerlinNoise::PerlinNoise(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
persistence = _persistence;
frequency = _frequency;
amplitude = _amplitude;
octaves = _octaves;
randomseed = 2 + _randomseed * _randomseed;
}
void PerlinNoise::Set(double _persistence, double _frequency, double _amplitude, int _octaves, int _randomseed)
{
persistence = _persistence;
frequency = _frequency;
amplitude = _amplitude;
octaves = _octaves;
randomseed = 2 + _randomseed * _randomseed;
}
double PerlinNoise::GetHeight(double x, double y) const
{
return amplitude * Total(x, y);
}
double PerlinNoise::Total(double i, double j) const
{
//properties of one octave (changing each loop)
double t = 0.0f;
double _amplitude = 1;
double freq = frequency;
for(int k = 0; k < octaves; k++)
{
t += GetValue(j * freq + randomseed, i * freq + randomseed) * _amplitude;
_amplitude *= persistence;
freq *= 2;
}
return t;
}
double PerlinNoise::GetValue(double x, double y) const
{
int Xint = (int)x;
int Yint = (int)y;
double Xfrac = x - Xint;
double Yfrac = y - Yint;
//noise values
double n01 = Noise(Xint-1, Yint-1);
double n02 = Noise(Xint+1, Yint-1);
double n03 = Noise(Xint-1, Yint+1);
double n04 = Noise(Xint+1, Yint+1);
double n05 = Noise(Xint-1, Yint);
double n06 = Noise(Xint+1, Yint);
double n07 = Noise(Xint, Yint-1);
double n08 = Noise(Xint, Yint+1);
double n09 = Noise(Xint, Yint);
double n12 = Noise(Xint+2, Yint-1);
double n14 = Noise(Xint+2, Yint+1);
double n16 = Noise(Xint+2, Yint);
double n23 = Noise(Xint-1, Yint+2);
double n24 = Noise(Xint+1, Yint+2);
double n28 = Noise(Xint, Yint+2);
double n34 = Noise(Xint+2, Yint+2);
//find the noise values of the four corners
double x0y0 = 0.0625*(n01+n02+n03+n04) + 0.125*(n05+n06+n07+n08) + 0.25*(n09);
double x1y0 = 0.0625*(n07+n12+n08+n14) + 0.125*(n09+n16+n02+n04) + 0.25*(n06);
double x0y1 = 0.0625*(n05+n06+n23+n24) + 0.125*(n03+n04+n09+n28) + 0.25*(n08);
double x1y1 = 0.0625*(n09+n16+n28+n34) + 0.125*(n08+n14+n06+n24) + 0.25*(n04);
//interpolate between those values according to the x and y fractions
double v1 = Interpolate(x0y0, x1y0, Xfrac); //interpolate in x direction (y)
double v2 = Interpolate(x0y1, x1y1, Xfrac); //interpolate in x direction (y+1)
double fin = Interpolate(v1, v2, Yfrac); //interpolate in y direction
return fin;
}
double PerlinNoise::Interpolate(double x, double y, double a) const
{
double negA = 1.0 - a;
double negASqr = negA * negA;
double fac1 = 3.0 * (negASqr) - 2.0 * (negASqr * negA);
double aSqr = a * a;
double fac2 = 3.0 * aSqr - 2.0 * (aSqr * a);
return x * fac1 + y * fac2; //add the weighted factors
}
double PerlinNoise::Noise(int x, int y) const
{
int n = x + y * 57;
n = (n << 13) ^ n;
int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff;
return 1.0 - double(t) * 0.931322574615478515625e-9;/// 1073741824.0);
}
La amplitud controla qué tan alto / bajo es el terreno, la frecuencia con que fluye, con una frecuencia más baja que fluye más.
Así que si quieres un paisaje montañoso irregular necesitas subir ambos.
Un amigo simplemente me vinculó a esta pregunta, y pensé que trataría de aclarar un par de cosas que no se abordan en la respuesta aceptada.
El artículo interesante y útil de Elias usa "ruido de valor" y no "ruido de Perlin". El ruido de valor implica el ajuste de curva de puntos aleatorios. El ruido de gradiente (del cual el ruido de Perlin es un ejemplo primario) crea un enrejado de puntos de 0 valores y le da a cada uno un gradiente aleatorio. ¡Con frecuencia se confunden el uno con el otro!
http://en.wikipedia.org/wiki/Gradient_noise
En segundo lugar, usar un tercer valor como semilla es costoso. Si desea un terreno aleatorio, considere traducir su origen en una cantidad aleatoria. Las llamadas 3D serán más costosas que las llamadas 2D. Y todo lo que está haciendo es usar el valor z para seleccionar una porción particular de ruido 2D.
En tercer lugar, la llamada de función directa va a devolver valores que son bastante suaves y en general, no tan escarpados como el terreno real, ya que su aleatoriedad se limita a una sola frecuencia. Para obtener un terreno más árido, una buena técnica es sumar varias llamadas que progresan a través del espacio de ruido a diferentes frecuencias, por lo general establecen valores "fractales".
Por lo tanto, por ejemplo, suma el noise(x, y) + (1/2)(noise(x*2, y*2) + (1/4)(noise(x*4, y*4)
...
La suma resultante probablemente estará a menudo fuera del rango -1 a 1, por lo que tendrá que normalizar el resultado antes de que los valores sean útiles. Me gustaría sugerir que se configuren las series de factores (1, 1/2, 1/4, etc.) para garantizar que permanezcan dentro de [-1, 1], lo que se puede hacer por ponderación progresiva dependiendo de cuántos ''octavas'' que usa. (Pero no sé si esta es realmente la forma más eficiente de hacer esto).
Ejemplo con cuatro octavas: (1/15)(noise(x, y) + (2/15)(noise(2x, 2y) + (4/15)(noise(4x, 4y) + (8/15)(noise(8x, 8y)
Luego, use la normalización de "ruido turbulento" de tomar la suma y hacerla = |sum|
(es decir, usando la función abs). Esto le dará al terreno crestas de valle angulares definidas en lugar de rodar suavemente.
Estoy trabajando en un visualizador SimplexNoise, espero abrirlo en GitHub eventualmente, como un proyecto Java. Puede encontrar un primer borrador del visualizador y ejecutarlo a través de este post en java-gaming.org: http://www.java-gaming.org/topics/simplex-noise-experiments-towards-procedural-generation/27163/view.html El énfasis en el primer borrador es más tutorial, con ejemplos de códigos generados (pero están en Java).
Gran artículo sobre cómo funciona SimplexNoise (y Perlin vs Gradient background): http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
Stefan Gustavson hizo un muy buen trabajo de esto!