ГоловнаСтаттіGLSL

Gerstner Хвилі в GLSL: Океанічна Вода у Вершинному Шейдері

Трохоїди, дисперсійна відношення, межі крутизни, піна та Френеля — і де переходить контроль швидкісного спектра (FFT) океанської хвилі.

mysimulator teamОновлено — червень 2026≈ 9 хв читання▶ Відкрити симуляцію

Чому синусоїда не схожа на море

Найзручніший спосіб анімувати океан – це вертикальне зміщення плоского меша сумою синусів. Це працює, і воно виглядає як желе. Справжній штиль має різкі гребені та широкі, рівні западини; синусоїда має гребінь і западину точно однакової форми. Причина полягає в фізичній: у глибоководному хвилі вода не рухається разом із хвилею, вона обертається. Пакунок води підіймається, рухається вперед під гребенем, опускається та віддаляється назад під западиною, майже повертаючись до того ж місця, де він почав. Тільки форма поширюється.

Це коловий рух, який кодує Gerstner wave, і тому гребінці стискаються. Це не графічна химера: Gerstner (або трохоїдальна) хвиля була опублікована Франтішком Йосифом Gerstner у 1802 році та є одним із небагатьох точних нелінійних розв’язків рівнянь для безмасової, незмінного потоку нескінченної глибини.

жива демонстрація · пов'язана симуляція● LIVE

Трохоїд

Герстне хвиля зміщує вершину горизонтально та вертикально. У вершинному шейдері, для плоскої точки сітки p, одна хвиля напряму D (одиничний вектор у горизонтальному плані), амплітуди A, довжини хвилі k та кутова частота ω:

float phase = k * dot(D, p.xz) - omega * time; vec3 disp; disp.xz = -D * A * sin(phase); // горизонтальна — це те, що загострює гребені disp.y = A * cos(phase); // вертикальна position = p + disp;

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

Параметри пов’язані між собою дисперсійним відношенням для глибоководних хвиль, яке стверджує, що довгі хвилі рухаються швидше за короткі: k = 2π / L // L = довжина хвилі omega = sqrt(g * k) // g = 9.81 м/с² (глибоководні) c = omega / k = sqrt(g / k) // швидкість фази Вибір ω замість виведення його з k є найпоширенішою помилкою при ручному створенні океанського шейдера: хвилі рухаються з однаковою швидкістю, спектр не розділяється і поверхня виглядає як механічно керована хвильова басейн, а не море.

float  phase = k * dot(D, p.xz) - omega * time;
vec3   disp;
disp.xz = -D * A * sin(phase);   // horizontal — this is what sharpens crests
disp.y  =      A * cos(phase);   // vertical
position = p + disp;

Кривизна, та хвиля, що з’їдає себе

Амплитуда сама по собі не є правильним керуванням. Значення, яке має сенс – це кривизна, S = A · k. При S = 0 поверхня рівна; коли S зростає, гребені стають гострішими. При S = 1 гребінь стає ідеальним витком під кутом 0°, та при S > 1 поверхня самоперетинається – сітка згинається через себе, і ви отримуєте невід’ємний стиснутий, викривлений артефакт вздовж ліній гребенів. Хвилі Герстера не мають концепції руйнування; вони просто перевертаються всередині.

Отже, авторство здійснюється через кривизну, а не амплітуду, та загальна сума планується на всій хвильовій послідовності:

A_i = S_i / k_i // кривизна → амплітуда, на кожній хвилі Σ (A_i * k_i) < 1 // тримайте суму нижче 1 для всіх хвиль // (практично бюджет ~0.6–0.8 для безпеки) Для порівняння, справжні океанські хвилі руйнуються при кривизні значно нижчій за математичний ліміт – класична Stokes limit відповідає куту гребеня 120°, а не витку – тому фізично правдоподібного моря ніколи не буде)

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

A_i = S_i / k_i                 // steepness → amplitude, per wave
Σ (A_i * k_i) < 1               // keep the SUM below 1 across all waves
                                // (in practice budget ~0.6–0.8 for safety)

Нормалі, піна та Френель

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

// для кожного хвиль, накопичуйте в тангент T і бінормаль B T += vec3(-D.x*D.x * (S * sin(phase)), D.x * (S * cos(phase)), -D.x*D.y * (S * sin(phase))); B += vec3(-D.x*D.y * (S * sin(phase)), D.y * (S * cos(phase)), -D.y*D.y * (S * sin(phase))); normal = normalize(cross(B, T));

Піна розміщується там, де поверхня стискається — там, де горизонтальне зміщення стискає вершини разом, тобто там, де якобіан карти вибіювання падає до нуля. Це відбувається точно на гострих піках, де з'являються білі хвилі в реальності, тому маску пены, керовану якобіаном (або, більш дешево, вертикальною складовою зміщення об’єднаною зі схильністю), розміщують у правильному місці без додаткових витрат.

Нарешті, вода є поверхнею Френеля. Дивлячись прямо вниз, ви бачите переважно рефракцію — колір водного тіла та те, що знаходиться під ним. Дивлячись на горизонт під кутом падіння, ви бачите майже чисту відбиття неба. Приближення Шліка захоплює перехід в одній рядку, з індексом заломлення приблизно 1,33, F₀ ≈ 0,02 для води:

float F = F0 + (1.0 - F0) * pow(1.0 - max(dot(N, V), 0.0), 5.0); color = mix(refractedColor, reflectedSky, F); Без Френеля океан виглядає як листок фарфору, незалежно від того, наскільки добре геометрія. З ним той самий меш читається як вода негайно.

// per wave, accumulate into tangent T and binormal B
T += vec3(-D.x*D.x * (S * sin(phase)),  D.x * (S * cos(phase)),
          -D.x*D.y * (S * sin(phase)));
B += vec3(-D.x*D.y * (S * sin(phase)),  D.y * (S * cos(phase)),
          -D.y*D.y * (S * sin(phase)));
normal = normalize(cross(B, T));

Gerstner проти океану на основі FFT

Альтернативою є океан на основі FFT (метод Tessendorf, який використовується у фільмах та в більшості AAA водних ефектів). Замість підсумовування кількох явних хвильових наборів, він заповнює спектр у частотній області – зазвичай спектр Phillips або JONSWAP, що відповідає швидкості та напрямку вітру – і застосовує інверсний перетворення FFT для отримання поля висоти, а також друге перетворення для горизонтальної штормовості, яке є точно Gerstner-терміном, відновленим.

Gerstner (сума N хвиль) Океан на основі FFT (Tessendorf) ───────────────────────── ──────────────────────────────── O(N) на вершину O(M² log M) за кадр, за сітку 4–8 хвиль у практиці тисячі частот одночасно безпосередньо контролюється контролюється через спектр вітру не потрібен CPU/обчислювальний прохід потрібно перетворення FFT (обчислення/CPU) плитки безпроблемно / нескінченно плитки з видимим періодом дешево на мобільних пристроях важчий, але значно більш детальний Компроміс очевидний. Gerstner є чистим ефектом вершинного шейдера: без попереднього обчислення, без повернення текстур, легко реалізується та кожна хвиля є названим параметром, який художник може налаштувати. Його верхня межа низька – з більш ніж десятком хвиль вартість зростає лінійно і поверхня все ще не має тонкої, хаотичної високочастотної деталізації, яку забезпечує повний спектр. FFT захоплює весь спектр одночасно, але потребує обчислювального проходу та текстури плиток, і його важче контролювати художньо, оскільки він керується через вітер, а не через окремі хвилі.

Океанічна симуляція на цьому сайті використовує Gerstner: кілька трохоїдних хвильових наборів підсумовуються у вершинному шейдері з використанням дисперсійного відношення глибоководдя, буферність шворсту нижче межі самоперетину, аналітичні нормалі, маску піни, що керується Якобі, та змішування Schlick–Fresnel між відбиттям неба та кольором води. Все це працює повністю на GPU в одному проході, що саме пояснює, чому воно підтримує 60 кадрів в секунду на телефоні.

Gerstner (sum of N waves)   FFT ocean (Tessendorf)
─────────────────────────   ────────────────────────────────
O(N) per vertex             O(M² log M) per frame, per grid
4–8 waves in practice       thousands of frequencies at once
directly art-directable     controlled through a wind spectrum
no CPU/compute pass needed  needs an FFT pass (compute/CPU)
tiles trivially / infinite  tiles with a visible period
cheap on mobile             heavier, but far richer detail

Часті запитання

Яка різниця між хвилею Гершнера та синусоїдальною хвилею?

Синусоїдальна хвиля лише зміщує поверхню вертикально, тому піки та дна мають однакову форму. Хвиля Гершнера також зміщує її горизонтально, змушуючи кожну вершину рухатися по колу – фізично правильний орбітальний рух глибоководдя. Це накопичує вершини на піку та розповсюджує їх у долині, створюючи гострі піки та плоскі дна справжнього шквалу.

Чому моя сітка океану згортається сама в собі?

Загальна крутизна перевищує 1. Крутизна дорівнює амплітуді помноженій на хвильове число (S = A·k), і при S = 1 пік стає кутом; виходячи за межі цього значення, поверхня самоперетинається. Підсумуйте A·k для всіх ваших хвиль та тримайте суму нижче приблизно 0.8.

Чи слід мені використовувати хвилі Гершнера чи океанічну FFT?

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

Спробуйте наживо

Усе, що вище, працює прямо у вашому браузері — відкрийте Interactive Ocean і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.

▶ Відкрити симуляцію Interactive Ocean

Що ви знайшли?

Додати кроки відтворення (опційно)