Создание Ландшафта. Фокусирование на создании Ландщафта (Перевод) Часть 7

Вот суть heightmap класса, который имеет дело с пропуском частиц:

/// <summary>

/// Generate a random heightmap using particle deposition and the passed parameters.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»min»></param>

/// <param name=»max»></param>

/// <param name=»settings»></param>

public void GenerateParticleDepositionHeightmap(int width,

int depth,

float min,

float max,

HeightmapParticleDepositionSettings settings)

{

_width = width;

_depth = depth;

_particleDepositionSettings = settings;

_heightValues = new float[_width * _depth];

GenerateParticleDepositionHeightmap();

}

/// <summary>

/// Generate a random heightmap using particle deposition and default parameters.

/// </summary>

public void GenerateParticleDepositionHeightmap()

{

int i, j, m, p, particleCount;

int x, px, minx, maxx, sx, tx;

int z, pz, minz, maxz, sz, tz;

bool done;

int[] dx = { 0, 1, 0, _width — 1, 1, 1, _width — 1, _width — 1 };

int[] dz = { 1, 0, _depth — 1, 0, _depth — 1, 1, _depth — 1, 1 };

float ch, ph;

int[] calderaMap = new int[_width * _depth];

// Clear the heightmap.

for (i = 0; i < _width * _depth; ++i)

{

_heightValues[i] = 0.0f;

}

// For each jump ..

for (p = 0; p < _particleDepositionSettings.Jumps; ++p)

{

// Pick a random spot.

x = RandomHelper.Random.Next(_width);

z = RandomHelper.Random.Next(_depth);

// px and pz track where the caldera is formed.

px = x;

pz = z;

// Determine how many particles we are going to drop.

particleCount = RandomHelper.Random.Next(

_particleDepositionSettings.MinParticlesPerJump,

_particleDepositionSettings.MaxParticlesPerJump);

// Drop particles.

for (i = 0; i < particleCount; ++i)

{

// If we have to move the drop point, agitate it in a random direction.

if ((_particleDepositionSettings.PeakWalk != 0) &&

((i % _particleDepositionSettings.PeakWalk) == 0))

{

m = RandomHelper.Random.Next(8);

x = (x + dx[m] + _width) % _width;

z = (z + dz[m] + _depth) % _depth;

}

// Drop it.

_heightValues[x + z * _width] += 1.0f;

// Now agitate it until it settles.

sx = x;

sz = z;

done = false;

// While it’s not settled

while (!done)

{

// Consider it is.

done = true;

// Pick a random neighbor and start inspecting.

m = RandomHelper.Random.Next();

for (j = 0; j < 8; ++j)

{

tx = (sx + dx[(j + m) % 8]) % _width;

tz = (sz + dz[(j + m) % 8]) % _depth;

// If we can move to this neighbor, do it.

if (_heightValues[tx + tz * _width] + 1.0f <

_heightValues[sx + sz * _width])

{

_heightValues[tx + tz * _width] += 1.0f;

_heightValues[sx + sz * _width] -= 1.0f;

sx = tx;

sz = tz;

done = false;

break;

}

}

}

// Check to see if the latest point is higher than the caldera point.

// If so, move the caldera point here.

if (_heightValues[sx + sz * _width] >

_heightValues[px + pz * _width])

{

px = sx;

pz = sz;

}

}

// Now that we are done with the peak, invert the caldera.

//

// ch is the caldera cutoff altitude.

// ph is the height at the caldera start point.

ph = _heightValues[px + pz * _width];

ch = ph * (1.0f — _particleDepositionSettings.Caldera);

// We do a floodfill, so we use an array of integers to mark the visited locations.

minx = px;

maxx = px;

minz = pz;

maxz = pz;

// Mark the start location for the caldera.

calderaMap[px + pz * _width] = 1;

done = false;

while (!done)

{

// Assume work is done.

done = true;

sx = minx;

sz = minz;

tx = maxx;

tz = maxz;

// Examine the bounding rectangle looking for unvisited neighbors.

for (x = sx; x <= tx; ++x)

{

for (z = sz; z <= tz; ++z)

{

px = (x + _width) % _width;

pz = (z + _depth) % _depth;

// If this cell is marked but unvisited, check it out.

if (calderaMap[px + pz * _width] == 1)

{

// Mark cell as visited.

calderaMap[px + pz * _width] = 2;

// If this cell should be inverted,

// invert it and inspect neighbors.

// We mark any unmarked and unvisited neighbor.

// We don’t invert any cells whose height exceeds the

// initial caldera height.

// This prevents small peaks from destroying large ones.

if ((_heightValues[px + pz * _width] > ch) &&

(_heightValues[px + pz * _width] <= ph))

{

done = false;

_heightValues[px + pz * _width] =

2 * ch — _heightValues[px + pz * _width];

// Left and right neighbors.

px = (px + 1) % _width;

if (calderaMap[px + pz * _width] == 0)

{

if (x + 1 > maxx)

{

maxx = x + 1;

}

calderaMap[px + pz * _width] = 1;

}

px = (px + _width — 2) % _width;

if (calderaMap[px + pz * _width] == 0)

{

if (x — 1 < minx)

{

minx = x — 1;

}

calderaMap[px + pz * _width] = 1;

}

// Top and bottom neighbors.

px = (x + _width) % _width;

pz = (pz + 1) % _depth;

if (calderaMap[px + pz * _width] == 0)

{

if (z + 1 > maxz)

{

maxz = z + 1;

}

calderaMap[px + pz * _width] = 1;

}

pz = (pz + _depth — 2) % _depth;

if (calderaMap[px + pz * _width] == 0)

{

if (z — 1 < minz)

{

minz = z — 1;

}

calderaMap[px + pz * _width] = 1;

}

}

}

}

}

}

}

// Since calderas increase aliasing, we erode the terrain with a filter value proportional

// to the prominence of the caldera.

FilterHeightmap(_particleDepositionSettings.Caldera);

// Normalize the heightmap.

NormalizeHeightmap();

}

Вы можете просто нажать кнопку B или клавишу ENTER, чтобы циклически пройти каждые типы heightmap.

Вы можете найти исходный текст в папке P303ParticleDeposition.

Шум Перлина

Еще один способ создать heightmaps состоит в том, чтобы генерировать их, используя шум Перлина. Шум Перлина был изобретен Кеном Перлином (Ken Perlin) и является одним из самых общих способов создать процедурное искажение. Идея заключается в том, что мы добавляем шумы различных частот. Чтобы сделать это, мы сначала нуждаемся в генераторе чисел, который возвращает одно и то же случайное число для определенного начального числа. Вот генератор, который мы используем:

/// <summary>

/// Return some noise for a specific seed.

/// </summary>

/// <param name=»i»></param>

/// <returns></returns>

public static float Noise(int i)

{

i = (i << 13) ^ i;

return (1.0f — ((i * (i * i * 15731 + 789221) + 1376312589) & 0x7FFFFFFF) / 1073741824.0f);

}

Эта функция возвращает значение с плавающей точкой в диапазоне от-1.0f до 1.0f. Затем, мы должны уметь эффективно интерполировать между двумя случайными значениями. Линейная интерполяция не очень хороша для heightmaps, так что мы будем использовать кубическую интерполяцию, она доступно в XNA в классе MathHelper. Она точнее, и это очень важно. Если мы генерируем связку случайных чисел, используя шумовую функцию, и если мы создаем кривую из этих чисел с помощью кубической интерполяции, то мы получаем хорошие случайные кривые.

До этого я сказал, что в шуме Перлина используются несколько искажений одновременно, не так ли? В нашем случае мы одновременно добавим кривые, сделанные из искажения. Частота — число случайных чисел, которые мы используем, чтобы генерировать кривую. Для каждой кривой, которую мы используем, мы удваиваем число случайных чисел. Каждую удвоенную частоту называют октавой. Когда частота каждой кривой увеличивается, мы хотим, чтобы амплитуда (влияние) каждой кривой уменьшилось. Мы управляем, сколько амплитуд уменьшаются в октаву, используя постоянство.

Формула весьма проста: Амплитуда = Постоянство ^ октава

Так же очень круто, что мы можем работать с шумом Перлина в 2D, 3D, да и вообще в любом другом измерении.

Вот некоторые иллюстрирации шума Перлина:


Начальное число = 1337, постоянство = 0.75, октавы = 10


То же самое в каркасе.


Начальное число = 1669, постоянство = 0.8, октавы = 4.


То же самое в каркасе.


Начальное число = 12345, постоянство = 0.7, октавы = 8.


То же самое в каркасе.

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

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

Вот суть heightmap класса, который работает с шумом Перлина:

/// <summary>

/// Generate a random heightmap using perlin noise and the passed parameters.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»min»></param>

/// <param name=»max»></param>

/// <param name=»settings»></param>

public void GeneratePerlinNoiseHeightmap(int width,

int depth,

float min,

float max,

HeightmapPerlinNoiseSettings settings)

{

_width = width;

_depth = depth;

_perlinNoiseSettings = settings;

_heightValues = new float[_width * _depth];

GeneratePerlinNoiseHeightmap();

}

/// <summary>

/// Generate a random heightmap using perlin noise and the default parameters.

/// </summary>

public void GeneratePerlinNoiseHeightmap()

{

int txi, tzi;

float freq, amp;

float xf, tx, fracx;

float zf, tz, fracz;

float v1, v2, v3, v4;

float i1, i2, total;

// For each height..

for (int z = 0; z < _depth; ++z)

{

for (int x = 0; x < _width; ++x)

{

// Scale x and y to the range of 0.0f, 1.0f.

xf = (float)x / (float)_width;

zf = (float)z / (float)_depth;

total = 0.0f;

// For each octaves..

for (int i = 0; i < _perlinNoiseSettings.Octaves; ++i)

{

// Calculate frequency and amplitude (different for each octave).

freq = (float)Math.Pow(2.0, i);

amp = (float)Math.Pow(_perlinNoiseSettings.Persistence, i);

// Calculate the x, z noise coodinates.

tx = xf * freq;

tz = zf * freq;

txi = (int)tx;

tzi = (int)tz;

// Calculate the fractions of x and z.

fracx = tx — txi;

fracz = tz — tzi;

// Get noise per octave for these four points.

v1 = RandomHelper.Noise(txi + tzi * 57 + _perlinNoiseSettings.Seed);

v2 = RandomHelper.Noise(txi + 1 + tzi * 57 + _perlinNoiseSettings.Seed);

v3 = RandomHelper.Noise(txi + (tzi + 1) * 57 + _perlinNoiseSettings.Seed);

v4 = RandomHelper.Noise(txi + 1 + (tzi + 1) * 57 + _perlinNoiseSettings.Seed);

// Smooth noise in the x axis.

i1 = MathHelper.SmoothStep(v1, v2, fracx);

i2 = MathHelper.SmoothStep(v3, v4, fracx);

// Smooth in the z axis.

total += MathHelper.SmoothStep(i1, i2, fracz) * amp;

}

// Save to heightmap.

_heightValues[x + z * _width] = total;

}

}

// Normalize the terrain.

NormalizeHeightmap();

}

Вы можете просто нажать кнопку B или клавишу ENTER, чтобы циклически пройти все типы heightmap.

Вы можете найти исходный текст в папке P304PerlinNoise.

Комбинирование Heightmap

В этой части мы добавляем возможность объединения двух heightmaps. Это довольно просто сделать. Для всех высот heightmap мы берем его высоту, умноженную на (1 — отношение), и добавляем высоту другого heightmap при передаче координат, которую мы умножаем на отношение. Отношение — процент от heightmap, который передают как параметр, он находится в диапазоне [0.0f; 1.0f]. Кроме того, я добавил параметр размера шума для создания шума Перлина и функцию NormalizeHeightmap с минимальным и максимальным параметрами.

Вот иллюстрирации комбинирования heightmap:


Основной heightmap.


Основной heightmap в 90 % и случайный heightmap в 10 %.


Основной heightmap в 50 % и случайное формирование ошибки в 50 %.


Основной heightmap в 50 % и случайное формирование ошибки в 50 %.


Основной heightmap в 50 % и случайное формирование ошибки в 50 %.


Основной heightmap в 50 % и случайная середина смещения пункта в 50 %.


Основной heightmap в 50 % и случайная середина смещения пункта в 50 %.


Основной heightmap в 50 % и случайная середина смещения пункта в 50 %.


Основной heightmap в 50 % и случайное смещение частицы в 50 %.


Основной heightmap в 50 % и случайное смещение частицы в 50 %.


Основной heightmap в 50 % и случайное смещение частицы в 50 %.


Основной heightmap в 50 % и случайное смещение частицы в 50 %.


Основной heightmap в 50 % и случайный шум Перлина в 50 %.


Основной heightmap в 50 % и случайный шум Перлина в 50 %.


Основной heightmap в 50 % и случайный шум Перлина в 50 %.

Вот функция CombineHeightmap:

/// <summary>

/// Combine the heightmap with the passed heightmap.

/// The amount variable is the ratio of the heightmap passed as a parameter.

/// </summary>

/// <param name=»heightmap»></param>

/// <param name=»amount»></param>

public void CombineHeightmap(Heightmap heightmap, float amount)

{

// Reject the heightmap if it doesn’t fit.

if ((_width != heightmap.Width) ||

(_depth != heightmap.Depth) ||

(_minimumHeight != heightmap.MinimumHeight) ||

(_maximumHeight != heightmap.MaximumHeight))

{

return;

}

// Combine the heightmaps.

// H1 = H1 * (1.0f — amount) + H2 * amount

for (int x = 0; x < _width; ++x)

{

for (int z = 0; z < _depth; ++z)

{

_heightValues[x + z * _width] =

_heightValues[x + z * _width] * (1.0f — amount) +

heightmap.GetHeightValue(x, z) * amount;

}

}

}

Вот пример на, как мы используем ее в классе GameFOT:

// Generate a random heightmap using perlin noise.

HeightmapPerlinNoiseSettings settings = new HeightmapPerlinNoiseSettings(1337, 0.75f, 10, 1.0f);

Heightmap heightmap = (Heightmap)_heightmap.Clone();

heightmap.PerlinNoiseSettings = settings;

heightmap.GeneratePerlinNoiseHeightmap();

_terrain.Heightmap = (Heightmap)_heightmap.Clone();

_terrain.Heightmap.CombineHeightmap(heightmap, 0.5f);

_terrain.BuildTerrain();

_heightmapType = «Base 50% + perlin noise (1337, 0.75f, 10, 1.0f) 50%»;

Вы можете найти исходный текст в папке P305HeightmapCombination.

Возвращаясь к heightmask

В этой части мы добавим поддержку heightmask, умножение и другие операции в класс heightmap. Этот новый heightmask класс работает как трафарет. У него одинаковый размер с heightmap и все значения маски между 0.0f и 1.0f.

Так как еще появляется возможность добавления и умножения heightmap, мы можем сделать комбинирование различных heightmap еще более интересным. Например, вы можете использовать множественные алгоритмы создания ландшафта вместе и применить маску, чтобы смешать ландшафт нескольких типов, например вулканическую область и острова. Вы могли бы задаться вопросом, каково различие между использованием heightmap в 0.0f — 1.0f диапазон и heightmask?

Различие находится в способе, которым мы комбинируем их:

Вот функция ApplyMask класса Heightmap:

/// <summary>

/// Apply a mask on the heightmap.

/// </summary>

/// <param name=»heightmask»></param>

public void ApplyMask(Heightmask heightmask)

{

// Reject the heightmask if it doesn’t fit.

if ((_width != heightmask.Width) ||

(_depth != heightmask.Depth))

{

return;

}

// Apply the heightmask.

for (int x = 0; x < _width; ++x)

{

for (int z = 0; z < _depth; ++z)

{

_heightValues[x + z * _width] =

(_heightValues[x + z * _width] — _minimumHeight) *

heightmask.GetMaskValue(x, z) + _minimumHeight;

}

}

}

Смотрите, функция ApplyMask уверяет вас, что значения останутся между минимальной и максимальной высотами heightmap. Если Вы хотите использовать heightmap как маску, Вы можете только умножить ее на heightmap, имеющим в качестве максимальной и минимальной высот 0.0f и 1.0f соответственно.

Вот функция MultiplyHeightmap и функция AddHeightmap:

/// <summary>

/// Multiply two heightmaps together.

/// </summary>

/// <param name=»heightmap»></param>

public void MultiplyHeightmap(Heightmap heightmap)

{

// Reject the heightmap if it doesn’t fit.

if ((_width != heightmap.Width) ||

(_depth != heightmap.Depth) ||

(_minimumHeight != heightmap.MinimumHeight) ||

(_maximumHeight != heightmap.MaximumHeight))

{

return;

}

// Multiply the heightmaps together.

for (int x = 0; x < _width; ++x)

{

for (int z = 0; z < _depth; ++z)

{

_heightValues[x + z * _width] = _heightValues[x + z * _width] *

heightmap.GetHeightValue(x, z);

}

}

}

/// <summary>

/// Add two heightmaps together.

/// </summary>

/// <param name=»heightmap»></param>

public void AddHeightmap(Heightmap heightmap)

{

// Reject the heightmap if it doesn’t fit.

if ((_width != heightmap.Width) ||

(_depth != heightmap.Depth) ||

(_minimumHeight != heightmap.MinimumHeight) ||

(_maximumHeight != heightmap.MaximumHeight))

{

return;

}

// Add the heightmaps together.

for (int x = 0; x < _width; ++x)

{

for (int z = 0; z < _depth; ++z)

{

_heightValues[x + z * _width] = _heightValues[x + z * _width] +

heightmap.GetHeightValue(x, z);

}

}

}

Вот несколько скриншотов простого использования heightmask:


heightmask относится к heightmap на максимальной высоте.


heightmask относится к случайному heightmap.


heightmask относится к случайному heightmap.

Вы можете найти исходный текст в папке P306HeightmaskRevisited.

Заключение

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

Если что – не стесняйтесь обращаться и следите за следующей серией на моем вебсайте, learn.nerdy-inverse.com

Загрузите исходный текст

Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s