- PVSM.RU - https://www.pvsm.ru -

Создаём процедурные глобусы планет

Искажения, бесшовный шум и как с ними работать.

image

Генерируем планету

Один из простейших способов генерации планеты — использование шума. Если мы решим выбрать его, то у нас есть пара возможных вариантов. Давайте рассмотрим каждый и определим лучший:

  • Шум Перлина (Perlin Noise) — самый простой вариант. Шум Перлина был разработан Кеном Перлином в 1983 году, он имеет пару недостатков — визуальные артефакты и довольно низкая по сравнению с другими вариантами скорость при генерации больших изображений.
  • Симплекс-шум (Simplex Noise) — разработан Кеном Перлином в 2001 году как попытка устранения недостатков шума Перлина; это вполне достойное и быстрое решение, однако обладающее серьёзным недостатком: использование трёхмерного симплекс-шума защищено патентом, что делает его довольно дорогостоящим.
  • Открытый симплекс-шум (Open Simplex Noise) — был разработан KDotJPG [1] с одной простой целью: создать современную и бесплатную версию симплекс-шума, относительно быструю и без искажений.

Из этих трёх лично я предпочитаю Open Simplex Noise, который использую в своих личных проектах. Стоит заметить, что в текущей реализации OpenSimplexNoise для получения простого доступа к масштабу, октавам и порождающим значениям [2] потребуется дополнительная работа. В Интернете есть множество информации о том, что делает каждый из этих элементов, и я крайне рекомендую вам её изучить. Однако в своей статье я буду говорить не об этом.

Создаём процедурные глобусы планет - 2

Вот как выглядит Open Simplex Noise с 16 октавами.

Бесшовный шум

Шум бесконечен, а значит, если мы просто создадим холст с соотношением сторон 2:1, чтобы получить равнопромежуточную проекцию [3], то она не будет зацикленной при наложении на сферу [4] (выражаю благодарность этому потрясающему веб-сайту), а на горизонтальном шве и на полюсах будут огромные отличия.

image

Шум, созданный без учёта швов.

image

Заметьте огромные швы, появившиеся при наложении шума на сферу.

Исправить это можно множеством способов; например, в этом отличном посте [5] Red Blob Games [перевод [6] на Хабре] достаточно было просто сгенерировать остров с помощью функции, получающей в качестве переменной расстояние до центра и и задавая на краях высоту 0 для минимизации швов.

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

Сферическое наложение

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

Однако такая реализация имеет свои ограничения, причины возникновения которых показаны в потрясающем посте Рона Валстара [7]. Самое главное — формы континентов в этом случае выглядят чрезвычайно странными и искажёнными, а поэтому мы не будем использовать этот вариант.

image

Сферическое наложение шума. Странные формы и искажения делают континенты довольно уродливыми.

image

Зато, по крайней мере, швов больше нет.

Кубическое наложение

В результате я использовал второй способ, взятый из поста Рона Валстара и серии статей acko Making Worlds [8]. В них описывается генерация глобуса через генерацию куба и его «надувания», как будто он воздушный шар, до тех пор, пока он не пример форму сферы.

Создаём процедурные глобусы планет - 7

Изображение взято с acko.net. Оно объясняет концепцию кубической карты в простом для визуализации виде.

Теперь нам нужно просто сгенерировать шесть граней, что достаточно просто, есть множество способов сделать это.

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

//Z STATIC
for(int y = 0; y < cubeFaceSize; y++) {
	for(int x = 0; x < cubeFaceSize * 2; x++) {
		//Generates FRONT
		if(x < cubeFaceSize) {
			cubeMap[cubeFaceSize+x][cubeFaceSize+y] = noise.noise3D(x, y, 0);                    
		}
		//Generates BACK
		else {
			cubeMap[cubeFaceSize*3+(x-cubeFaceSize)][cubeFaceSize+y] = noise.noise3D(cubeFaceSize-(x-cubeFaceSize), y, cubeFaceSize);
		}
	}
}
//X STATIC
for(int y = 0; y < cubeFaceSize; y++) {
	for(int x = 0; x < cubeFaceSize * 2; x++) {
		//Generates LEFT
		if(x < cubeFaceSize) {
			cubeMap[x][cubeFaceSize+y] = noise.noise3D(0, y, cubeFaceSize-x);                   
		}
		//Generates RIGHT
		else {
			cubeMap[cubeFaceSize*2+(x-cubeFaceSize)][cubeFaceSize+y] = noise.noise3D(cubeFaceSize, y, x-cubeFaceSize);
		}
	}
}
//Y STATIC
for(int y = 0; y < cubeFaceSize * 2; y++) {
	for(int x = 0; x < cubeFaceSize; x++) {
		//Generates TOP
		if(y < cubeFaceSize) {
			cubeMap[cubeFaceSize+x][y] = noise.noise3D(x, 0, cubeFaceSize-y);          
		}
		//Generates BOTTOM
		else {
			cubeMap[cubeFaceSize+x][cubeFaceSize*2+(y-cubeFaceSize)] = noise.noise3D(x, cubeFaceSize, y-cubeFaceSize);
		}                
	}
}

Таким образом мы можем создать кубическую карту, которая легко преобразуется в равнопромежуточную проекцию с помощью замечательного кода, написанного Bartosz [9].

image

Сгенерированная алгоритмом кубическая карта.

image

Равнопромежуточное преобразование кубической карты.

image

Глобус кубической карты, отрендеренный на сайте maptoglobe.com [4].

Как видите, равнопромежуточная карта имеет гораздо более красивые формы, а при наложении на сферу она создаёт схожие со сферическим наложением результаты, не имея всех его недостатков. Кстати, равнопромежуточную проекцию можно легко преобразовать разными программами, например, NASA G.Projector [10], в практически любой тип карты.

В заключение

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

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

На самом деле, оно создаёт только матрицу значений в определённом интервале величин. Для изображений в градациях серого это интервал 0-255. Затем значения преобразуются в пиксель, создающий изображение, схожее с первым изображением в градациях серого, или в изображение в интервале от -11000 до 8000 для симуляции разности высот реального мира, после чего пиксели окрашиваются цветами в соответствии с интервалами высот (например, значения с 0 по 5 окрашиваются в цвет песка для симуляции побережья).

При построении Вселенной Бог использовал математику высшего уровня.

— Поль Дирак

Автор: PatientZero

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/karty/314767

Ссылки в тексте:

[1] KDotJPG: http://uniblock.tumblr.com/post/97868843242/noise

[2] масштабу, октавам и порождающим значениям: http://libnoise.sourceforge.net/glossary/index.html

[3] равнопромежуточную проекцию: https://en.wikipedia.org/wiki/Equirectangular_projection

[4] наложении на сферу: https://www.maptoglobe.com/

[5] в этом отличном посте: https://www.redblobgames.com/maps/terrain-from-noise/

[6] перевод: https://habr.com/ru/post/430384/

[7] потрясающем посте Рона Валстара: http://ronvalstar.nl/creating-tileable-noise-maps

[8] Making Worlds: http://acko.net/blog/making-worlds-introduction/

[9] с помощью замечательного кода, написанного Bartosz: https://stackoverflow.com/a/34427087/10560850

[10] NASA G.Projector: https://www.giss.nasa.gov/tools/gprojector/

[11] Источник: https://habr.com/ru/post/448324/?utm_source=habrahabr&utm_medium=rss&utm_campaign=448324