Dlaczego fala sinusoidalna nie przypomina morza
Najoczywistszym sposobem na animowanie oceanu jest pionowe przesunięcie płaskiej siatki przez sumę sinusoid. Działa to i wygląda jak żele. Prawdziwe fale mają ostre grzbiety i szerokie, płaskie dolne płyty; fala sinusoidalna ma grzbiety i dolne płyty dokładnie tej samej formy. Wynika to z przyczyny fizycznej: w głębokim morzu woda sama nie przemiesza się wraz z falami, porusza się okręgami. Paczka wody unosi się, przesuwa się do przodu pod grzbietem, opada i przesuwa się do tyłu pod dolnym płytem, wracając prawie dokładnie w to samo miejsce, gdzie zaczęła się znajdować. Tylko kształt rozprzestrzenia się.
Ten okrągły obrót jest tym, co koduje fala Gerstnera, a to dlatego grzbiety są ściszane. Nie jest to sztuczek graficzny: fala Gerstnera (lub trochoidalna) została opublikowana przez Františka Józefa Gerstnera w 1802 roku i jest jednym z nielicznych dokładnych rozwiązań nie-liniowych równań dla bezmasowej, niemożliwej do rozpraszania się cieczy o nieskończonej głębokości.
Toroide
Fala Gerstnera przesunięcie wierzchołka poziomo oraz pionowo. W weryjatorze, dla punktu siatki płaskiej p, jedna fala o kierunku D (wektor jednostkowy w płaszczyźnie poziomym), amplitudzie A, współczynniku fali k i częstotliwości kątowej ω:
float faza = k * dot(D, p.xz) - omega * time; vec3 disp; disp.xz = -D * A * sin(faza); // poziomy — to wyostrza grzbiety disp.y = A * cos(faza); // pionowy position = p + disp; Pomijając człon poziomy, wracamy do powierzchni sinusoidalnej. Zachowując go, każdy wierzchołek śledzi okrąg o promieniu A, gromadząc wierzchołki w grzbietu i rozpraszając je w dolinie. Rezultatem jest toroid – krzywa śladana przez punkt na obracającym się kole – i to dokładnie profil widoczny na fotografii fali morskiej.
Parametry są powiązane ze sobą przez zależność dyspersji dla głębokich wód, która mówi, że długie fale poruszają się szybciej niż krótkie:
k = 2π / L // L = długość fali omega = sqrt(g * k) // g = 9.81 m/s² (głęboka woda) c = omega / k = sqrt(g / k) // prędkość fazowa Wybór ω swobodnie zamiast wyprowadzenia go z k jest najczęstszą błędną decyzją przy ręcznie tworzonej mapie oceanu: fale poruszają się wtedy ze stałą prędkością, spektrum nie rozdziela się i powierzchnia wygląda jak mechanicznie napędzane basen falowe, a nie morze.
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;
Nachylenie i fala, która się trawi
Amplituda sama w sobie nie stanowi właściwej kontroli. Ważną wielkością jest nachylenie, S = A · k. Przy S = 0 powierzchnia jest płaska; gdy S rośnie, krawędzie stają się bardziej ostre. Przy S = 1 krawędź nabiera idealnego kształtu cuspu z kątem 0°, a przy S > 1 powierzchnia samoczynnie się przecina – siatka składa się na siebie i powstaje nieodłączny, zwężony, zakrzywiony artefakt wzdłuż linii krawędzi. Fale Gerstnera nie mają pojęcia o pęczekaniu; po prostu odwracają się do środka.
Dlatego autorowanie odbywa się na podstawie nachylenia, a nie amplitudy, a całkowita wartość jest rozliczana w całym szeregu fal:
A_i = S_i / k_i // nachylenie → amplituda, na pojedynczą falę Σ (A_i * k_i) < 1 // utrzymuj SUMĘ poniżej 1 dla wszystkich fal // (w praktyce budżet ~0.6–0.8 dla bezpieczeństwa) W odniesieniu do rzeczywistych fal oceanicznych, pękają one przy nachyleniu znacznie poniżej matematycznego limitu – klasyczny limit Stokesa odpowiada kątowi krawędzi 120°, a nie cusp – więc fizycznie realistyczny morze nigdy nie zbliża się do S = 1. Przekonujące ocean składa się zazwyczaj z czterech do ośmiu szeregów fal: jeden lub dwa długie, niskie podmuchy przenoszące większość energii oraz kilka krótkich, stromych fal o kierunkach rozproszonych wokół kierunku wiatru, każda z własną długością fali, nachyleniem, fazą i prędkością wyprowadzonym z relacji dyspersji.
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)
Normale, pianowata struktura i rozszczepienie Fresnela
Ponieważ wierzchołek jest przesuwany w trzech osiach, nie można pobrać wektora normalnego z pola wysokościowego. Można różniczkować przesunięcie analitycznie – pochodna sumy funkcji sinusowych to suma funkcji cosinusowych, więc styczny i binormalny pojawiają się w zamkniętej postaci w tym samym shaderze, bez dodatkowych kosztów próbkowania:
// dla każdej fali, akumuluj do stycznego T i binormalnego 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));
Pianowata struktura jest umieszczana tam, gdzie powierzchnia jest ściszana – tam, gdzie przesunięcie poziome ściska wierzchołki ze sobą, tzn. tam, gdzie macierz Jataka (Jacobian) mapy przesunięcia zbliża się do zera. Dzieje się to dokładnie na ostrych szczytach, gdzie pojawiają się białe pienki w rzeczywistości, więc maska piany sterowana macierzą Jataka (lub, taniej, pionowym składnikiem przesunięcia połączonym ze stromym nachyleniem) trafia w odpowiednie miejsce bez zbędnych kosztów.
Wreszcie, woda jest powierzchnią Fresnela. Patrząc prosto w dół, widzimy głównie refrakcję – kolor ciała wodnego i tego, co znajduje się pod nim. Patrząc na horyzont pod kątem nachylenia, widzimy prawie czysty odbicie nieba. Przybliżenie Schlicka uchwyci to w jednej linii, z indeksem załamania około 1,33, dając F₀ ≈ 0,02 dla wody:
float F = F0 + (1.0 - F0) * pow(1.0 - max(dot(N, V), 0.0), 5.0); color = mix(refractedColor, reflectedSky, F); Bez Fresnela ocean wygląda jak arkusz pomalowanego plastiku, niezależnie od tego, jak dobra jest geometria. Dzięki niemu ten sam siatka odczytuje się jako woda natychmiast.
// 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 kontra ocean FFT
Alternatywą jest ocean FFT (metoda Tessendorf, wykorzystywana w filmach i większości gier AAA). Zamiast sumować kilka wyraźnych fal, wypełnia się spektrum w dziedzinie częstotliwości – zwykle spektrum Phillipsa lub JONSWAP dopasowane do prędkości i kierunku wiatru – a następnie stosuje się odwrotny FFT, aby uzyskać pole wysokości, oraz drugą transformację dla dyspersji poziomej, co dokładnie definiuje termin Gerstnera, który został ponownie wprowadzony.
Gerstner (suma N fal) Ocean FFT (Tessendorf) ───────────────────────── ──────────────────────────────── O(N) na wierzchołek O(M² log M) na klatkę, na siatkę 4–8 fal w praktyce tysiące częstotliwości naraz direktownie kontrolowalny kontrolowany przez spektrum wiatru nie wymaga przejścia CPU/obliczeniowego wymaga przejścia FFT (obliczeniowego/CPU) tile trivially / nieskończone tile'y z widocznym okresem niewygodne na urządzeniach mobilnych cięższe, ale znacznie bogatsze szczegóły Wybór jest jasny. Gerstner to czysty efekt w wierzchołku: bez prekomputacji, bez podróży tekstury, trivially tani i każda faza jest nazwą parametru, który artysta może dostosować. Jego limit jest niski – z ponad kilkunastoma fazami koszt rośnie liniowo a powierzchnia nadal brakuje drobnych, chaotycznych wysokich częstotliwości detali, które daje pełne spektrum. FFT przechwytuje całe spektrum naraz, ale wymaga przejścia obliczeniowego i tekstury o siatce, a jest trudniejsze do artystycznego sterowania, ponieważ sterujesz nim przez wiatr zamiast indywidualnymi falami.
Symulacja oceanu na tej stronie to Gerstner: kilka trochoidalnych fal sumowanych w wierzchołku z wykorzystaniem głębinowej relacji rozszczepienia, budżetowana gwałtowność poniżej limitu samoseczącego się, analityczne normalne, maska piany napędzana przez Jacobiego i mieszanie Schlicka–Fresnela między odbiciem na niebie a kolorem wody. Uruchamia się to w całości na GPU w jednym przejściu, co dokładnie wyjaśnia, dlaczego utrzymuje 60 fps na telefonie.
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
Często zadawane pytania
Jakie jest różnica między falą Gerstnera a falą sinusoidalną?
Fala sinusoidalna jedynie przesuwa powierzchnię w pionowej linii, więc czubki i doliny mają ten sam kształt. Fala Gerstnera również przesuwa ją poziomo, powodując, że każdy wierzchołek porusza się po kole — prawidłowe ruch orbitalne głębinowych wód. Zgromadza wierzchołki na czubku i rozprasza je w dolinie, dając ostre czubki i płaskie doliny prawdziwych fal morskich.
Dlaczego moja siatka oceaniczna składa się sama na siebie?
Całkowita nachyloność przekracza 1. Nachyloność to amplituda pomnożona przez długość fali (S = A·k), a przy S = 1 czubek staje się kępą; powyżej tego poziomu powierzchnia przecina się sama ze sobą. Zsumuj A·k dla wszystkich zestawów fal i utrzymuj całkowitą wartość poniżej w przybliżeniu 0,8.
Czy powinienem używać fal Gerstnera czy oceanu FFT?
Gerstner do taniego, artystycznie kontrolowanego pojedynczego przejścia z wierzchołkami — idealnego dla urządzeń mobilnych i stylizowanego wody. Ocean FFT (Tessendorf) do fotorealistycznej wody z pełnym spektrem fal napędzanych wiatrem, kosztem przejścia obliczeniowego, tekstury o dzielonym podziale i mniejszej bezpośredniej kontroli.
Wypróbuj na żywo
Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz Interactive Ocean i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.
▶ Otwórz symulację Interactive Ocean