Як ви генеруєте плитковий шум Перліна?


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 ;

Дає небо як

нерозбірливий

Що не є плитковим.

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

Як я можу зробити його плитковим?


14
Просто FYI, те, що у вас там, - це не шум Перліна; це фрактальний шум. Перліновий шум, ймовірно, функція "шум2", що генерує кожну октаву фрактального шуму.
Натан Рід

Відповіді:


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

Як бачите, це дійсно плитка:

fBm Шум, черепиця

Ось невелике налаштування та кольорове відображення, ось хмарне зображення, нанесене плиткою 2х2:

Хмари!

Сподіваюся, це допомагає!


3
я не хлопець-пітон, тому я запитую, як x*2**oперетворюється на C? це: x*pow(2,o)або pow(x*2,o)?
ідев

7
x*pow(2, o), оскільки експоненція має вищий пріоритет, ніж множення.
Джон Калсбек

1
хтось може перетворити це на C? У мене величезні проблеми з розумінням цього коду, так як я ніколи нічого не робив з python. наприклад, що таке aцінність? і я не впевнений, як функції перетворюються на C ... я отримую прямі лінії лише у виході.
ідев

1
Це, безумовно, найкраще рішення до тих пір, поки ви не будете добре, якщо область вашого шуму буде прив’язана до форми вашої плитки. Наприклад, це не дозволяє довільних поворотів. Але якщо вам нічого такого не потрібно, це ідеальна відповідь.
Джон Калсбек

1
Примітка: якщо ви хочете генерувати інші розміри, ніж 128, НЕ змінюйте числових значень у рядку im.putdata(data, 128, 128). (Для незнайомих з пітоном чи PIL: вони означають масштаб та зміщення, а не розмір зображення.)
Antti Kissaniemi

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
Це, безумовно, правильна відповідь. Додавання розмірів - старий трюк математика. Доцет Олінде Родрігес (сер WR Hamilton docet, але трохи менше)
FxIII

@FxIII, чи знаєте ви, як слід реалізувати цей Noise4D ()? Я хотів би спробувати це, але у мене немає поняття, як цей Noise4D () повинен працювати.
ідев

4
Ви можете використовувати будь-яку 4D функцію шуму. Моя рекомендація - симплексний шум. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
Джон Калсбек

2
дякую Джон! спрацював, милий! ніхто не сказав цього, але: x1, y1, x2, y2 здається, що це якесь масштабування, чим більша відстань, детальний шум. якщо це комусь допоможе.
ідев

5
Зауважте, що це топологічно еквівалентно відповіді bobobobo: ваше відображення вбудовує 2-торус у ℝ⁴, що можливо без метричних спотворень, які ви невідчутно отримуєте, вкладаючи його в ℝ³.
Ліворуч близько

22

Добре. Я зрозумів. Відповідь - ходити в торі в 3D-шумі, генеруючи з нього 2D текстуру.

тора обгортає 2 др

Код:

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

Результати:

Один раз:

доступне небо

І плиткою:

показуючи це плитки


6
Це якось працює, але, схоже, ви отримуєте купу спотворень через викривлення тору.
Натан Рід

1
Ви дійсно можете просто модулювати позицію, але я люблю всі приголомшливі / креативні відповіді на це питання. Стільки різних способів зробити те саме.

я помітив, що ви насправді не хочете використовувати значення 0-1, але значення 0-0,9999 ...! тож ви б використовували: х / ширина, у / висота тощо. інакше шви не збігаються (робить протилежні краї точно такими ж пікселями). також виглядає, що функція PerlinNoise3D () також потребує затискання для значення результату, або деякі значення пікселів переповнюються.
ідев

@Nathan, ти знаєш, як виправити спотворення?
ідев

2
@idev Я вважаю, що спосіб виправити спотворення - це використовувати метод 4D у верхній відповіді на це питання. ;)
Натан Рід

16

Один простий спосіб, який я можу придумати, - це взяти на виході функцію шуму і віддзеркалити / перевернути її на зображення, яке в два рази перевищує розмір. Це важко пояснити, ось ось таке зображення: введіть тут опис зображення

Зараз у цьому випадку цілком очевидно, що ви робили, дивлячись на це. Я можу придумати два способи (можливо :-)) вирішити це:

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

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

Сподіваюсь, це допомагає.


3
Дуже приємно, але занадто симетрично!
бобобобо

1
@bobobobo Ось що я думав, інші кроки полегшать. Таким чином, ви можете створити "базу" за допомогою цього методу, а потім додати ще трохи деталей у всій справі, щоб вона виглядала так, що це не (так) дзеркальне відображення.
Річард Марскелл - Дракір

Ви починаєте отримувати якісь дивні візерунки, коли робите подібні речі. Цей, зокрема, схожий на метелика. Хоча просте рішення.
нотлеш

Це був мій перший підхід, але він має проблему, помітну тут: dl.dropbox.com/u/6620757/noise_seam.png Коли ви переходите межу фліп, ви викликаєте нерозбірливість функції шуму, негайно перевернувши нахил функція. Навіть якщо ви застосуєте другу функцію шуму вгорі, вона все ще може бути помітна на виході.
Jherico

Чудова ідея. Це легко зробити в піксельній шейдері за допомогою трикутної хвильової функції: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. Таким чином, значення за цілими координатами будуть повторюватися через кожну одиницю періоду, а інтерполяція забезпечить гладку функцію.

Однак зауважте, що це працює лише тоді, коли Період перевищує 1. Отже, щоб реально використовувати це для створення безшовних текстур, вам доведеться вибирати квадрат Період x Період, а не 1х1.


Але як ви робите noise2періодичні (з коротким періодом, таким як 1 одиниця)? Я думаю, що саме це питання в кінцевому підсумку задається. Стандартний шум Перліна періодичний з періодом 256 на кожній осі, але ви хочете, щоб змінився шум з меншим періодом.
Натан Рід

@ Натан Рід Якщо ви телефонуєте , noise2як запропоновано, ви будете отримувати періодичні результати, будь то сама функція періодичної чи ні. Тому що аргументи обертаються кожні 1 одиниця.
забудьте

1
Але тоді ви отримуєте шви на лініях сітки, чи не так? Оскільки немає гарантії, що шум2 (0, 0,999) є чимось поблизу шуму2 (0, 0), якщо я щось не пропустив.
Натан Рід

1
@Nathan Reed Це хороший момент. Насправді я просто перевірив старий код і виявилося, що я помилявся. Я зараз відредагую відповідь.
забудьте

Чудово! Це насправді хороша відповідь. +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 - це функція для виклику для отримання розділів у кожному напрямку, які легко з’єднуються з рештою текстур.

Звичайно, цей спосіб є більш затратним, ніж просто мати єдину текстуру, яку можна використовувати на різних поверхнях. Якщо ви хочете створити деякі випадкові текстури з плиткою, це може вас зацікавити.


6

Хоча тут є деякі відповіді, які б спрацювали, більшість з них є складними, повільними та проблемними.

Все, що вам потрібно зробити - це використовувати функцію періодичного генерування шуму. Це воно!

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

Щодо різних сферичних проекцій, які деякі з них пропонують: ну, вони по суті працюють (повільно), але вони також створюють 2D текстуру, яка є сплющеною сферою, так що краї будуть більш згущеними, ймовірно, створюючи небажаний ефект. Звичайно, якщо ви збираєтеся для 2D текстури для проектування на сферу, це шлях, але це не те , що в даний час просили.


4

Ось набагато простіший спосіб зробити кахельний шум:

кахельний шум перлін від коду шадертої

Ви використовуєте модульне обгортання для кожної шкали шуму. Вони підходять до країв незалежно від того, яку частоту ви використовуєте. Тому вам доведеться використовувати лише звичайний 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);
}

1
Ваше посилання на зображення знищилось. Я найкраще здогадався і замінив його знімком екрана виходу з опублікованого вами коду shadertoy. Якщо це неправильно, повторно завантажте призначене зображення безпосередньо на сервер Stack Exchange.
Пікалек

3

У мене були непогані результати інтерполяції біля країв плитки (обклеєні краєм), але це залежить від того, якого ефекту ви намагаєтеся досягти та точних параметрів шуму. Відмінно спрацьовує для дещо розмитого шуму, не дуже гарний із колосками / дрібнозернистими.


0

Я перевіряв цю тему в пошуках відповіді на подібну проблему, тоді я отримав чисте і компактне рішення від розробника цього коду python для генерування фрактального шуму від шуму perlin / simplex. Оновлений код надається в цьому (закритому) випуску і може бути відновлений при встановленні градієнтів для правої частини "генератора", рівних тим, які знаходяться з лівої сторони (і однакових для верхньої та нижньої), наприклад у

# 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, подібне до цього, яке мені потрібно, без артефактів чи спотворень.

фрактальна рельєфна місцевість

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.