Стаття Науки про Землю · ≈ ⏱ 11 хв читання

Гідравлічна ерозія: як річки формують гори

Сирий heightmap із шуму Перліна (згенерована комп'ютером карта висот із випадковим, але плавним шумом) виглядає як зім'ята фольга — горбистий, але мертвий. Гідравлічна ерозія — це алгоритм, який перетворює його на переконливий ландшафт: симульовані краплі дощу підхоплюють наноси на шляху вниз, вирізають долини й осідають там, де вода сповільнюється, — той самий фізичний процес, що сформував кожну річкову дельту та каньйон на Землі.

Коротко: Гідравлічна ерозія симулює краплі дощу, що котяться вниз по heightmap, підхоплюючи наноси там, де вода тече швидко, і осаджуючи їх там, де потік сповільнюється. Повторивши це для сотень тисяч крапель, алгоритм автоматично вирізає реалістичні V-подібні долини, гострі хребти й розгалужені річкові мережі з плаского одноманітного шуму — без жодних ручних правил рельєфу.

1. Чому самого шуму недостатньо

Heightmap, згенерований лише з шуму Перліна / fBm, має фатальну візуальну ваду: кожен пагорб виглядає так само, як і будь-який інший. Реальний рельєф анізотропний — вода завжди тече вниз найкрутішим шляхом, тож гори накопичують гострі хребти й річкові долини, вирізані за мільйони років, тоді як рельєф лише з шуму не має жодної «напрямної пам'яті».

Гідравлічна ерозія виправляє це, буквально симулюючи рух води по рельєфу, яка підхоплює і осаджує наноси на своєму шляху. Прогнавши кілька сотень тисяч симульованих крапель дощу над heightmap, хребти загострюються, долини поглиблюються, а деревоподібні річкові мережі виникають повністю з фізики — без ручних правил розгалуження.

Дві родини алгоритмів

Моделі на основі сітки (Musgrave та ін., 1989) відстежують об'єм води й наносів у кожній комірці heightmap і легко запускаються на GPU. Моделі на основі часток/крапель (Beyer, 2015; Hjelle, 2017) симулюють окремі краплі дощу, що котяться вниз, — простіші в реалізації, і саме на цьому підході зосереджена ця стаття.

2. Модель ерозії краплями

Кожна крапля — це маленька симульована частка з позицією, напрямком, швидкістю, об'ємом води та вантажем наносів. На кожному кроці вона:

  • Зчитує висоту та градієнт рельєфу під собою (білінійна інтерполяція між 4 сусідніми комірками сітки)
  • Оновлює свій напрямок — частково слідуючи градієнту вниз, частково зберігаючи попередній імпульс (інерція)
  • Робить один крок у цьому напрямку
  • Обчислює, скільки наносів вода здатна нести за своєї нової швидкості (ємність наносів)
  • Або розмиває рельєф (якщо нижче ємності), або осаджує наноси (якщо вище ємності)
  • Втрачає частину води через випаровування
Оновлення напрямку (з інерцією) dir = dir · inertia − ∇h(pos) · (1 − inertia)

де ∇h — градієнт рельєфу в позиції краплі, а inertia ∈ [0, 1] (типово 0.05) обмінює чутливість до градієнта на плавніші, природніші русла

Крапля «вмирає», коли повністю випаровується, стікає за межі мапи або перевищує максимальний час життя (типово 30–60 кроків). Для ерозії всієї мапи симулюють мільйони таких дешевих незалежних крапель.

3. Ємність наносів і рівняння транспорту

Основна фізична ідея, запозичена з реальної флювіальної геоморфології: швидка глибока вода несе більше наносів, ніж повільна мілка. Ємність наносів краплі на кожному кроці:

Ємність наносів C = max(−Δh, minSlope) · speed · water · Kc

де: Δh — зміна висоти на кроці (від'ємна = вниз), minSlope — нижня межа, що не дає ємності занулитись на пласкій ділянці, speed — швидкість краплі, water — залишок води, Kc — константа ємності наносів (параметр налаштування)

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

4. Ерозія, осадження та випаровування

Порівняння поточного вантажу наносів s з ємністю C визначає, чи крапля розмиває, чи осаджує:

Правило ерозії / осадження якщо s > C: осадити (s − C) · depositRate наносів на 4 сусідні комірки (білінійні ваги)

якщо s < C: розмити min((C − s) · erodeRate, −Δh) з рельєфу під краплею, розподіливши по невеликому радіусу пензля, і додати до s

Ерозія через «пензель» (м'який круговий спад, типово радіус 2–4 комірки), а не в одну точку, уникає гострих одноточкових артефактів ранніх реалізацій і дає плавніші стінки русел.

Випаровування water(t + 1) = water(t) · (1 − evaporateRate)

типове evaporateRate ≈ 0.01 за крок — крапля поступово висихає, змушуючи її осадити рештки наносів перед тим, як зникнути

5. Теплова ерозія — осипи та щебінь

Сама лише гідравлічна ерозія може залишати схили крутіші, ніж здатен витримати будь-який реальний матеріал, — прямовисні уступи з пухкого ґрунту під 80°. Реальні схили також формуються тепловою (гравітаційною) ерозією: цикли замерзання-відтавання та гравітація повільно зсувають пухкий матеріал донизу, доки кожен схил не досягне кута природного укосу — максимального нахилу, який здатен утримувати сипкий матеріал, перш ніж посипатись.

Прохід теплової ерозії (осипу) для кожної комірки c із сусідом n:
  якщо slope(c, n) > tanThresholdAngle:
    Δmove = (slope − threshold) · c · settlingRate
    перенести Δmove матеріалу з c до n

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

6. Псевдокод

Повне життя однієї краплі (спрощено за реалізацією Hjelle, 2017):

function simulateDroplet(heightmap, startPos):
  pos = startPos
  dir = { x: 0, y: 0 }
  speed = 1
  water = 1
  sediment = 0

  for step in 0..maxLifetime:
    // 1. Зчитати висоту + градієнт (білінійно)
    [height, gradient] = sampleHeightAndGradient(heightmap, pos)

    // 2. Оновити напрямок з інерцією, потім рухатись
    dir = dir * inertia - gradient * (1 - inertia)
    dir = normalize(dir)
    pos += dir

    if outOfBounds(pos): break

    // 3. Обчислити різницю висот після руху
    newHeight = sampleHeight(heightmap, pos)
    deltaH = newHeight - height

    // 4. Ємність наносів на цьому кроці
    capacity = max(-deltaH, minSlope) * speed * water * Kc

    if sediment > capacity or deltaH > 0:
      // Осадити надлишок наносів
      amount = deltaH > 0
        ? min(deltaH, sediment)
        : (sediment - capacity) * depositRate
      sediment -= amount
      deposit(heightmap, pos, amount)
    else:
      // Розмити рельєф, обмежено доступним перепадом висоти
      amount = min((capacity - sediment) * erodeRate, -deltaH)
      erode(heightmap, pos, amount, brushRadius)
      sediment += amount

    // 5. Оновити швидкість і випаровування
    speed = sqrt(speed*speed + deltaH * gravity)
    water *= (1 - evaporateRate)

    if water < minWaterThreshold: break

7. Масштабування: ерозія на сітці та GPU

Модель крапель вище паралелізується легко по краплях, але дві краплі, що пишуть в одну комірку одночасно, — це стан гонки, незручний для GPU. Два поширені рішення:

  • Пакетні CPU-краплі: симулювати краплі послідовно на CPU (або невеликими партіями без перетину), як роблять більшість офлайн-інструментів для рельєфу (World Machine, Gaea) для heightmap 1024×1024 за кілька секунд.
  • Ерозія на сітці — модель мілкої води (Mei, Decaudin & Hu, 2007): замість дискретних крапель зберігається неперервне поле висоти води по всій сітці, яке крокує вперед за спрощеним рівнянням мілкої води — це природно лягає на WebGL fragment shaders / compute shaders і розмиває весь рельєф паралельно кожен кадр.
Практичне правило

Для рельєфу в реальному часі в браузері (як у WebGL-демо на цьому сайті) стандартний вибір — ерозія мілкої води на сітці як пінг-понг прохід render-target: краплинну ерозію легше зрозуміти, але важче паралелізувати на GPU.

▶ Демо наживо

🏔️ Спробуйте симуляцію рельєфу

Інтерактивний WebGL-рельєф, побудований на шарах шуму Перліна — досліджуйте хребти й долини, які вирізав би алгоритм ерозії з цієї статті.

Відкрити симуляцію →

🔗 Схожі симуляції

🏔️Рельєф 🌍Тектонічні плити 🌊Океан 💧Рідина