You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 11:27:42