generation
September 16, 2024

Демо сцена. Форма ландшафта.

Telegram: Infinity World

Введение

Вторым этапом создания демо сцены является определение базовой формы ландшафта, которая будет исходной точкой для дальнейшей проработки окружения. От формы ландшафта очень много зависит, начиная от цвета, который будет преобладать на сцене, и заканчивая объектами окружения, такими как булыжники и камни, деревья и кустарники, и т.д. и т.п.

В первой части я определила, что хочу видеть в демо сцене в качестве биома, вот цитата из той статьи:

Биом представляет из себя невысокие холмы с выступами и обрывами средней частоты.

То есть никаких гор, глубоких впадин: просто холмы и выступы и обрывы, которые помогут разнообразить вид.

Первую часть можно посмотреть вот тут, в ней я рассказываю про концепт демо сцены.

1. Генерация

После того, как я перевела генерацию вокселей на GPGPU (Compute Shaders), то все описание поверхности ландшафта представлено кодом на HLSL. Для этого я использую библиотеку FastNoise для HLSL: не самый удобный вариант, но на текущий момент он покрывает все мои потребности.

Пример кода на HLSL:

Рисунок 1. Пример кода на HLSL. Одна из небольших функций, описывающая базовую поверхность ландшафта.

Каждый биом у меня представлен отдельным .hlsl файлом, который указывается в ядре (kernel) генерации как один из возможных биомов для выбора. Но так как в демо сцене у меня один биом, то получается, что он всегда и выбирается, а логика выборки биомов линейна.

Стоит заметить, что подобный подход, когда у нас N биомов, и мы по входящим данным выбираем нужный кусок кода, является довольно неоптимальным. И тут существует две проблемы.
Первая проблема заключается в том, что на выходе в вычислительном шейдере получается множество ветвлений, и в итоге метрика занятости GPU является очень низкой. То есть наши вычислительные мощности простаивают.
Вторая проблема является больше неудобством: время компиляции всех веток в коде увеличивается в геометрической прогрессии. Я с этим столкнулась даже при работе с одним биомом, так как использую множество вариаций шума.

1.1 Базовый слой

С чего необходимо начинать любую генерацию? С основы! Основной (базовый) слой должен определять общую форму: это именно то, что делает ландшафт холмами, горами, равнинами, каньонами и т.п. Этот основной слой потом дополняется средними и малыми деталями.

В качестве базового слоя я взяла самый обычный Simplex шум в 2D версии. Почему именно Simplex, а не Perlin? В среднем Simplex дает получше результаты и не так сильно уходит в самоповторение, а также немного пошустрее работает. Хотя в данном случае, производительность меня мало интересует, так как математика там простая.

Рисунок 2. Пример Simplex noise.

Взяв Simplex шум, я также его модифицировала, добавив Fractal Brownian Motion (FBM), чтобы получить фрактальный шум, то есть как крупные детали, так и средние/малые, а значит более интересный ландшафт.

Fractal Brownian Motion (FBM) выглядит просто как N итераций, где на каждой итерации мы добавляем шум с изменением частоты и амплитуды, получая таким образом более мелкие детали.
Рисунок 3. Тот же самый шум, что и на рисунке 2, но с применением Fractal Brownian Motion (FBM).

Как видно из рисунка 3, FBM очень сильно меняет шум, добавляя множество деталей на каждой итерации. Это очень мощный инструмент: lacunarity, gain, количество octaves и другие параметры подбираются в зависимости от того, что хочется получить.

Немного поигравшись с параметрами, я получаю результат (рисунок 4), который мне подходит.

Рисунок 4. FBM Simplex 2D в качестве базового слоя ландшафта.

На рисунке 4 ландшафт вполне похож на небольшой высоты холмы. Они плавные, различной формы, но выглядят неестественно. Эта неестественность заключается в том, что холмы слишком уж плавные, а также в том, что форма хоть и разная, но при этом слишком много общего в каждой из них по отношению к другим.

Чтобы убрать неестественность форм, можно добавить немного хаоса: мы можем добавить еще шум с разной частотой и амплитудой, или попробовать изменить координаты, по которым этот шум генерируется. В первом случае, мы скорее всего все равно получим плохой результат, так как неестественность лежит в основе шума. Во втором случае, мы можем получить получше результаты, так как будем изменять шум не на его уровне, а на уровне координатной сетки.

Чтобы получить хаос, можно воспользоваться техникой Domain Warp. Она представляет собой все тот же FBM (я уже говорила, что это очень мощный инструмент?) в своей основе, который применяется к координатам. Результат показан на рисунке 5.

Рисунок 5. Результат применения Domain Warp.

Выглядит уже намного лучше! Паттерн разбился, а также добавилось много средних деталей: выемки, небольшие выступы и подъемы.

1.2 Крупные детали

Базовый слой получился неплохо, даже с некоторыми деталями, но их количество все еще недостаточно, ландшафт кажется слишком однообразным. Необходимо добавить крупные детали, которые будут сильно выделяться.

Создать процедурно ландшафт с уникальными деталями по всей площади практически невозможно: он все равно будет так или иначе повторяться. Поэтому в решении задачи генерации только формой ландшафта не обойтись и надо будет искать дополнительные варианты: размещение объектов окружения, крупные структуры и т.п.

В качестве крупных деталей я решила добавить большие выступы на вершинах холмов: будто они немного приподнимаются, с резкими обрывами в некоторых местах

Первым шагом надо определить место, где эти детали будут: результатом будет маска, где черный цвет (0) обозначает отсутствие деталей, а белый цвет (1) - наличие. Так как нам нужны вершины холмов, то мы берем базовую высоту и по какому-то порогу отсекаем.

Рисунок 6. Маска для крупных деталей, где белым цветом обозначаются вершины холмов.

Маска на рисунке 6 даст нам выступ на каждой вершине холма, что снова является неестественно, так что, пожалуй, уберу некоторую часть по какому-то другому шуму (рисунок 7).

Рисунок 7. Окончательная маска для вершин холмов, где белым обозначается выступ, а черным - его отсутствие.
На данном этапе я поняла, что не хватает некоторого функционала в генераторе: наличие и/или отсутствие выступов возможно стоило сделать на основании размера холма, а эту информацию можно получить только при итеративном подходе генерации вокселей.

Сам выступ просто добавляется к базовой высоте по получившейся маске.

Рисунок 8. Ландшафт с выступами на вершине холмов.

1.3 Средние детали

Для разнообразия необходимо добавить еще деталей, но уже не таких крупных, как в предыдущем шаге. Это тоже должны быть выступы, но небольшие, и на склонах, так как именно склоны сейчас выглядят пустыми.

Как и в случае с крупными деталями, нам необходимо определить, где мы можем добавлять выступы, а где нет, то есть сгенерировать маску. Только в этот раз, маска будет основываться на склонах холмов.

Чтобы определить, является ли точка на склоне холмов, можно воспользоваться концепцией частных производных. Так как ландшафт сейчас основан на 2D шуме, то можно откинуть производную по Y-оси, и посчитать только для X и Z осей, что немного уменьшит количество вычислений.

Рисунок 9. Маска, где белым показаны склоны холмов.

Осталось только добавить сами выступы, но что они из себя представляют? Идея заключается в том, чтобы взять какую-нибудь простейшую геометрическую фигуру и вписать ее в базовый ландшафт. Я взяла для этой цели цилиндр, добавив к нему "шапочку" - полусферу. Получилось вполне неплохо, хотя полностью вписать в ландшафт пока не получилось.

В этом моменте я еще раз подумала про итеративный генератор: тогда можно было бы определить в некой области (цилиндра) градиент базовой поверхности, и на его основе вписать фигуру. Без итеративности это сделать невозможно, так как нет информации о промежуточных результатах соседних вокселей.
Рисунок 10. Дополнительные выступы на склонах холмов.

1.4 Мелкие детали

В качестве мелких деталей что-то сложное не добавишь, потому что разрешение сетки небольшое, просто не получится эту сложность показать. Можно натыкать еще каких-нибудь выступов или выемок, но при текущем масштабе это проще будет сделать уже с помощью объектов: камушков и булыжников.

Хотя кое-что все же можно сделать, так как если посмотреть на саму поверхность, то она гладкая. Да, эта поверхность без текстур, но ландшафт и с помощью геометрии должен передавать шершавость, а не быть гладким как стекло.

Рисунок 11. Ландшафт без мелких деталей, гладкий, даже слишком.

Если же добавить небольшой шум по всей поверхности, то гладкость уйдет.

Рисунок 12. Ландшафт с мелкими деталями, стал более шероховатым.
После добавления шероховатости к ландшафту стал очень заметен шов между разными LOD. До этого момента, данная ошибка ускользала от моего внимания. Что ж, заводим задачу и когда-нибудь исправим!

Заключение

Процедурная генерация далека от уникальности, но какие-то инструменты решения этой проблемы все равно есть. Я показала достаточно простой вариант, но даже в нем получилось немного разбить "похожесть" с помощью многослойности.

Какие моменты проявились при создании формы ландшафта для этого биома:

  1. Похоже, что стоит задуматься об итеративном генераторе вокселе. Это небольшая часть всего генератора мира, она полностью находится на стороне GPU, но при этом это самая важная часть генератора. Под итеративностью тут я подразумеваю генерацию step-by-step всех вокселей, так, чтобы каждый следующий шаг имел доступ к результатам предыдущего шага для всех вокселей. Это позволит усложнить генерацию и добиться очень хороших результатов.
  2. Проявилась ошибка в вычислении нормалей на стыках чанков с разными LOD. Это связано с разным шагом, с которым идет оценка пространства в каждом вокселе. Поправить не сложно, надо просто сделать отвязать от LOD пограничные воксели.

Следующий этап: базовые цвета ландшафта.