Как вы создаете чередующийся шум Perlin?
по теме:
Я бы хотел создать чередующийся шум Perlin. Я работаю из функций Пола Боурка PerlinNoise*()
, которые например:
// alpha is the "division factor" (how much to damp subsequent octaves with (usually 2))
// beta is the factor that multiplies your "jump" into the noise (usually 2)
// n is the number of "octaves" to add in
double PerlinNoise2D(double x,double y,double alpha,double beta,int n)
{
int i;
double val,sum = 0;
double p[2],scale = 1;
p[0] = x;
p[1] = y;
for (i=0;i<n;i++) {
val = noise2(p);
sum += val / scale;
scale *= alpha;
p[0] *= beta;
p[1] *= beta;
}
return(sum);
}
Использование кода:
real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test
return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;
Дает небо, как
Что не является тлируемым.
Значения пикселей равны 0-> 256 (ширина и высота), а пиксель (0,0) использует (x, y) = (0,0), а пиксель (256, 256) использует (x, y) = ( 1,1)
Как я могу сделать его черепичным?
9 ответов
Есть две части, чтобы сделать бесшовно плетеный шум fBm, как это. Во-первых, вам нужно сделать функцию шума Perlin самой плиткой. Вот несколько Python-кода для простой функции шума Perlin, которая работает с любым периодом до 256 (вы можете тривиально расширять ее так, как вам нравится, изменяя первый раздел):
import random
import math
from PIL import Image
perm = range(256)
random.shuffle(perm)
perm += perm
dirs = [(math.cos(a * 2.0 * math.pi / 256),
math.sin(a * 2.0 * math.pi / 256))
for a in range(256)]
def noise(x, y, per):
def surflet(gridX, gridY):
distX, distY = abs(x-gridX), abs(y-gridY)
polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
hashed = perm[perm[int(gridX)%per] + int(gridY)%per]
grad = (x-gridX)*dirs[hashed][0] + (y-gridY)*dirs[hashed][1]
return polyX * polyY * grad
intX, intY = int(x), int(y)
return (surflet(intX+0, intY+0) + surflet(intX+1, intY+0) +
surflet(intX+0, intY+1) + surflet(intX+1, intY+1))
Перлин-шум генерируется из суммирования небольших «серпухов», которые являются произведением произвольно ориентированного градиента и разделимой функции полиномиального спада. Это дает положительную область (желтый) и отрицательную область (синий)
Поверхности имеют протяженность 2x2 и сосредоточены на целых точках решетки, поэтому значение шума Перлина в каждой точке пространства создается путем суммирования серпухов в углах ячейки, которые он занимает.
Если вы сделаете перенос направлений градиента с некоторым периодом, сам шум будет автоматически завершен с тем же периодом. Вот почему приведенный выше код берет координату решетки по модулю периода до того, как он хэширует ее через таблицу перестановок.
Другим шагом является то, что при суммировании октав вам нужно масштабировать период с частотой октавы. По сути, вы захотите, чтобы каждая октава разбивала всего одно изображение раз, а не несколько раз:
def fBm(x, y, per, octs):
val = 0
for o in range(octs):
val += 0.5**o * noise(x*2**o, y*2**o, per*2**o)
return val
Поместите это вместе, и вы получите что-то вроде этого:
size, freq, octs, data = 128, 1/32.0, 5, []
for y in range(size):
for x in range(size):
data.append(fBm(x*freq, y*freq, int(size*freq), octs))
im = Image.new("L", (size, size))
im.putdata(data, 128, 128)
im.save("noise.png")
Как вы можете видеть, это действительно гладко:
С небольшим изменением и цветовым отображением, вот изображение облака, разбитое 2x2:
Надеюсь, это поможет!
Вот один довольно умный способ, который использует 4D Perlin noise.
В основном, сопоставьте координату X вашего пикселя с двумерным кругом и координатой Y вашего пикселя со вторым двумерным кругом и поместите эти два круга, ортогональные друг другу в пространстве 4D. Полученная текстура является плетёной, не имеет явных искажений и не повторяется так, как зеркальная текстура будет.
Скопируйте код из статьи:
for x=0,bufferwidth-1,1 do
for y=0,bufferheight-1,1 do
local s=x/bufferwidth
local t=y/bufferheight
local dx=x2-x1
local dy=y2-y1
local nx=x1+cos(s*2*pi)*dx/(2*pi)
local ny=y1+cos(t*2*pi)*dy/(2*pi)
local nz=x1+sin(s*2*pi)*dx/(2*pi)
local nw=y1+sin(t*2*pi)*dy/(2*pi)
buffer:set(x,y,Noise4D(nx,ny,nz,nw))
end
end
Хорошо, я понял. Ответ заключается в том, чтобы пройти в torus в 3D-шуме, создав из него 2D-текстуру.
код:
Color Sky( double x, double y, double z )
{
// Calling PerlinNoise3( x,y,z ),
// x, y, z _Must be_ between 0 and 1
// for this to tile correctly
double c=4, a=1; // torus parameters (controlling size)
double xt = (c+a*cos(2*PI*y))*cos(2*PI*x);
double yt = (c+a*cos(2*PI*y))*sin(2*PI*x);
double zt = a*sin(2*PI*y);
double val = PerlinNoise3D( xt,yt,zt, 1.5, 2, 12 ) ; // torus
return val*val*cloudWhite + 2*val*(1-val)*gray + (1-val)*(1-val)*skyBlue ;
}
Результаты:
Когда:
И плиточный:
Один простой способ, я могу думать о том, чтобы взять вывод функции шума и зеркало /перевернуть его в изображение, которое в два раза больше. Трудно объяснить, так вот изображение:
Первая версия этого ответа была фактически неправильной, я обновил ее
Метод, который я использовал успешно, делает шум доменом черепичным. Другими словами, сделайте свою базовую функцию noise2()
периодической. Если noise2()
является периодическим, а beta
- целочисленным, в результате шум будет иметь тот же период, что и noise2()
.
Как сделать noise2()
периодическим? В большинстве реализаций эта функция использует некоторый шум решетки. То есть, он получает случайные числа в целых координатах и интерполирует их. Например:
function InterpolatedNoise_1D(float x)
integer_X = int(x)
fractional_X = x - integer_X
v1 = SmoothedNoise1(integer_X)
v2 = SmoothedNoise1(integer_X + 1)
return Interpolate(v1 , v2 , fractional_X)
end function
Эта функция может быть тривиально модифицирована, чтобы стать периодической с целым периодом. Просто добавьте одну строку:
integer_X = integer_X % Period
перед вычислением v1
и v2
. Таким образом, значения в целых координатах будут повторяться каждый период единиц, а интерполяция гарантирует, что результирующая функция будет гладкой.
Обратите внимание, однако, что это работает только тогда, когда Period больше 1. Итак, чтобы использовать это при создании бесшовных текстур, вам нужно будет выбрать квадрат Period x Period, а не 1x1.
Другой альтернативой является создание шума с использованием библиотек libnoise. Вы можете легко создавать шум над теоретическим бесконечным пространством.
Посмотрите на следующее: http://libnoise.sourceforge.net/tutorials/tutorial3.html# плитка
Существует также порт XNA выше: http://bigblackblock.com/tools/libnoisexna
Если вы закончите использовать порт XNA, вы можете сделать что-то вроде этого:
Perlin perlin = new Perlin();
perlin.Frequency = 0.5f; //height
perlin.Lacunarity = 2f; //frequency increase between octaves
perlin.OctaveCount = 5; //Number of passes
perlin.Persistence = 0.45f; //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;
//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);
//Get a section
_map.GeneratePlanar(left, right, top, down);
GeneratePlanar - это функция вызова, чтобы получить разделы в каждом направлении, которые будут легко соединяться с остальными текстурами.
Конечно, этот метод является более дорогостоящим, чем просто наличие единой текстуры, которая может использоваться на нескольких поверхностях. Если вы хотите создать некоторые случайные плиткие текстуры, это может быть чем-то, что вас интересует.
Хотя здесь есть некоторые ответы, которые будут работать, большинство из них сложны, медленны и проблематичны.
Все, что вам действительно нужно сделать, это использовать функцию генерации шума периодическая . Вот и все!
Отличная реализация общедоступного домена, основанная на первичном алгоритме «продвинутого» шума, может быть найдена здесь . Вам нужна функция pnoise2. Код был написан Стефаном Густавсоном, который сделал острый комментарий здесь о том, как именно эта проблема, и как другие ошибались. Слушайте Густавсона, он знает, о чем говорит.
В отношении различных сферических проекций, которые некоторые из них предложили: ну, они по существу работают (медленно), но они также создают двумерную текстуру, которая является сплющенной сферой, так что края будут более уплотненными, что, вероятно, приведет к нежелательному эффекту. Конечно, если вы намереваетесь для того, чтобы ваша 2D-текстура была проецирована на сферу, это путь, но это не то, о чем просили.
Вот гораздо более простой способ сделать черепичный шум: 2D Плиточный шум http://imageshack.us/a/img41/6294/nvkb.jpg
Вы используете модульную оболочку для каждой шкалы шума. Они соответствуют краям области независимо от того, какую частоту вы используете. Таким образом, вам нужно использовать обычный 2D-шум, который намного быстрее. Вот живой код WebGL, который можно найти в ShaderToy: https://www.shadertoy.com/view/4dlGW2
Три функции выполняют всю работу, а fBM передается вектор x /y в диапазоне от 0.0 до 1.0.
// Tileable noise, for creating useful textures. By David Hoskins, Sept. 2013.
// It can be extrapolated to other types of randomised texture.
#define SHOW_TILING
#define TILES 2.0
//----------------------------------------------------------------------------------------
float Hash(in vec2 p, in float scale)
{
// This is tiling part, adjusts with the scale...
p = mod(p, scale);
return fract(sin(dot(p, vec2(35.6898, 24.3563))) * 353753.373453);
}
//----------------------------------------------------------------------------------------
float Noise(in vec2 x, in float scale )
{
x *= scale;
vec2 p = floor(x);
vec2 f = fract(x);
f = f*f*(3.0-2.0*f);
//f = (1.0-cos(f*3.1415927)) * .5;
float res = mix(mix(Hash(p, scale),
Hash(p + vec2(1.0, 0.0), scale), f.x),
mix(Hash(p + vec2(0.0, 1.0), scale),
Hash(p + vec2(1.0, 1.0), scale), f.x), f.y);
return res;
}
//----------------------------------------------------------------------------------------
float fBm(in vec2 p)
{
float f = 0.4;
// Change starting scale to any integer value...
float scale = 14.0;
float amp = 0.55;
for (int i = 0; i < 8; i++)
{
f += Noise(p, scale) * amp;
amp *= -.65;
// Scale must be multiplied by an integer value...
scale *= 2.0;
}
return f;
}
//----------------------------------------------------------------------------------------
void main(void)
{
vec2 uv = gl_FragCoord.xy / iResolution.xy;
#ifdef SHOW_TILING
uv *= TILES;
#endif
// Do the noise cloud (fractal Brownian motion)
float bri = fBm(uv);
bri = min(bri * bri, 1.0); // ...cranked up the contrast for no reason.
vec3 col = vec3(bri);
#ifdef SHOW_TILING
vec2 pixel = (TILES / iResolution.xy);
// Flash borders...
if (uv.x > pixel.x && uv.y > pixel.y // Not first pixel
&& (fract(uv.x) < pixel.x || fract(uv.y) < pixel.y) // Is it on a border?
&& mod(iGlobalTime-2.0, 4.0) < 2.0) // Flash every 2 seconds
{
col = vec3(1.0, 1.0, 0.0);
}
#endif
gl_FragColor = vec4(col,1.0);
}
У меня были некоторые не плохие результаты, интерполирующие вблизи краев плитки (обернутые края), но это зависит от того, какой эффект вы пытаетесь достичь, и точных параметров шума. Отлично подходит для слегка расплывчатого шума, не очень хорошего со спикеем /мелкозернистыми.