Сглаживание случайных шумов с разными амплитудами
У меня есть функция, которая возвращает ограниченный шум. Например, давайте представим, что входной диапазон равен [-1, 1]. С моим методом я могу вернуть ограниченный / в диапазоне шум (в зависимости от биома, который мы в настоящее время).
/// <summary>
/// Converts the range.
/// </summary>
/// <param name="originalStart">The original start.</param>
/// <param name="originalEnd">The original end.</param>
/// <param name="newStart">The new start.</param>
/// <param name="newEnd">The new end.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
public static float ConvertRange(
float originalStart, float originalEnd, // original range
float newStart, float newEnd, // desired range
float value) // value to convert
{
float scale = (newEnd - newStart) / (originalEnd - originalStart);
return (newStart + ((value - originalStart) * scale));
}
/// <summary>
/// Gets the bounded noise.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="meanHeight">Height of the mean.</param>
/// <param name="amplitude">The amplitude.</param>
/// <returns></returns>
// [InRange(-.5f, .5f)] && [InRange(0, 1)]
public static float GetBoundedNoise(float value, float meanHeight, float amplitude)
{
return Mathf.Clamp01(ConvertRange(0, 1, -amplitude, amplitude, ConvertRange(-1, 1, 0, 1, value)) + (meanHeight + .5f));
}
Отметьте это, чтобы понять, что означает средняя высота и амплитуда: https://i.gyazo.com/9dc9cbe949f82d7342d7778e904563de.mp4
Примечание. Значение шума задается библиотекой FastNoise. (Вы можете увидеть это на Github)
Проблема в том, что на границе каждого региона есть несоответствие высоты:
Нормальный регион:
Шумообразованный регион:
Черный пиксель равен y=0, а белый пиксель y=1. (Вы можете игнорировать желтые пиксели)
Но, как видите, есть разные биомы с разными амплитудами и средней высотой (Вода, Трава, Трава, DryGrass, Асфальт).
Я попробовал свертку по Гауссу, но есть проблема: слишком много итераций для CPU (было бы оптимальным для выполнения в GPU).
Зачем? Ну, я применяю гауссову свертку для каждого пикселя границы области (у меня есть оптимизированный метод, чтобы получить это). Представьте, что мы получаем 810 тыс. Баллов. И применять сверточный пиксель foreach имеет 81 итерацию (чтобы получить среднее значение высоты из этой части). Но это только для одного пикселя, теперь мы должны сделать среднее значение для других 81 пикселей (9x9) или 25 пикселей (5x5) или чего-то еще.
Есть (в лучшем случае) 1 640 250000 итераций (чтобы получить очень маленькую сглаженную сетку вокруг границы каждого региона).
Вы можете увидеть мой старый код для этого:
// Smothing segments
//var kernel = Kernels.GAUSSIAN_KERNEL_9;
//int kernelSize = kernel.GetLength(0);
//if (pol != null && !pol.Segments.IsNullOrEmpty())
// foreach (Segment segment in pol.Segments)
// {
// int d = segment.Distance;
// for (int i = 0; i <= d; ++i)
// {
// Point p = (Vector2)segment.start + segment.Normal * d;
// //if (d % kernelSize == 0) // I tried to get less itwrations by checking if the current d modulus from kernelSize was 0. But no luck.
// Filters<Color32>.ConvolutionAtPoint(mapWidth, mapHeight, p.x, p.y, target, kernel, 1, pol.Center.x, pol.Center.y, true);
// }
// }
//else
//{
// if (pol == null)
// ++nullPols;
// else if (pol != null && pol.Segments.IsNullOrEmpty())
// ++nullSegments;
//}
++ являются счетчиками отладки, игнорируйте их.
И Convolution at Point делает следующее:
private static void ConvolutionAtPointFunc(int width, int height, T[] source, params object[] parameters)
{
float[][] kernel = (float[][])parameters[0];
int kernelSize = kernel.Length;
int iteration = (int)parameters[1];
int _x = (int)parameters[2];
int _y = (int)parameters[3];
int xOffset = (int)parameters[4];
int yOffset = (int)parameters[5];
bool withGrid = (bool)parameters[6];
for (int ite = 0; ite < iteration; ++ite)
{
Color c = new Color(0f, 0f, 0f, 0f);
for (int y = 0; y < kernelSize; ++y)
{
int ky = y - kernelSize / 2;
for (int x = 0; x < kernelSize; ++x)
{
int kx = x - kernelSize / 2;
try
{
if (!withGrid)
{
c += ((Color)(dynamic)source[F.P(_x + kx + xOffset, _y + ky + yOffset, width, height)]) * kernel[x][y];
++FiltersDebug.convolutionIterations;
}
else
{
for (int i = 0; i < 81; ++i)
{
int __x = i % 9,
__y = i / 9;
c += ((Color)(dynamic)source[F.P(_x + __x + kx + xOffset, _y + __y + ky + yOffset, width, height)]) * kernel[x][y];
++FiltersDebug.convolutionIterations;
}
source[F.P(_x + kx + xOffset, _y + ky + yOffset, width, height)] = (dynamic)c;
}
}
catch
{
++FiltersDebug.outOfBoundsExceptionsIn;
}
}
}
if (!withGrid)
try
{
source[F.P(_x + xOffset, _y + yOffset, width, height)] = (dynamic)c;
}
catch
{
++FiltersDebug.outOfBoundsExceptions;
}
}
}
Как видите, очень неоптимизировано. Код от этого: http://wiki.unity3d.com/index.php/TextureFilter
Я не могу представить другого способа сделать такой подход. Лучшее, что я мог себе представить, это нарисовать перпендикулярную линию (перпендикулярно текущему сегменту (у меня есть утилита, чтобы получить ребра многоугольника (сегмент образован начальной точкой и конечной точкой, где начало сегмента = текущее). край и конец сегмента = предыдущий край))) (со средним шумом для каждой точки на линии). Но есть и проблема:
Существует промежуток (отмечен желтым цветом) между сегментами с тупой проекцией. И перекрывающийся градиент шума на сегментах с четкой проекцией.
Другой подход, который я реализовал, - получить градиентный контур от всех границ региона, которые в этом нуждаются.
Что-то вроде того:
Я также видел реализацию Cuberite ( http://cuberite.xoft.cz/docs/Generator.html):
Но я не понимаю эту часть, и если я могу извлечь что-то из этого:
Если мы возьмем площадь 9x9 биомов, центрированную вокруг запрашиваемого столбца, сгенерируем высоту для каждого из них в биомах, подведем их итоги и поделим на 81 (количество суммированных биомов), мы будем эффективно получать 9-кратное скользящее среднее за местность, и все границы внезапно станут гладкими. На следующем рисунке показана ситуация из предыдущего абзаца после применения процесса усреднения.
Примечание: я уже создал искаженную функцию voronoi, чтобы получить текущий биом в точке местности (следуя этому руководству, но я не до конца понимаю, что делать, потому что я не понимаю этот подход, и я также не вижу никакого кода относительно к этому тексту).
Но я не знаю, с чего начать и как решить проблему с оптимизированным алгоритмом. Кроме того, я не знаю, что исследовать. Поэтому у меня есть проблема, по этой причине любая помощь приветствуется.
1 ответ
Я думаю, что это не лучший способ сделать это.
Но всем, кто посещает этот вопрос, вам следует посетить Часть 2: Сглаживание шумов с разными амплитудами (Часть 2)
Есть очень хороший ответ.