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

image

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


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

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

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


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

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


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

image

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

image

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

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

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

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


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

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

image

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

image

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

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


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


Изображение взято с 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.

image

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

image

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

image

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

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

В заключение


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

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

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

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

— Поль Дирак