Unity自定义Perlin Noise实现异常:纹理硬边界问题求助
自行实现Perlin Noise出现硬边界问题
我在Unity中手动实现Perlin Noise,代码写完后生成的纹理和预期效果差距很大,高亮区域周围有明显的硬边界。我已经调整过amplitude、frequency等所有参数,但问题依然存在。想问下是代码有错误,还是需要继续调整参数?
相关代码
Perlin Noise 核心实现
public static class PerlinNoise { private static readonly int[] permutation = { 151,160,137,91,90,15, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 }; private static readonly int[] p; static PerlinNoise() { p = new int[512]; for (int x = 0; x < 512; x++) { p[x] = permutation[x % 256]; } } public static float noice(float x, float y) { int xPos =(int) (Mathf.Floor(x) % 255); int yPos =(int) (Mathf.Floor(y) % 255); float xf = x - Mathf.Floor(x); float yf = y - Mathf.Floor(y); Vector2 topRight = new Vector2(xf - 1.0f, yf - 1.0f); Vector2 topLeft = new Vector2(xf, yf - 1.0f); Vector2 bottomRight = new Vector2(xf - 1.0f, yf); Vector2 bottomLeft = new Vector2(xf, yf); int valueTopRight = p[p[xPos + 1] + yPos + 1]; int valueTopLeft = p[p[xPos] + yPos + 1]; int valueBottomRight = p[p[xPos + 1] + yPos]; int valueBottomLeft = p[p[xPos] + yPos]; float dotTopRight = Vector2.Dot(GetConstantVector(valueTopRight), topRight); float dotTopLeft = Vector2.Dot(GetConstantVector(valueTopLeft), topLeft); float dotBottomRight = Vector2.Dot( GetConstantVector(valueBottomRight), bottomRight); float dotBottomLeft = Vector2.Dot(GetConstantVector(valueBottomLeft), bottomLeft); float u = Fade(xf); float v = Fade(yf); return Lerp(u, Lerp(v, dotBottomLeft, dotTopLeft), Lerp(v, dotBottomRight, dotTopRight) ); } private static float Fade( float t) { return ((6 * t - 15) * t + 10) * t * t * t; } private static float Lerp(float t,float a1, float a2) { return a1 + t * (a2 - a1); } public static Vector2 GetConstantVector(int v) { int h = v % 4; switch (h) { case 0: return new Vector2(1.0f, 1.0f); case 1: return new Vector2(-1.0f, 1.0f); case 2: return new Vector2(-1.0f, -1.0f); case 3: return new Vector2(1.0f, -1.0f); default: return new Vector2(0, 0); } } }
噪声图生成器
public static float[,] GenerateNoiseMap(int width, int height, float frequency, float amplitude, int octaveCount, float persistence, float lacunarity) { float[,] noiseMap = new float[width, height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { noiseMap[i, j] = EvaluatePosition(i, j, frequency, amplitude, octaveCount, persistence, lacunarity); } } return noiseMap; } public static float EvaluatePosition(int x, int y, float frequency, float amplitude, int octaveCount, float persistence, float lacunarity) { float value = 0; for (int k = 0; k < octaveCount; k++) { value += PerlinNoise.noice(x * frequency, y * frequency) * amplitude; amplitude *= persistence; frequency *= lacunarity; } return value; }
Unity显示类
public void DrawNoiseMap(float[,] noiseMap) { int width = noiseMap.GetLength(0); int height = noiseMap.GetLength(1); Texture2D texture = new Texture2D(width, height); Color[] colorMap = new Color[width * height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { colorMap[width * y + x] = Color.Lerp(Color.black, Color.white, noiseMap[x, y]); } } texture.SetPixels(colorMap); texture.Apply(); textureRenderer.sharedMaterial.mainTexture= texture; textureRenderer.transform.localScale = new Vector3(width, 1, height); }
问题根源及修复方案
1. 硬边界核心原因:噪声值未归一化
Perlin Noise的原始输出范围是[-√2/2, √2/2](约[-0.707, 0.707]),叠加多八度后值会超出这个范围。而你直接把噪声值传给Color.Lerp,该方法会自动将超出[0,1]的数值截断为0或1,这就是硬边界的直接原因。
修复:生成噪声图时归一化到[0,1]
修改GenerateNoiseMap方法,先收集所有值的极值,再归一化:
public static float[,] GenerateNoiseMap(int width, int height, float frequency, float amplitude, int octaveCount, float persistence, float lacunarity) { float[,] noiseMap = new float[width, height]; float minNoise = float.MaxValue; float maxNoise = float.MinValue; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { noiseMap[i, j] = EvaluatePosition(i, j, frequency, amplitude, octaveCount, persistence, lacunarity); minNoise = Mathf.Min(minNoise, noiseMap[i, j]); maxNoise = Mathf.Max(maxNoise, noiseMap[i, j]); } } // 归一化到[0,1]区间 for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { noiseMap[i, j] = Mathf.InverseLerp(minNoise, maxNoise, noiseMap[i, j]); } } return noiseMap; }
2. Perlin实现的其他错误
- 索引越界与负数处理:原代码中
xPos = (int)(Mathf.Floor(x) % 255)会在x为负数时得到负索引,导致数组访问错误。改为用位运算处理:int xPos = (int)Mathf.Floor(x) & 255; int yPos = (int)Mathf.Floor(y) & 255; - 梯度向量问题:原代码仅使用4个轴向向量,且未归一化,会导致噪声平滑度不足。改为使用8个方向的单位向量:
private static Vector2 GetConstantVector(int v) { int h = v & 7; switch (h) { case 0: return new Vector2(1, 1).normalized; case 1: return new Vector2(-1, 1).normalized; case 2: return new Vector2(-1, -1).normalized; case 3: return new Vector2(1, -1).normalized; case 4: return new Vector2(1, 0).normalized; case 5: return new Vector2(-1, 0).normalized; case 6: return new Vector2(0, 1).normalized; case 7: return new Vector2(0, -1).normalized; default: return Vector2.zero; } } - 插值顺序错误:原代码的双线性插值顺序颠倒,导致计算逻辑错误,修复后:
float lerpX1 = Lerp(u, dotBottomLeft, dotBottomRight); float lerpX2 = Lerp(u, dotTopLeft, dotTopRight); return Lerp(v, lerpX1, lerpX2);
3. 参数优化建议
修复代码后,推荐参数:
- octaveCount:4-8
- persistence:0.5左右
- lacunarity:2左右
- frequency:初始值0.01-0.1
内容的提问来源于stack exchange,提问作者Olof N
相关产品推荐
相关产品推荐

