我的 Perlin 噪声发生器出了什么问题?

问题描述 投票:0回答:1

我一直在尝试根据这篇论文编写自己的 Perlin 噪声算法实现。然而,它最终产生了一个奇怪的模式,这与我正在寻找的结果相去甚远。

这是我的代码:

using System;
using System.Drawing;

class PerlinNoise2D
{
    // Fade function for smoothing transitions
    private static double Fade(double t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

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

    // Dot product of gradient and displacement vectors
    private static double DotGridGradient(int gridX, int gridY, double x, double y, Random rand)
    {
        // Generate a pseudo-random gradient vector at the grid point
        double angle = rand.NextDouble() * Math.PI * 2;
        double gradX = Math.Cos(angle);
        double gradY = Math.Sin(angle);

        // Displacement vector from grid point to input point
        double dx = x - gridX;
        double dy = y - gridY;

        // Return dot product
        return (dx * gradX + dy * gradY);
    }

    // Noise function for a single layer
    public static double Noise(double x, double y, int gridSize, Random rand)
    {
        // Identify the grid cell the point is in
        int x0 = (int)Math.Floor(x) % gridSize;
        int y0 = (int)Math.Floor(y) % gridSize;
        int x1 = (x0 + 1) % gridSize;
        int y1 = (y0 + 1) % gridSize;

        // Local coordinates within the grid cell
        double localX = x - Math.Floor(x);
        double localY = y - Math.Floor(y);

        // Apply fade function to smooth transitions
        double xFade = Fade(localX);
        double yFade = Fade(localY);

        // Compute dot products with gradients at each corner
        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);

        // Interpolate along x for the two rows
        double nx0 = Lerp(xFade, n00, n10);
        double nx1 = Lerp(xFade, n01, n11);

        // Interpolate along y for the final noise value
        return Lerp(yFade, nx0, nx1);
    }

    // Perlin noise with multiple octaves for fractal-like detail
    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 seed for consistent results (same noise pattern for the same inputs)
        Random rand = new(69);

        // Iterates through octaves (layers of noise)
        for (int i = 0; i < octaves; i++)
        {
            // Generates noise for current octave (scaled by frequency and amplitude)
            total += Noise(x * frequency, y * frequency, gridSize, rand) * amplitude;

            // Amplitude added to max possible value
            maxValue += amplitude;

            // Persistence is a factor between 0 and 1, which dictates how much each successive octave contributes
            amplitude *= persistence;

            // Frequency is doubled as higher octaves have smaller frequencies
            frequency *= 2;
        }

        // Returns noise normalized to a [0, 1] range
        return total / maxValue;
    }

    static void Main()
    {
        // Configuration for the Perlin noise
        int width = 512;           // Width of the output image
        int height = 512;          // Height of the output image
        int gridSize = 16;         // Size of the grid for noise
        int octaves = 4;           // Number of noise octaves
        double persistence = 0.5;  // Persistence factor

        // Create a bitmap to store the Perlin noise
        Bitmap bitmap = new Bitmap(width, height);

        // Generate Perlin noise for each pixel
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                // Normalize coordinates for Perlin noise
                double noiseX = (double)x / width * gridSize;
                double noiseY = (double)y / height * gridSize;

                // Compute Perlin noise value using the provided implementation
                double noiseValue = Perlin(noiseX, noiseY, gridSize, octaves, persistence);

                // Map noise value to grayscale (0-255)
                int gray = (int)(noiseValue * 255);
                gray = Math.Max(0, Math.Min(255, gray)); // Ensure it's within valid range

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

                // Set the pixel color in the bitmap
                bitmap.SetPixel(x, y, color);
            }
        }

        // Save the image to a file
        bitmap.Save("PerlinNoise.png");
        Console.WriteLine("Perlin noise image saved as 'PerlinNoise.png'.");
    }
}

创建的图像如下所示:

Resultant image

我尝试摆弄噪声函数和点积函数,但我不确定到底出了什么问题。我怀疑我在其背后的逻辑上犯了一个愚蠢的错误,所以如果你们中有人能为我指出这一点,那么我将不胜感激。

c# perlin-noise
1个回答
0
投票

您的

DotGridGradient
应始终为相同的网格坐标生成相同的随机数。据我所知,事实并非如此,因为它是使用相同的种子为每个像素重新创建的,这可能解释了您所看到的重复模式。

最简单的解决方案可能是预先生成梯度网格。另一种可能的解决方案是使用网格坐标作为生成器的种子。我从未编写过 perlin 生成器,所以我不确定首选什么解决方案。

其他一些建议

  • 调试时,从最简单的情况开始,例如 2x2 网格和单个八度音阶。这往往会让您更容易地跟随程序并弄清楚发生了什么。
  • 这样的代码实际上需要 Vector2 类型,因此您不必在每个步骤中对 x 和 y 重复相同的计算。有很多可用的向量库,请参阅 math.netSystem.numerics 作为开始。或者自己写。
© www.soinside.com 2019 - 2024. All rights reserved.