2D-рельеф местности с использованием Perlin Noise (Tile Based)
Я искал перлин-шум для приложения, над которым я работаю, это в основном игра "двухмерный вид сбоку" (симуляция). Я не совсем понимаю, как работает перлин-шум, но понимаю общее представление.
Я нашел эту статью с кодом Perlin Noise и решил дать ее назад, способ, которым я настроил world gen на данный момент, состоит в том, что каждый "кусок" имеет плитки размером 16x16, и я только что заявил, что если Y >= HEIGHT / 2, сделать все плитки "сплошные", иначе "воздух", так что это работает... но земля очень ровная, как вы могли себе представить. Вот где я думал, что появится шум перлина, поэтому я использовал код, найденный на сайте выше, но я не уверен, как преобразовать шум в сплошные / воздушные плитки.
Как видите не очень реалистичная местность
Я больше ищу местность, чтобы быть как ниже. должен выглядеть как горы и холмы) Я не беспокоюсь о острых краях (квадрат)
Я не уверен, как перебрать массив плиток, чтобы превратить шум в выше
В данный момент я перебираю массив плиток (2d) примерно так, но, как вы можете видеть из первого рисунка, он просто рандомизирует, является ли плитка сплошной или нет, он не создает холмистую местность или горы.
for(int Y = 0;Y < HEIGHT;++Y) {
for(int X = 0;X < WIDTH;++X) {
Tile Tile;
if(noise(X,Y) < 0) {
Tile.Type = BLOCK;
} else {
Tile.Type = Air;
}
// Then I push the Tile on to a vector
Я должен также упомянуть, что справа от этого фрагмента 16x16 есть еще один фрагмент, который должен быть сопоставлен с этим, и то же самое относится к следующему фрагменту после этого. Куски не должны быть такими же, как предыдущие, только чтобы они выглядели как отдельные куски, если это имеет смысл.
Любая помощь очень ценится! Спасибо
ОБНОВИТЬ
Я изменил свои функции perlin на функции этого и реализовал то, что предложил phresnel:
PerlinNoise *test=new PerlinNoise(0.25, 6); //0.25 is Persistance 6 is Octaves
float f,h;
float seed = 804.343;
for (int X=0; X!=MAP_WIDTH; ++X) {
f = test->GetNoise((X * ChunkX) * seed);
h = f * MAP_HEIGHT + (MAP_HEIGHT / 2);
for (int y=0; y<h; ++y) {
Tile tempTile;
tempTile.Tile = TILE_NONE;
Tiles[X][y] = tempTile;
}
for (int y=h; y<MAP_HEIGHT; ++y) {
Tile tempTile;
tempTile.TileID = TILE_BLOCK;
Tiles[X][y] = tempTile;
}
}
После этого это выглядит намного лучше, я X * ChunkX
для каждого шума, как если бы я не сделал, каждый кусок будет одинаковым, так что теперь это выглядит следующим образом
Как вы можете видеть, местность - это больше, чем я хочу, чем прежде, но все же она не очень похожа на реальную местность, так как она не очень гладкая, есть ли что-то, что я могу сделать, чтобы сгладить ее общий вид, чтобы создать холмистую местность и равнины
Я удалил создание плиток под значениями шума, чтобы было легче видеть диапазон
Как вы можете видеть, все выглядит хорошо, значения просто нужно сгладить, чтобы они не выглядели как псевдослучайные значения. Мне также интересно, почему первый блок, показанный слева, имеет такие же значения на всем пути.
Благодарю.
1 ответ
Ваши результаты хороши тем, что у вас есть острова, континенты, пруды и моря. Шум Перлина помогает вам в создании случайной местности неслучайного характера.
Вы в основном уменьшаете (математически) бесконечное разрешение шумовой функции в отдельный набор из двух разных тайлов вместе с очень ограниченным разрешением блока. Учитывая это, ваш результат кажется хорошим.
Разные вещи могут улучшить ваш результат. Например, рытье между вашими плитками графики:
// Exemplary p-code, fine-tuning required
Color color_at (x, y) {
Real u = x / width, v = y / height
Real f = min (max(noise(u,v), 0), 1);
Color c = water*f + hill*(1-f);
return c;
}
Вы можете обобщить свою функцию lerp для любого количества плиток.
Другая возможность для острых граней основана на диаграммах Вороного (1). У вас та же функция, что и выше, но вместо интерполяции вы берете плитку, ближайшую к значению шума:
Color color_at (x, y) {
Real u = x / width, v = y / height
Real f = min (max(noise(u,v), 0), 1);
if (f<0.5) return water;
else return hill;
}
Это так же, как у вас, за исключением того, что вы делаете это по пикселям. Опять же, это может быть обобщено.
Если у вас есть хорошая настройка, вы можете использовать шум для всего: распределения растений / деревьев, распределения врагов, анимации (это будет функция 1d-noise) и т. Д.
Изменить как для вашего комментария:
Смежная 1d-местность:
Если вам нужен вид сбоку, как в классическом 2d-прыжке и беге, подумайте о функции 1d-шума, итерируйте от 0 до ширины изображения, на каждой остановке возьмите образец f функции шума, преобразуйте f в пространство экрана, тогда каждый пиксель выше будет частью неба, каждый пиксель ниже будет взят из ваших плиток:
for (int x=0; x!=width; ++x) {
// Get noise value:
f = noise1d (x/width);
h = f * height + (height/2); // scale and center at screen-center
// Select tile:
Tile *tile;
if (h < waterLimit) {
h = waterLimit;
tile = waterTile;
} else {
tile = terrainTile;
}
// Draw vertical line:
for (int y=0; y<h; ++y)
setPixel (x, y, skyColor(x,y));
for (int y=h; y<height; ++y)
setPixel (x, y, tileColor(x,y, tile));
}
Опять примерный псевдокод.
Полные 2d-уровни в вашей текущей настройке:
Функция шума очень гибкая. Если вы хотите меньше синего и больше зеленого, просто уменьшите постоянный член, т.е. вместо if(h < 0) -> blue
, делать if(h < -0.25) -> blue
,
1: я никогда не пробовал этот подход, поскольку я занимаюсь рендерингом 3D-ландшафта ( http://picogen.org/)