平滑不同振幅的随机噪声
Smoothing random noises with different amplitudes
我有一个 return 有界噪声的函数。例如,假设 out 输入范围是 [-1, 1]。使用我的方法,我可以 return 一个 bounded/in 范围的噪音(取决于我们目前所在的生物群系)。
/// <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。 (可以忽略黄色像素)
但如您所见,存在具有不同振幅和平均高度的不同生物群落(水、草、草、干草、沥青)。
我试过高斯卷积,但有一个问题:CPU的迭代太多(最好在GPU中执行)。
为什么?好吧,我为每个区域边界像素应用高斯卷积(我有一个优化的方法来获得它)。想象一下我们得到 810k 点。并为每个像素应用卷积有 81 次迭代(以获取该部分的高度平均值)。但这只是一个像素,现在我们必须对另外 81 个像素 (9x9) 或 25 个像素 (5x5) 或其他任何像素进行平均。
有(在最好的情况下)1,640,250,000 次迭代(在每个区域边界周围得到一个非常小的平滑网格)。
你可以看到我的旧代码:
// 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;
//}
++是调试计数器,忽略它们。
点卷积执行以下操作:
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#heightgen):
但是我不明白这部分,如果我能从中提取一些东西:
If we take the area of 9x9 biomes centered around the queried column,
generate height for each of the biomes therein, sum them up and divide
by 81 (the number of biomes summed), we will be effectively making a
9-long running average over the terrain, and all the borders will
suddenly become smooth. The following image shows the situation from
the previous paragraph after applying the averaging process.
注意:我已经创建了一个扭曲的 voronoi 函数来获取地形点的当前生物群落(遵循本指南,但我不完全理解该怎么做,因为我不理解这种方法,我也不理解可以看到与本文相关的任何代码)。
但我不知道从哪里开始,也不知道如何使用优化算法解决问题。另外,我不知道要研究什么。所以我有一个问题,因此,欢迎任何帮助。
我认为这不是最好的方法。
但是,对于访问此问题的每个人,您应该访问第 2 部分:
有一个很好的答案。
我有一个 return 有界噪声的函数。例如,假设 out 输入范围是 [-1, 1]。使用我的方法,我可以 return 一个 bounded/in 范围的噪音(取决于我们目前所在的生物群系)。
/// <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。 (可以忽略黄色像素)
但如您所见,存在具有不同振幅和平均高度的不同生物群落(水、草、草、干草、沥青)。
我试过高斯卷积,但有一个问题:CPU的迭代太多(最好在GPU中执行)。
为什么?好吧,我为每个区域边界像素应用高斯卷积(我有一个优化的方法来获得它)。想象一下我们得到 810k 点。并为每个像素应用卷积有 81 次迭代(以获取该部分的高度平均值)。但这只是一个像素,现在我们必须对另外 81 个像素 (9x9) 或 25 个像素 (5x5) 或其他任何像素进行平均。
有(在最好的情况下)1,640,250,000 次迭代(在每个区域边界周围得到一个非常小的平滑网格)。
你可以看到我的旧代码:
// 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;
//}
++是调试计数器,忽略它们。
点卷积执行以下操作:
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#heightgen):
但是我不明白这部分,如果我能从中提取一些东西:
If we take the area of 9x9 biomes centered around the queried column, generate height for each of the biomes therein, sum them up and divide by 81 (the number of biomes summed), we will be effectively making a 9-long running average over the terrain, and all the borders will suddenly become smooth. The following image shows the situation from the previous paragraph after applying the averaging process.
注意:我已经创建了一个扭曲的 voronoi 函数来获取地形点的当前生物群落(遵循本指南,但我不完全理解该怎么做,因为我不理解这种方法,我也不理解可以看到与本文相关的任何代码)。
但我不知道从哪里开始,也不知道如何使用优化算法解决问题。另外,我不知道要研究什么。所以我有一个问题,因此,欢迎任何帮助。
我认为这不是最好的方法。
但是,对于访问此问题的每个人,您应该访问第 2 部分:
有一个很好的答案。