Принцип работы генератора чанков. Начало

Принцип работы генератора чанков. Начало

Версия(и) Minecraft
1.16-1.17
Всем привет!
Данную статью я хотел написать уже давно, и вот наконец желание оказалось сильнее отсутствия времени :)
В ней я постараюсь простыми словами описать тот сложный процесс, который в майне зовётся генерацией.
Слова буду подкреплять схемами и скриншотами, так как с ними материал будет намного более наглядней. А ведь это важно, когда речь заходит о трёхмерном пространстве.

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

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

Естественно, когда дело дошло до моддинга, одним из первых желаний было сгенерировать что-то своё. Так, чтобы и форма была интересной и в нужных местах находились нужные мне блоки.
С горем пополам, мне таки удалось разобраться как работает setBlock() (тогда это была ещё версия 1.7.10), и пошло-поехало!

Есть куча способов как заставить блоки размещаться так, как нужно тебе. И благо, на форуме по этому поводу есть множество статей. К примеру, в Туманном мире я часто использую циклы.
Вот тут есть статья про них, которую я тоже давно хотел написать, но меня опередили )

Однако собственные структуры - это хорошо, но в какой-то момент тебе начинает хотеться большего! Долгое время для меня оставалось загадкой, как я могу влиять на ландшафт мира: размещать в нужных местах целые биомы, выбирать горы тут буду или равнины, моря или леса.
В тот момент @fewizz дал мне очень ценные советы по тому, как работать со слоями, а позже появилась очень подробная статья про них, с которой я советую всем ознакомиться (надеюсь, её оформление всё-таки исправят): Генерация биомов (часть 1)

Это всё помогло мне разобраться с распределением биомов и с их настройкой (высота, величина перепадов рельефа). Но основной секрет ускользал от меня, а именно то, как детально указывать форму рельефа? К примеру, в Туманном я очень хотел поработать над формой обрывов, сделать их более аккуратными, задать им определённый профиль.

И вот, в один прекрасный момент я открыл генератор чанков версии 1.16.5. В нём разбили весь процесс генерации на отдельные методы немного более подробно, чем это было сделано ранее, и тут меня вдруг осенило!

А теперь пора приступить к самому интересному ;)

Основные принципы:​

Чтобы понять, с чем нам придётся работать, для начала нужно уяснить для себя несколько вещей:
1. Чанк использует в своей генерации трёхмерный шум значений, а не карту высот, как может показаться на первый взгляд. О том, что такое шум значений можно почитать тут: Value noise - Wikipedia.
Если вкратце, то это трёхмерная решётка из псевдорандомных значений. А если ещё грубее, то это трёхмерный массив цифр. В отличие от того же шума Перлина, здесь нет никаких векторов.
К слову, так выглядит двухмерный шум Перлина:
Двухмерный шум Перлина
Ред. от 17.05.2024: Как выяснилось, всё уже придумано до нас, и данный принцип генерирования поверхностей называется SDF. Почитайте, кому интересно )

2. При работе с генератором чанков нужно забыть о блоках как таковых. Размер решётки трёхмерного шума не соответствует размеру блока, как опять же может показаться на первый взгляд. На самом деле размер одной ячейки составляет 4х4х8 блоков.
Многие, кто заглядывал в класс генератора, могли заметить две магические цифры: 5 и 33. В последних версиях эти цифры вынесены в настройки и от них зависит размер чанка, в версии 1.7.10 они были жёстко прописаны в коде. Меня тоже по началу сбивали с толку такие странные значения. А почему не 4 и 32, думал я, ведь это было бы логично. Размер чанка 16х16х256, делим это на 4 и 8 соответственно и получаем 4х4х32. Логично? Нет.
Дело в том, что каждый чанк генерируется независимо, и для того, чтобы наш ландшафт совпадал с ландшафтом соседних чанков, нужно указывать величину шума по его границам. То есть, значения в вершинах решётки на границе у двух смежных чанков будут одинаковыми. Отсюда и получается финальный размер решётки для одного чанка 5х5х33.
Всё это очень напоминает кристаллическую решётку, не так ли?
Кристаллическая решётка чанка
3. "Не всё так гладко в Датском королевстве"... Но об это позже.

Как оно работает:​

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

Основная магия происходит в методе fillFromNoise(). Здесь берётся одна ячейка решётки, а это 8 значений в её вершинах, и заполняется блоками по хитрому алгоритму, в который, мы не будем сейчас углубляться. Из-за того, что размер ячейки 4х4х8 блока, ландшафт получается более менее гладким и приятным для глаз.
Кроме того, вес вершин влияет на то, насколько много блоков будут генерироваться вокруг них. К примеру, если соседние вершины имеют веса -10 и 1, то в вершине с положительным значением сгенерируется только единичный блок, если же веса в вершинах будут -1 и 10, то большая часть пространства будет заполнена блоками камня. При этом могут получаться как ровные кубы, так и шарообразные скопления блоков. Всё зависит от соотношения весов в соседних вершинах.
Если сделать срез чанка в любой из плоскостей, мы получим знакомый нам двухмерный шум.

Чтобы было более наглядно, как именно веса вершин влияют на финальный результат, я сделал за вас несколько экспериментов с разными значениями веса. Вершинам с чётным порядковым номерам по всем трём координатам я назначил положительное значение, всем остальным - отрицательное.
Значения вершин: 1 и 0
Как видно из картинки, нулевое значение не сильно влияет на соседние вершины.
1_0

Так это выглядит сверху.
Для наглядности, указал разными цветами, где находятся вершины с положительными значениями, а где с отрицательными. Можно заметить, что точки сетки располагаются не в центрах блоков, а на их вершинах.
1_0_


Значения вершин: 1 и -1
Здесь хорошо видно, что решётка чанка вытянута по вертикали. Да и сами массивы блоков в узлах решётки получаются вытянутыми.
1_-1


Значения вершин: 1 и -10

Это уже напоминает Sky Blocks :)
Отрицательные значения сдавили вершины сетки со всех сторон.
1_-10


Значения вершин: 10 и -1
А вот это уже эпично :)
10_-1

Если у вас не официальные маппинги, метод fillFromNoise() можно найти по следующим признакам:
1. Это один из самых больших методов в классе генератора.
2. Там встречаются страшные трёхмерные массивы даблов:
Java:
double[][][] adouble = new double[2][this.chunkCountZ + 1][this.chunkCountY + 1];
3. Там встречаются ещё более страшные операции с этими массивами:
Java:
for(int z = 0; z < this.chunkCountZ; ++z) {
...
for(int y = this.chunkCountY - 1; y >= 0; --y) {
    double d0 = adouble[0][z][y];
    double d1 = adouble[0][z + 1][y];
    double d2 = adouble[1][z][y];
    double d3 = adouble[1][z + 1][y];
    double d4 = adouble[0][z][y + 1];
    double d5 = adouble[0][z + 1][y + 1];
    double d6 = adouble[1][z][y + 1];
    double d7 = adouble[1][z + 1][y + 1];

По сути, это всё, что нужно знать о генераторе. Можно расходится )

Но постойте! - скажете вы, - А как же, тогда, получается сам ландшафт? Ведь таким образом можно получить лишь трёхмерную губку, что-то по типу последнего обновления Caves & Cliffs.
Не удивлюсь, если именно так они и сделали свои большие пещеры )))
К примеру, вот, что получилось у меня, если использовать чистый шум:
Ландшафт в виде трёхмерной губки

Дело в том, что для генерации ландшафта в майне используется не только шум значений.

Ландшафт:​

Итак! Как я уже говорил, на форму финального ландшафта влияет не столько вес вершин в решетке чанка, сколько соотношение между весами соседних вершин. Однако, есть и пограничное значение, которое определяет, будет ли данная точка пространства заполнена блоками камня или блоками воздуха, и это значение 0. Всё, что выше нуля - камень, всё, что ниже - воздух. И в майне придумали интересное решение для превращения трёхмерного шума в поверхность земли с горами и равнинами.
К нашему шуму, чьи значение колеблются где-то в районе от -10 до 10, добавляют ещё и вертикальный градиент, чьи значения могут улетать на многие тысячи в плюс и в минус.
Таким образом, "оболочка" нулевых значений, которая до этой операции представляла из себя ту самую губчатую структуру, приобретает вид поверхности похожей на ландшафт местности.
Для наглядности взглянем на уже знакомую нам картинку двухмерного шума, но теперь добавим к ней градиент:
Шум + Градиент

Да, здесь это не очень видно, но если добавить контраста, то есть всё тёмное увести в чёрное, а всё светлое - в белое (а ведь именно это делает генератор, заполняя объём блоками), мы получим следующее:
Шум + Градиент + Контраст

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

Мне трудно конкретно указать место, где именно происходит данное смешивание, так как код генератора успели уже несколько раз переписать и поменять местами многие методы. Но если вчитываться в код версии 1.16.5, то это чудо случается где-то внутри метода fillNoiseColumn(). Того самого, который определяет значения в одной из колонн вершин нашей кристаллической решётки.
Тут-то на арену выходят всем известные биомы )

Влияние биомов:​

Помните статью про слои, которую я советовал прочитать в самом начале?
Так вот, вся финальная карта биомов строится именно ради метода fillNoiseColumn(). Карта, которая получается в финале работы со слоями - это не карта блоков, как в который раз может показаться на первый взгляд, это карта вершин решётки чанка. То есть, реальная местность будет в 4 раза больше, чем пиксели на карте слоёв.
Именно в методе fillNoiseColumn() при вычислении каждой отдельной колонны вершин пробегаются по 25-ти соседним точкам на карте, выясняют какие на этих точках находятся биомы и вычисляют среднее значение их высот и величины перепада рельефа.

Для чего всё это делается?
Для определения типа градиента, который мы будем смешивать с нашим трёхмерным шумом.
Высота биома - это смещение градиента вверх и вниз, то есть собственно высота финальной местности.
Величина перепада рельефа биома - это "контраст" градиента, то есть насколько большие значения будут находится в самой низкой и самой высокой точках колонны вершин.
И ещё несколько картинок для наглядности )
Применение контрастного градиента

Применение не контрастного градиента

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

Теперь в ваших руках есть понимание принципа формирования трёхмерного ландшафта. С таким инструментом вы можете горы свернуть! Вернее сгенерировать )))
Но позволю себе добавить немного дёгтя.

Датское королевство:​

Данная система спроектирована специально для более удобной работы с биомами. Есть всего два основных параметра биома и зная их можно сформировать всё то разнообразие ландшафтов, которое вы можете наблюдать в игре.

Однако есть и ограничения. К примеру, будет проблематично сгенерировать чанк в виде пингвина ) Для этого придётся отдельно где-то строить трёхмерную карту этого пингвина, где внутри него значения вершин положительные, а снаружи - отрицательные. Кроме того, с вершинами чанка нельзя работать как с блоками. То есть нельзя поставить вершину с одним значением тут, а с другим там, а потом отступить пару вершин влево и изменить значение в данном месте.

Дело в том, что решётка вершин генерируется налету по два вертикальных среза за раз. То есть, за один просчёт генерации, в памяти хранится только 10 колонн вершин. Именно поэтому приведённый выше массив даблов имеет первое значение 2, а не this.chunkCountZ + 1.
Java:
double[][][] adouble = new double[2][this.chunkCountZ + 1][this.chunkCountY + 1];
Затем, второй вертикальный срез копируется в первый, а на его месте собирается информация о следующем. Таким образом генерация чанка происходит послойно, и в памяти никогда не хранится вся решётка целиком.
Поэтому, чтобы производить какие либо манипуляции с вершинами, вам придётся строить их карты отдельно.
Лично я так и делал, когда задавал краям обрывов Туманного мира их финальную форму )
Новый вид обрывов в Туманном мире

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

Продолжение статьи можно посмотреть здесь.
Автор
Liahim
Просмотры
4,807
Первый выпуск
Обновление
Оценка
5.00 звёзд 9 оценок

Другие ресурсы пользователя Liahim

Последние обновления

  1. SDF

    Добавил официальное название принципа генерации в разделе Основные принципы.
  2. Обновление скриншота

    На одном из скриншотов с экспериментами генерации указал положение вершин сетки чанка.

Последние рецензии

Понятно рассказано о сложном
Это гениально! Пришёл сюда искать вдохновение для собственного генератора для собственного прототипа игры и теперь я кажется понимаю, куда копать в файлах версий самого майнкрафта.
Liahim
Liahim
Рад, когда удаётся кого-то вдохновить )
понял что понимал не так
Прочитал на одном дыхании, отличная статья
5 звёзд и спасибо от чела, который просто любит кодить всякое. Благодаря этому посту я смог написать гениратор 2d карты, очень похожий на Terraria. Думаю там были использованы такие же методы, что и в Minecraft. Ну и к самому посту не придраться даже - всё довольно подробно и структурировано как надо.
Liahim
Liahim
О! Это клёва, когда кому-то удаётся применить теорию на практике )
Рад, что помог )))
Подробно и просто.
Ты один из самых долгоживущих моддеров)
Я был удивлен, что ты не только продолжаешь что то делать, но еще и гайды пишешь)
Liahim
Liahim
Это уже больше по инерции )
Прекрасно всё объяснил с примерами.
Однозначно 5 звёзд
Век живи, век учись! Теория по майну это всегда прекрасно
Сверху