![](https://habrastorage.org/webt/hp/jz/8f/hpjz8fiymln8natu6_s0gfhv0l8.png)
Гидравлическая эрозия — это процесс постепенного преображения водой рельеф. В основном она вызывается осадками, но на неё также влияют разбивающиеся о побережье волны океана, а также течения рек. На рисунке 1 показаны масштабные эффекты, которые может оказать небольшой поток на окружающие его скалы. При создании реалистичных окружений необходимо учитывать влияние эрозии. Я уже экспериментировал с процедурной генерацией при создании сцен для послойного рендеринга вокселей и для демонстрации кубического шума. Такие рельефы очень просты и в них не учитывается влияние эрозии. Следовательно, им не хватает деталей, из-за чего они при близком рассмотрении кажутся нереалистичными.
![](https://habrastorage.org/getpro/habr/post_images/f1b/424/05b/f1b42405bce5347228c5cfe078cf1c6f.jpg)
Рисунок 1: небольшой водопад.
В этой статье я подробно расскажу о простом и быстром способе аппроксимации эффектов гидравлической эрозии. Задача этого способа заключается в создании правдоподобных окружений, а не в достижении высокой степени реализма. Пока результат выглядит естественно, ради скорости мы можем жертвовать точностью. В целом способ должен отвечать следующим требованиям:
- Результаты должны выглядеть естественными.
- Алгоритм должен быть простым.
- Алгоритм должен быть быстрым.
- Алгоритм должен симулировать гидравлическую эрозию, вызываемую осадками и реками.
Различные подходы
Существуют различные подходы к реализации симуляции эрозии. Все способы симулируют одно и то же явление: воду, движущуюся из высоких точек в низкие, в процессе своего движения подвергая рельеф эрозии и смещая осадочные породы вниз по пути движения. Этот процесс всегда приводит к таким созданию таких узнаваемых особенностей рельефа, как овраги и долины в местах течения рек, дельты в местах, где реки достигают своей цели, а также аллювиальные вееры в местах, где небольшие потоки соединяются в реки. В процессе изучения этой темы я встретил в исследовательской литературе следующие стратегии:
- Эрозия симулируется отслеживанием местонахождения воды для каждой позиции на рельефе. Для окружения создаётся сетка (или 2D-массив), в котором для каждой ячейки хранятся уровни воды и давления. При обновлении давления определяют, куда будет течь вода. В процессе течения вода перемещает осадочные породы.
- Эрозия симулируется бросанием на рельеф множества частиц, имитирующих капли дождя. Затем частицы перемещаются по склонам рельефа. Они могут переносить с собой осадочные породы или отлагать их.
![](https://habrastorage.org/getpro/habr/post_images/464/113/3cb/4641133cb717a285bf50b4b159bcc646.jpg)
Рисунок 2: остров после выполнения симуляции эрозии.
В основном из соображений скорости я решил реализовать способ с каплями. Так как большинство капель не течёт слишком далеко, многие симуляции неактивных капель можно прерывать на ранних этапах, а основная часть вычислительных ресурсов будет применяться к каплям, которые занимаются созданием особенностей рельефа. В симуляции с сеткой в каждом цикле обновления потребовалось бы симулировать каждую часть рельефа.
Снежки
Капли в симуляции можно рассматривать как снежки, а не дождевые капли. Я думаю, что в контексте этой симуляции такая аналогия будет более подходящей. При падении снежки сначала маленькие, но когда они начинают катиться вниз по склонам, то накапливают больше материала. Когда они становятся слишком большими, то начинают при движении терять материал. Когда они прекращают катиться, попадая в долины или в море, снежки распадаются и оставляют свой материал на рельефе.
Ниже представлен полный алгоритм эрозии (на Javascript). В этом коде для выполнения эрозии используется объект
heightMap
. Эту карту высот можно считывать и выполнять в неё запись, а функцию sampleNormal
можно применять для получения нормали поверхности. Это 3D-вектор, указывающий вверх перпендикулярно рельефу, поэтому его можно использовать для определения направления и крутизны склона./**
* Let a snowball erode the height map
* @param {Number} x The X coordinate to start at
* @param {Number} y The Y coordinate to start at
*/
trace = function(x, y) {
const ox = (random.getFloat() * 2 - 1) * radius; // The X offset
const oy = (random.getFloat() * 2 - 1) * radius; // The Y offset
let sediment = 0; // The amount of carried sediment
let xp = x; // The previous X position
let yp = y; // The previous Y position
let vx = 0; // The horizontal velocity
let vy = 0; // The vertical velocity
for (let i = 0; i < maxIterations; ++i) {
// Get the surface normal of the terrain at the current location
const surfaceNormal = heightMap.sampleNormal(x + ox, y + oy);
// If the terrain is flat, stop simulating, the snowball cannot roll any further
if (surfaceNormal.y === 1)
break;
// Calculate the deposition and erosion rate
const deposit = sediment * depositionRate * surfaceNormal.y;
const erosion = erosionRate * (1 - surfaceNormal.y) * Math.min(1, i * iterationScale);
// Change the sediment on the place this snowball came from
heightMap.change(xp, yp, deposit - erosion);
sediment += erosion - deposit;
vx = friction * vx + surfaceNormal.x * speed;
vy = friction * vy + surfaceNormal.z * speed;
xp = x;
yp = y;
x += vx;
y += vy;
}
};
// Simulate 50000 snowballs
const snowballs = 50000;
for (let i = 0; i < snowballs; ++i)
trace(
random.getFloat() * width,
random.getFloat() * height);
// Blur the height map to smooth out the effects
heightMap.blur();
У алгоритма есть несколько примечательных свойств:
- Переменные
ox
иoy
кодируют смещение снежка. Они используются для считывания склона рельефа с определённым смещением, чтобы сделать движение снежка чуть более грубым, позволяя снизить схождение путей снежков. - Когда нормаль поверхности указывает ровно вверх (то есть когда значение y этой нормали равно единице), снежок останавливается. На практике это означает, что снежок достиг края симулируемой площади или морского дна, где симуляция завершается. Так как в этих областях ничего не происходит, симуляция эрозии там будет пустой тратой вычислительной мощи.
- При изменении величины осадочных пород снежок изменяет карту высот в своей предыдущей позиции, а не в текущей. Эрозия и отложение происходят в предыдущей позиции, чтобы снежки не закапывались в рельеф.
- После завершения симуляции эрозии к карте высот применяется размытие по Гауссу. Так как карта высот в этих примерах имеет низкое разрешение, требуется размытие, чтобы поверхности оставались визуально гладкими.
Так как при выполнении эрозии используется смещение и из-за довольно высокой степени эрозии, каждый отслеживаемый снежок имеет большее влияние на рельеф, чем мог бы узел меньшего размера, например, дождевая капля. Это приводит к быстрой симуляции, но снижает точность.
Результаты
![](https://habrastorage.org/getpro/habr/post_images/e82/76b/17c/e8276b17c75ee64b1f433f8f1a6558d5.jpg)
Рисунок 3: результаты алгоритма эрозии.
После применения описанного выше алгоритма с различным количеством снежков мы получаем результаты, отрендеренные на рисунке 3. Алгоритм работает в браузере, а его исходный код можно найти на GitHub. При нажатии на пробел генерируется новый остров. «Начальный материал» для симуляции показан на первом изображении рисунка 3. Форма острова сгенерирована при помощи алгоритма, очень похожего на тот, который я использовал в примерах рельефа для послойного рендеринга вокселей. Хотя форма имеет отдельные детали и хребты, она очень гладкая и не содержит следов гидравлической эрозии.
На втором изображении показан тот же остров после падения на него 35 000 снежков. Они падали случайным образом и распределялись равномерно. Из-за случайных изначальных условий исходной формы, структуры, напоминающие долины и реки, образуются там, где снежки нашли ближайший путь к морю. Может показаться, что 35 000 — это много, но нужно помнить, что симуляция снежков, которые достигают морского дна или края карты, сразу же завершается. Большинство капель не падает на остров, поэтому только небольшое количество скатывается по одной из долин, показанных на изображении.
На третьем изображении показан тот же остров после падения 50 000 снежков. По сравнению с предыдущим изображением не образовалось никаких новых деталей, хотя особенности рельефа стали более выраженными.
На последнем изображении показан остров после падения 100 000 снежков. Очевидно, что этого слишком много: хребты становятся очень глубокими, а побережье — очень неровным. Кроме того, результаты выглядят менее реалистично. Долины вырезают очень резкие особенности рельефа, которые самостоятельно уничтожатся вследствие эрозии.
Все острова из представленных выше изображений на моём домашнем компьютере можно сгенерировать за полсекунды, при том, что алгоритм работает на одном потоке ЦП. Следовательно, в большинстве случаев необязательно снижать количество снежков из соображений производительности, алгоритм быстр и в таком виде.
Заключение
Предлагаемый алгоритм обеспечивает быстрый способ аппроксимации гидравлической эрозии. Хотя реализм не был моим приоритетом, при тестировании способа на различных рельефах проявляются вполне ожидаемые паттерны эрозии и отложений осадочных пород.
Так как код работает очень быстро (в отличие от большинства альтернативных решений, которые можно найти в литературе), он может быть применим для таких областей применения, как процедурная генерация рельефа в играх. В таких областях желательно получать результаты быстро, а сами они не обязаны быть особо реалистичными; им достаточно выглядеть убедительными.
Способ можно усовершенствовать, чтобы он отслеживал контуры русел рек. Долины, по которым скатывается много снежков, реалистичным образом могут стать реками. Когда область достигает определённого порога «трафика снежков», в ней можно создать реку или озеро.
Ещё одним интересным дополнением может стать текстура, отслеживающая количество эрозии и отложений материала на рельефе. Эти данные можно затем использовать для раскрашивания рельефа — если в области отложилось много материала, то там накапливается песок и мелкие частицы. Области, подвергнувшиеся малому объёму эрозии, будут выглядеть иначе, чем склоны с сильной эрозией.
Приложение: рендеринг прибрежных волн
![](https://habrastorage.org/getpro/habr/post_images/11c/908/aad/11c908aad0e5f1d2bdc91523a5d90389.jpg)
Рисунок 4: рендеринг прибрежных волн.
В анимированном примере показаны волны, движущиеся к побережью островов. Они только подчёркивают форму острова и украшают сцену, но никак не влияют на эрозию.
На рисунке 4 этапы создания анимации волн:
- Сначала вокруг побережья острова создаётся диаграмма Вороного. Диаграмма создаётся не из точек, а из фигур. Каждая часть острова, не находящаяся под уровнем моря, по сути, является точкой диаграммы Вороного. В этом посте объясняется алгоритм заливки скачками, который использовался для генерации диаграммы Вороного на GPU; в этом посте также объясняется использование фигур для построения диаграммы.
- После создания диаграммы Вороного её данные используются для создания текстуры, в которой для каждого пикселя хранятся расстояние и направление до ближайшей точки побережья. На рисунке 4 показано, что для хранения вектора направления использованы каналы красного и зелёного. Величина этого вектора кодирует расстояние до берегам (чёрные области дальше всего от побережья).
- Создаётся синусоида, представляющая глобальный паттерн волн. Позиция синусоиды определяется по направлению к ближайшей точке побережья, и волна медленно сдвигается в сторону берега. Если волны трёхмерные, то направление к ближайшей точке побережья можно использовать для вычисления нормалей поверхности формы волны.
- В конце волны стилизуются, а паттерны волн немного разделяются, чтобы создать ощущение, что все волны являются отдельными сущностями.
Эта анимация демонстрирует этапы рисунка 4 в реальном времени.
См. также: