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

如何在C#中实现Perlin噪声生成器?

2D Perlin噪声的C#实现与调试过程

基于马里兰大学的Perlin噪声课程资料,我尝试自行编码实现2D Perlin噪声算法,过程中遇到不少困难,最终完成后决定分享代码。

初始尝试代码

using System;
using System.Drawing;

class PerlinNoise2D
{
    // 用于平滑过渡的Fade函数
    private static double Fade(double t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    // 线性插值函数
    private static double Lerp(double t, double a, double b)
    {
        return a + t * (b - a);
    }

    // 梯度向量与位移向量的点积
    private static double DotGridGradient(int gridX, int gridY, double x, double y, Random rand)
    {
        // 在网格点生成伪随机梯度向量
        double angle = rand.NextDouble() * Math.PI * 2;
        double gradX = Math.Cos(angle);
        double gradY = Math.Sin(angle);

        // 从网格点到输入点的位移向量
        double dx = x - gridX;
        double dy = y - gridY;

        // 返回点积
        return (dx * gradX + dy * gradY);
    }

    // 单层级噪声函数
    public static double Noise(double x, double y, int gridSize, Random rand)
    {
        // 确定点所在的网格单元
        int x0 = (int)Math.Floor(x) % gridSize;
        int y0 = (int)Math.Floor(y) % gridSize;
        int x1 = (x0 + 1) % gridSize;
        int y1 = (y0 + 1) % gridSize;

        // 网格单元内的局部坐标
        double localX = x - Math.Floor(x);
        double localY = y - Math.Floor(y);

        // 应用Fade函数平滑过渡
        double xFade = Fade(localX);
        double yFade = Fade(localY);

        // 计算四个角点的梯度点积
        double n00 = DotGridGradient(x0, y0, x, y, rand);
        double n10 = DotGridGradient(x1, y0, x, y, rand);
        double n01 = DotGridGradient(x0, y1, x, y, rand);
        double n11 = DotGridGradient(x1, y1, x, y, rand);

        // 沿X轴插值两行结果
        double nx0 = Lerp(xFade, n00, n10);
        double nx1 = Lerp(xFade, n01, n11);

        // 沿Y轴插值得到最终噪声值
        return Lerp(yFade, nx0, nx1);
    }

    // 多八度的Perlin噪声,生成分形细节
    public static double Perlin(double x, double y, int gridSize, int octaves, double persistence)
    {
        double total = 0;
        double frequency = 1;
        double amplitude = 1;
        double maxValue = 0;

        // 固定随机种子以保证结果一致
        Random rand = new(69);

        // 遍历所有八度层级
        for (int i = 0; i < octaves; i++)
        {
            // 生成当前八度的噪声并缩放
            total += Noise(x * frequency, y * frequency, gridSize, rand) * amplitude;

            // 累加最大可能值用于归一化
            maxValue += amplitude;

            // 衰减系数控制后续八度的贡献度
            amplitude *= persistence;

            // 每八度频率翻倍
            frequency *= 2;
        }

        // 归一化到[0,1]范围
        return total / maxValue;
    }

    static void Main()
    {
        // Perlin噪声配置参数
        int width = 512;           // 输出图像宽度
        int height = 512;          // 输出图像高度
        int gridSize = 16;         // 噪声网格尺寸
        int octaves = 4;           // 八度数量
        double persistence = 0.5;  // 衰减系数

        // 创建存储噪声的位图
        Bitmap bitmap = new Bitmap(width, height);

        // 为每个像素生成Perlin噪声
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                // 归一化坐标以适配噪声网格
                double noiseX = (double)x / width * gridSize;
                double noiseY = (double)y / height * gridSize;

                // 计算Perlin噪声值
                double noiseValue = Perlin(noiseX, noiseY, gridSize, octaves, persistence);

                // 将噪声值映射到灰度范围(0-255)
                int gray = (int)(noiseValue * 255);
                gray = Math.Max(0, Math.Min(255, gray)); // 确保值在有效范围内

                Color color = Color.FromArgb(gray, gray, gray);

                // 设置像素颜色
                bitmap.SetPixel(x, y, color);
            }
        }

        // 保存图像到文件
        bitmap.Save("PerlinNoise.png");
        Console.WriteLine("Perlin噪声图像已保存为'PerlinNoise.png'。");
    }
}

初始生成图像

初始生成的Perlin噪声图像

初始代码的核心问题在于:每次计算梯度时都实时生成随机向量,导致噪声图案缺乏连续性和一致性,最终生成的图像纹理杂乱。


优化后的最终代码

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

public class PerlinNoise
{
    private readonly int[] permutation;

    public PerlinNoise(int seed = 0)
    {
        Random rand = new Random(seed);

        // 创建置换表
        permutation = new int[512];
        for (int i = 0; i < 256; i++)
        {
            permutation[i] = i;
        }

        // 打乱数组
        for (int i = 255; i > 0; i--)
        {
            int j = rand.Next(i + 1);
            (permutation[i], permutation[j]) = (permutation[j], permutation[i]);
        }

        // 复制置换表以避免越界
        for (int i = 0; i < 256; i++)
        {
            permutation[256 + i] = permutation[i];
        }
    }

    private static double Fade(double t)
    {
        // Ken Perlin改进的平滑函数:6t^5 - 15t^4 + 10t^3
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    private static double Lerp(double t, double a, double b)
    {
        return a + t * (b - a);
    }

    private static double Grad(int hash, double x, double y)
    {
        // 将哈希值的低4位转换为12种梯度方向
        int h = hash & 15;
        double u = h < 8 ? x : y;
        double v = h < 4 ? y : (h == 12 || h == 14 ? x : 0);
        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
    }

    public double Noise(double x, double y)
    {
        // 找到包含点的单位网格
        int X = (int)Math.Floor(x) & 255;
        int Y = (int)Math.Floor(y) & 255;

        // 计算点在网格内的相对坐标
        x -= Math.Floor(x);
        y -= Math.Floor(y);

        // 计算x和y的平滑曲线
        double u = Fade(x);
        double v = Fade(y);

        // 哈希四个网格角点的坐标
        int A = permutation[X] + Y;
        int AA = permutation[A];
        int AB = permutation[A + 1];
        int B = permutation[X + 1] + Y;
        int BA = permutation[B];
        int BB = permutation[B + 1];

        // 混合四个角点的结果
        double res = Lerp(v,
            Lerp(u,
                Grad(permutation[AA], x, y),
                Grad(permutation[BA], x - 1, y)
            ),
            Lerp(u,
                Grad(permutation[AB], x, y - 1),
                Grad(permutation[BB], x - 1, y - 1)
            )
        );

        return res;
    }

    public double OctaveNoise(double x, double y, int octaves, double persistence = 0.5)
    {
        double total = 0;
        double frequency = 1;
        double amplitude = 1;
        double maxValue = 0;

        for (int i = 0; i < octaves; i++)
        {
            total += Noise(x * frequency, y * frequency) * amplitude;
            maxValue += amplitude;
            amplitude *= persistence;
            frequency *= 2;
        }

        return total / maxValue;
    }

    public void GenerateNoiseImage(string outputPath, int width, int height, double scale = 10.0, int octaves = 4, double persistence = 0.5)
    {
        using (Bitmap bitmap = new Bitmap(width, height))
        {
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    // 缩放坐标采样噪声
                    double nx = x / scale;
                    double ny = y / scale;

                    // 获取噪声值
                    double value = OctaveNoise(nx, ny, octaves, persistence);

                    // 将噪声值从[-1,1]归一化到[0,1]
                    value = (value + 1) * 0.5;

                    // 转换为灰度颜色(0-255)
                    int grayscale = (int)(value * 255);
                    grayscale = Math.Max(0, Math.Min(255, grayscale)); // 限制值范围

                    Color pixelColor = Color.FromArgb(grayscale, grayscale, grayscale);
                    bitmap.SetPixel(x, y, pixelColor);
                }
            }

            // 保存位图
            bitmap.Save(outputPath, ImageFormat.Png);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 创建带种子的Perlin噪声生成器
        PerlinNoise perlin = new PerlinNoise(42);

        // 定义输出路径
        string outputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "perlin_noise.png");

        // 生成不同风格的噪声图像

        // 标准噪声
        perlin.GenerateNoiseImage(
            outputPath,
            width: 800,
            height: 600,
            scale: 50.0,
            octaves: 6,
            persistence: 0.5
        );

        // 细节丰富的噪声(更高频率)
        perlin.GenerateNoiseImage(
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "perlin_noise_detailed.png"),
            width: 800,
            height: 600,
            scale: 25.0,
            octaves: 8,
            persistence: 0.6
        );

        // 平滑噪声(更低频率)
        perlin.GenerateNoiseImage(
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "perlin_noise_smooth.png"),
            width: 800,
            height: 600,
            scale: 100.0,
            octaves: 4,
            persistence: 0.4
        );

        Console.WriteLine($"图像已生成在:{AppDomain.CurrentDomain.BaseDirectory}");
        Console.WriteLine("1. perlin_noise.png - 标准噪声");
        Console.WriteLine("2. perlin_noise_detailed.png - 细节丰富版");
        Console.WriteLine("3. perlin_noise_smooth.png - 平滑版");
    }
}

最终生成图像

  • 标准噪声:标准Perlin噪声
  • 细节丰富版:细节丰富的Perlin噪声
  • 平滑版:平滑的Perlin噪声

内容的提问来源于stack exchange,提问作者Ramennoodles

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 03:20:53