您正在使用的算法正在做的不是查看二维数组(从现在开始我们将其称为“块”),而是进行平滑处理(这些是if ((x - 1) > 0) // Check to left
条件)。
当然,这可以防止边界错误的数组索引,并且对于没有任何邻居的块来说这很好,但如果您希望该块与其邻居混合,则不能。
解决方案非常简单:如果您希望块的边缘与其相邻块平滑融合,则在平滑块时必须使用它们。
也就是说,您仍然可以将要平滑的值限制为一个块,但您必须能够读取其相邻块的边缘以计算正确的平均值。
我希望这个解释足够清楚,如果不只是让我知道,我会写一些示例代码。
顺便说一句,您参考的博客上描述的平滑算法实现得相当粗糙。您可以尝试使用单个更大的过滤器,而不是使用 3x3 卷积过滤器的多次传递。我发现这个页面(参见前几段,直到“Alpha 通道”)解释得很好,尽管它是在 java.lang.
好的,我已经编写了一些示例代码。一个警告:我将我的 2d 数组视为行优先,即myArray[y, x]
,但我将我的索引器属性声明为[x, y]
.
首先,让我们为 Block 创建一个类,这允许一些辅助方法,并且可以很容易地在我们正在处理 Block 的其余代码中看到:
public class Block
{
public const int Size = 8;
private float[,] _values;
public float this[int x, int y]
{
get { return _values[y, x]; }
set { _values[y, x] = value; }
}
public Block(float value)
{
//Initialize all cells with the given value, this makes it easier to demo the code.
_values = new float[Size, Size];
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
_values[y, x] = value;
}
}
public string[] TextLines
{
get
{
List<string> lines = new List<string>();
for (int y = 0; y < Size; y++)
{
StringBuilder sbLine = new StringBuilder();
for (int x = 0; x < Size; x++)
sbLine.AppendFormat("{0:00} ", _values[y, x]);
lines.Add(sbLine.ToString());
}
return lines.ToArray();
}
}
}
接下来,我们将定义一个抽象层,它允许我们处理 3x3 块,就好像它是一个大区域一样。坐标范围从 -8 到 15(含)。
/// <summary>
/// Wrapper around 3x3 Blocks, allows reading from a range of [-8, 15] x [-8, 15].
/// Not that writing is not supported.
/// </summary>
public class BlockContainer
{
private Block[,] _blocks;
public const float DefaultValue = 128;
public float this[int x, int y]
{
get
{
int blockX = 1, blockY = 1;
//If the coordinates exceed the center Block, move to the adjacent Block.
if (x < 0)
{
blockX--;
x += Block.Size;
}
else if (x >= Block.Size)
{
blockX++;
x -= Block.Size;
}
if (y < 0)
{
blockY--;
y += Block.Size;
}
else if (y >= Block.Size)
{
blockY++;
y -= Block.Size;
}
//Get the Block to read from - if there is no Block, just return the DefaultValue.
//This is not ideal, but for now it works.
Block block = _blocks[blockY, blockX];
return (block != null)
? block[x, y]
: DefaultValue;
}
}
public BlockContainer(Block[,] blocks)
{
if (blocks == null || blocks.GetLength(0) != 3 || blocks.GetLength(1) != 3)
throw new ArgumentException("Expected 3x3 Blocks.");
_blocks = blocks;
}
}
最后,(控制台)程序将 4x2 块定义为地形,并使用以下类平滑 1 个块:
class Program
{
private const float OneNinth = 1.0f / 9;
/// <summary>
/// Simple convolution filter that does a rectangle blur.
/// </summary>
private static float[,] Filter = new float[,]
{
{OneNinth, OneNinth, OneNinth},
{OneNinth, OneNinth, OneNinth},
{OneNinth, OneNinth, OneNinth},
};
/// <summary>
/// Simple 4x2 terrain example. For demo purposes, each block consists of only 1 value.
/// </summary>
private static Block[,] Terrain = new Block[,]
{
{ new Block(8f), new Block(16f), new Block(24f), new Block(32f) },
{ new Block(8f), new Block(32f), new Block(16f), new Block(8f) },
};
public static void Main(string[] args)
{
BlockContainer container = GetBlockContainer(2, 0); //The Block with only 24f values.
Block result = Apply3x3Filter(Filter, container);
Console.WriteLine(string.Join(Environment.NewLine, result.TextLines));
Console.WriteLine("Press enter to exit...");
Console.ReadLine();
}
/// <summary>
/// Gets the 3x3 Block area around (terrainX, terrainY) as a BlockContainer.
/// </summary>
private static BlockContainer GetBlockContainer(int terrainX, int terrainY)
{
Block[,] readBlocks = new Block[3, 3];
for (int blockY = -1; blockY <= 1; blockY++)
{
for (int blockX = -1; blockX <= 1; blockX++)
{
int sourceX = terrainX + blockX;
int sourceY = terrainY + blockY;
if (sourceX >= 0 && sourceX < 4 && sourceY >= 0 && sourceY < 2)
readBlocks[blockY + 1, blockX + 1] = Terrain[sourceY, sourceX];
}
}
return new BlockContainer(readBlocks);
}
private static Block Apply3x3Filter(float[,] filter, BlockContainer container)
{
Block resultBlock = new Block(0.0f);
for (int y = 0; y < Block.Size; y++)
{
for (int x = 0; x < Block.Size; x++)
{
//Read the 3x3 area around (x, y) and multiply them with the values in the
//convolution filter.
float sum = 0.0f;
for (int fy = -1; fy <= 1; fy++)
{
for (int fx = -1; fx <= 1; fx++)
sum += (container[x + fx, y + fy] * filter[fy + 1, fx + 1]);
}
//The sum is our averaged value for (x, y).
resultBlock[x, y] = sum;
}
}
return resultBlock;
}
}
输出是:
57 59 59 59 59 59 59 60
21 24 24 24 24 24 24 27
21 24 24 24 24 24 24 27
21 24 24 24 24 24 24 27
21 24 24 24 24 24 24 27
21 24 24 24 24 24 24 27
21 24 24 24 24 24 24 27
21 21 21 21 21 21 21 22
Press enter to exit...
这可以通过它的左侧和下方具有 16f 的块和右侧的 32f 来解释。它上面没有 Block,但我们将 DefaultValue 定义为 128,因此平均值为 59。
我希望这能解释事情:) 如果您有任何问题,请告诉我。