Perlin Noise生成异常:C#实现中邻接单元格不匹配问题排查
2D Perlin Noise实现中单元格衔接异常的问题

每个单元格单独显示正常,但相邻单元格无法平滑衔接(少数情况下能匹配)。
以下是原实现的代码:
原Perlin结构体代码
public struct Perlin { public struct _2D { public static float Simple(float x, float y, int seed) { Vector2 pos = new Vector2(x % 1, y % 1); // Position in the unit square Vector2[] points = new Vector2[4]; points[0] = new Vector2(Mathf.Floor(x), Mathf.Floor(y)); // Corner positions points[1] = new Vector2(Mathf.Floor(x), Mathf.Ceil(y)); // Corner positions points[2] = new Vector2(Mathf.Ceil(x), Mathf.Ceil(y)); // Corner positions points[3] = new Vector2(Mathf.Ceil(x), Mathf.Floor(y)); // Corner positions Vector2[] vectorTable = new Vector2[] { new Vector2(-1, -1), new Vector2(1, -1), new Vector2(1, 1), new Vector2(-1, 1) , new Vector2(Mathf.Sqrt(2), 0), new Vector2(0, Mathf.Sqrt(2)), new Vector2(-Mathf.Sqrt(2), 0), new Vector2(0, -Mathf.Sqrt(2)) }; Vector2[] gradients = new Vector2[4]; for (int g = 0; g < gradients.Length; g++) { gradients[g] = vectorTable[Mathf.RoundToInt(Random(new Vector2(points[g].x + seed, points[g].y + seed)) * (vectorTable.Length-1))]; // Gradient vector } Vector2[] directions = new Vector2[4]; for (int d = 0; d < directions.Length; d++) { directions[d] = (new Vector2(x - points[d].x, y - points[d].y) - pos).normalized; // Direction vector from the x and y position to the corner } float[] dots = new float[4]; for (int i = 0; i < points.Length; i++) { dots[i] = (Vector2.Dot(directions[i], gradients[i])); // Dot products } float lerp1 = (Mathf.Lerp(dots[0], dots[1], Fade(pos.y))); float lerp2 = (Mathf.Lerp(dots[2], dots[3], Fade(pos.y))); float lerp3 = (Mathf.Lerp(lerp1, lerp2, Fade(pos.x))); return (lerp3 + 1) / 2; // Remapping the output from (-1 to 1) to (0 to 1) float Fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } } } }
原Random函数代码
static float Random(Vector2 seed) { Vector2 v1 = new Vector2(3.1251f, 17.8737f); float f1 = 43758.545312f; return fract(Mathf.Sin(Vector2.Dot(seed, v1) * f1)) ; float fract(float x) { return Mathf.Abs(x % 1.0f); } }
问题根源与修复方案
1. 方向向量计算错误
当前代码中错误地对偏移向量做了减法和归一化操作:
directions[d] = (new Vector2(x - points[d].x, y - points[d].y) - pos).normalized;
new Vector2(x - points[d].x, y - points[d].y)本身就是当前点到网格顶点的偏移向量,和pos是同一个值,减法后会得到0向量,完全破坏了方向逻辑。同时Perlin Noise不需要对方向向量归一化,这会打乱梯度的分布特性。
修复后代码:
directions[d] = new Vector2(x - points[d].x, y - points[d].y);
2. 梯度索引的精度问题
使用Mathf.RoundToInt可能因浮点数精度问题导致索引越界,且(vectorTable.Length-1)的限制会浪费最后一个梯度向量的使用概率。改用Mathf.FloorToInt并直接乘以向量表长度,能保证索引稳定在合法范围内。
修复后代码:
int index = Mathf.FloorToInt(Random(new Vector2(points[g].x + seed, points[g].y + seed)) * vectorTable.Length); gradients[g] = vectorTable[index];
3. 插值顺序逻辑错误
原代码的横向插值顺序完全颠倒,导致相邻单元格的衔接逻辑混乱。正确的顺序应该是先对上下边的横向顶点分别插值,再做纵向插值。
修复后插值代码:
float bottomLerp = Mathf.Lerp(dots[0], dots[3], Fade(pos.x)); float topLerp = Mathf.Lerp(dots[1], dots[2], Fade(pos.x)); float finalLerp = Mathf.Lerp(bottomLerp, topLerp, Fade(pos.y));
修复后的完整代码
Perlin结构体
public struct Perlin { public struct _2D { public static float Simple(float x, float y, int seed) { Vector2 pos = new Vector2(x % 1, y % 1); // 单元格内相对位置 Vector2[] points = new Vector2[4]; points[0] = new Vector2(Mathf.Floor(x), Mathf.Floor(y)); // 左下角顶点 points[1] = new Vector2(Mathf.Floor(x), Mathf.Ceil(y)); // 左上角顶点 points[2] = new Vector2(Mathf.Ceil(x), Mathf.Ceil(y)); // 右上角顶点 points[3] = new Vector2(Mathf.Ceil(x), Mathf.Floor(y)); // 右下角顶点 Vector2[] vectorTable = new Vector2[] { new Vector2(-1, -1), new Vector2(1, -1), new Vector2(1, 1), new Vector2(-1, 1), new Vector2(Mathf.Sqrt(2), 0), new Vector2(0, Mathf.Sqrt(2)), new Vector2(-Mathf.Sqrt(2), 0), new Vector2(0, -Mathf.Sqrt(2)) }; Vector2[] gradients = new Vector2[4]; for (int g = 0; g < gradients.Length; g++) { int index = Mathf.FloorToInt(Random(new Vector2(points[g].x + seed, points[g].y + seed)) * vectorTable.Length); gradients[g] = vectorTable[index]; } Vector2[] directions = new Vector2[4]; for (int d = 0; d < directions.Length; d++) { directions[d] = new Vector2(x - points[d].x, y - points[d].y); } float[] dots = new float[4]; for (int i = 0; i < points.Length; i++) { dots[i] = Vector2.Dot(directions[i], gradients[i]); } float bottomLerp = Mathf.Lerp(dots[0], dots[3], Fade(pos.x)); float topLerp = Mathf.Lerp(dots[1], dots[2], Fade(pos.x)); float finalLerp = Mathf.Lerp(bottomLerp, topLerp, Fade(pos.y)); return (finalLerp + 1) / 2; float Fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } } } }
Random函数
static float Random(Vector2 seed) { Vector2 v1 = new Vector2(3.1251f, 17.8737f); float f1 = 43758.545312f; return fract(Mathf.Sin(Vector2.Dot(seed, v1) * f1)); float fract(float x) { return Mathf.Abs(x % 1.0f); } }
内容的提问来源于stack exchange,提问作者JacksStuff
相关产品推荐
相关产品推荐

