Как вы генерируете мозаичный шум Перлина?

127

Связанный:

Я хотел бы генерировать мозаичный шум Перлина. Я работаю с функциями Пола Бурка 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 ;

Дает небо как

nontileable

Который не является мозаичным.

Значения пикселя: 0-> 256 (ширина и высота), а пиксель (0,0) использует (x, y) = (0,0), а пиксель (256,256) использует (x, y) = (1,1)

Как я могу сделать это мозаичным?

bobobobo
источник
14
Просто к вашему сведению, что у вас есть, это не шум Перлина; это фрактальный шум. Шум Перлина, вероятно, является функцией «noise2», генерирующей каждую октаву фрактального шума.
Натан Рид

Ответы:

80

Есть две части, чтобы создать плавный шум 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))

Шум Перлина генерируется суммированием маленьких «серфлетов», которые являются произведением случайно ориентированного градиента и разделяемой полиномиальной функции спада. Это дает положительный регион (желтый) и отрицательный регион (синий)

ядро

Сурфлеты имеют размер 2х2 и центрированы на целочисленных точках решетки, поэтому значение шума Перлина в каждой точке пространства получается путем суммирования сурфлетов в углах ячейки, которую он занимает.

суммирование

Если вы сделаете направление градиента обтеканием с некоторым периодом, то сам шум будет незаметно обернутым с тем же периодом. Вот почему приведенный выше код принимает координату решетки по модулю периода, прежде чем хешировать его через таблицу перестановок.

Другим шагом является то, что при суммировании октав вы хотите масштабировать период с частотой октавы. По сути, вы хотите, чтобы каждая октава отображала все изображение только один раз, а не несколько раз:

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")

Бесшовное шумоподавление

Как вы можете видеть, это действительно плавно:

fBm шум, плиточный

С небольшим изменением цвета и отображением цветов, вот изображение облака размером 2x2:

Облака!

Надеюсь это поможет!

буджума
источник
3
Я не парень с питоном, поэтому я спрашиваю, как x*2**oконвертировать в C? это: x*pow(2,o)или pow(x*2,o)?
Idev
7
x*pow(2, o), поскольку возведение в степень имеет более высокий приоритет, чем умножение.
Джон Калсбек
1
может кто-то преобразовать это в C? У меня огромные проблемы с пониманием этого кода, так как я никогда не делал ничего с Python. например, что такое aценность? и я не уверен, как функции преобразуются в C ... я получаю прямые линии только на выходе.
Idev
1
Это определенно лучшее решение, если вы согласны с тем, чтобы область вашего шума была привязана к форме вашей плитки. Например, это не допускает произвольных поворотов. Но если вам не нужна такая вещь, это идеальный ответ.
Джон Калсбек
1
Примечание: если вы хотите сгенерировать размер, отличный от 128, НЕ меняйте числовые значения в строке im.putdata(data, 128, 128). (Для тех, кто не знаком с Python или PIL: они означают масштаб и смещение, а не размер изображения.)
Антти Киссаниеми
87

Вот один довольно умный способ, который использует шум 4D Perlin.

По сути, сопоставьте координату X вашего пикселя с 2D-окружностью, а координату Y вашего пикселя - со вторым 2D-кругом, и поместите эти два круга, ортогональные друг другу в 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
Джон Калсбек
источник
3
Это определенно правильный ответ. Добавить размеры это старый математический трюк. Olinde Rodrigues docet (сэр WR Гамильтон тоже, но немного меньше)
FxIII
@FxIII, вы знаете, как этот Noise4D () должен быть реализован? Я хотел бы попробовать это, но я понятия не имею, как этот Noise4D () должен работать.
Idev
4
Вы можете использовать любую функцию 4D шума. Симплексный шум будет моей рекомендацией. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
Джон Калсбек
2
спасибо Джон! Получил это работает, сладкий! никто не сказал этого, но: x1, y1, x2, y2, кажется, какое-то масштабирование, большее расстояние, детальный шум. если это кому-нибудь поможет
Idev
5
Обратите внимание, что это топологически эквивалентно ответу бобобо: ваше отображение встраивает 2-тор в ℝ⁴, что возможно без метрических искажений, которые вы неизбежно получаете при встраивании его в ℝ³.
оставил около
22

Ладно, я понял. Ответ заключается в том, чтобы ходить в торе в 3D-шуме, создавая из него 2D-текстуру.

Торус обертывает 2 dirs

Код:

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

Результаты:

Однажды:

наклонное небо

И выложены плиткой:

показывая это плитки

bobobobo
источник
6
Это вроде работает, но похоже, что вы получаете кучу искажений из-за искривления тора.
Натан Рид
1
Вы действительно можете просто по модулю позиции, но я люблю все удивительные / креативные ответы на этот вопрос. Так много разных способов сделать одно и то же.
я заметил, что вы на самом деле не хотите использовать 0-1 значения, но 0-0.9999 ... значения! поэтому вы должны использовать: x / width, y / height и т. д., иначе швы не совпадают (делает противоположные края точно такими же пикселями). также похоже, что функция PerlinNoise3D () также нуждается в ограничении для значения результата или переполнения некоторых значений пикселей.
Idev
@ Натан, ты знаешь, как исправить искажение?
Idev
2
@idev Я считаю, что способ исправить искажение заключается в использовании метода 4D в верхнем ответе на этот вопрос. ;)
Натан Рид
16

Один простой способ, который я могу придумать, состоит в том, чтобы взять вывод функции шума и отразить / перевернуть его в изображение, которое в два раза больше. Это трудно объяснить, поэтому вот изображение: введите описание изображения здесь

Теперь, в этом случае, совершенно очевидно, что вы делали, когда смотрите на это. Я могу думать о двух способах (возможно :-)) решить это:

  1. Вы могли бы сделать это увеличенное изображение, а затем генерировать еще больше шума поверх него, но (и я не уверен, если это возможно) сфокусировано к середине (таким образом, края остаются прежними). Это может добавить дополнительную разницу, которая заставит ваш мозг думать, что это не просто зеркальное отображение.

  2. (Я также не уверен, если это возможно) Вы можете попробовать поиграть со входами в функцию шума, чтобы сгенерировать исходное изображение по-другому. Вы должны будете сделать это методом проб и ошибок, но обратите внимание на функции, которые привлекают ваше внимание, когда вы их накладываете / зеркально отражаете, а затем пытаетесь заставить их не генерировать их.

Надеюсь это поможет.

Ричард Марскелл - Дракир
источник
3
Очень красиво, но слишком симметрично!
Бобобобо
1
@bobobobo Это то, что я думал, другие шаги облегчили бы. Таким образом, вы можете сгенерировать «базу» с помощью этого метода, а затем добавить еще несколько деталей, чтобы все выглядело так, как будто оно (так) не отражено.
Ричард Марскелл - Дракир
Вы начинаете получать некоторые странные модели, когда вы делаете такие вещи. Этот, в частности, выглядит как бабочка. Простое решение, хотя.
Нотлеш
Это был и мой первый подход, но у него есть проблема, которая видна здесь: dl.dropbox.com/u/6620757/noise_seam.png Когда вы пересекаете перевернутую границу, вы вызываете дизъюнкт в функции шума, мгновенно инвертируя наклон функция. Даже если вы примените вторую шумовую функцию сверху, она все равно может быть видна на выходе.
Джерико
Отличная идея. Это можно легко сделать в пиксельном шейдере с помощью волновой функции треугольника :tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou
10

Первая версия этого ответа на самом деле была неправильной, я обновил ее

Метод, который я успешно использовал, состоит в создании мозаичного домена . Другими словами, сделайте свою базовую 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.

Неважно
источник
Но как вы делаете noise2периодические (с коротким периодом, например, 1 единица)? Я думаю, что это то, что в конечном итоге задает вопрос. Стандартный шум Перлина является периодическим с периодом 256 на каждой оси, но вы хотите изменить шум с меньшим периодом.
Натан Рид
@ Натан Рид Если вы звоните , noise2как предложено, вы будете получать периодические результаты, будь то сама функция периодической или нет. Потому что аргументы обертывают каждую 1 единицу.
Nevermind
1
Но тогда вы получаете швы на линиях сетки, не так ли? Поскольку нет никаких гарантий, что noise2 (0, 0,999) - это что-то близкое к noise2 (0, 0), если я что-то не пропустил.
Натан Рид
1
@ Натан Рид Это хороший момент. На самом деле, я просто перепроверил свой старый код, и оказалось, что я ошибался. Я сейчас отредактирую ответ.
Nevermind
Большой! Это на самом деле хороший ответ сейчас. +1 :)
Натан Рид
6

Другой альтернативой является генерация шума с использованием библиотек libnoise. Вы можете создавать шум на теоретически бесконечном пространстве, без проблем.

Посмотрите на следующее: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

Существует также порт 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 - это функция, вызываемая для получения разделов в каждом направлении, которые будут бесшовно соединяться с остальными текстурами.

Конечно, этот метод более дорогостоящий, чем просто наличие одной текстуры, которую можно использовать на нескольких поверхностях. Если вы хотите создать несколько случайных мозаичных текстур, это может вас заинтересовать.

jgallant
источник
6

Хотя здесь есть некоторые ответы, которые будут работать, большинство из них сложные, медленные и проблемные.

Все, что вам действительно нужно сделать, это использовать функцию периодической генерации шума. Это оно!

Отличная реализация общественного достояния, основанная на «продвинутом» алгоритме шума Perlin, может быть найдена здесь . Вам нужна функция pnoise2. Код был написан Стефаном Густавсоном, который сделал заостренный комментарий здесь именно о этой проблеме, и как другие взяли неправильный подход. Послушай Густавсона, он знает, о чем говорит.

Что касается различных сферических проекций, которые некоторые здесь предложили: они, по сути, работают (медленно), но они также создают двухмерную текстуру, которая является сплюснутой сферой, так что края будут более уплотненными, что, вероятно, приведет к нежелательному эффекту. Конечно, если вы намереваетесь проецировать 2D-текстуру на сферу, это путь, но это не то, о чем просили.

Таль Лирон
источник
4

Вот намного более простой способ сделать плиточный шум:

черепица перлин из шадерого кода

Вы используете модульную обертку для каждой шкалы шума. Они соответствуют краям области независимо от того, какую частотную шкалу вы используете. Так что вам нужно использовать только обычный 2D шум, который намного быстрее. Вот живой код WebGL, который можно найти на ShaderToy: https://www.shadertoy.com/view/4dlGW2

Три верхние функции выполняют всю работу, и fBM передается вектор х / у в диапазоне от 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);
}
Krondike
источник
1
Ссылка на ваше изображение обесточена. Я сделал правильный выбор и заменил его снимком экрана с выводом кода, который вы опубликовали. Если это не правильно, пожалуйста, повторно загрузите нужное изображение прямо на сервер Stack Exchange.
Пикалек
3

У меня были некоторые неплохие результаты, интерполированные около краев плитки (обернутые по краям), но это зависит от того, какого эффекта вы пытаетесь достичь, и точных параметров шума. Прекрасно работает с немного размытым шумом, не очень хорошо с остроконечными / мелкозернистыми.

kaoD
источник
0

Я проверял эту ветку в поисках ответа на похожую проблему, а затем получил чистое и компактное решение от разработчика этого кода на Python для генерации фрактального шума из перлин / симплексного шума. Обновленный код представлен в этом (закрытом) выпуске и может быть возобновлен при установке градиентов для правой стороны «генератора», равных градиентам на левой стороне (и одинаковым для верхней и нижней частей), например, в

# Gradients
angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
# Make the noise tileable
gradients[-1,:] = gradients[0,:]
gradients[:,-1] = gradients[:,0]

Похоже на элегантное и чистое решение, я избегаю копирования всего кода здесь (поскольку это не мое собственное решение), но оно доступно по ссылке, приведенной выше. Надеюсь, что это может быть полезно для тех, кто хочет создать мозаичное фрактальное 2d-изображение, подобное тому, которое мне было нужно, без артефактов или искажений.

мозаичный фрактальный ландшафт

Стефано
источник