Du bist nicht angemeldet.

Werbeanzeige

1

19.03.2016, 19:51

Implementation eines Perlin-Noise-Effekts

Ich wollte einen Perlin-Noise-Algorithmus schreiben der einfach eine Bitmap mit so einem Effekt füllt.
Ich erstelle dazu vier Maps (128x128, 64x64, 32x32 und 16x16) und überlager die so, das eigentlich ein guter Effekt entstehen soll.

Leider tut er das nicht :(

Das ist der Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    public class PerlinNoise
    {
        public static Random Random;

        public double[,] Map128 { get; set; }
        public double[,] Map64 { get; set; }
        public double[,] Map32 { get; set; }
        public double[,] Map16 { get; set; }

        public void InitializeMaps()
        {
            Random = new Random();

            // create Map128
            Map128 = new double[128, 128];
            for (int x = 0; x < 128; x++)
            {
                for (int y = 0; y < 128; y++)
                {
                    Map128[x, y] = Random.NextDouble();
                }
            }

            // create Map64
            Map64 = new double[64, 64];
            for (int x = 0; x < 64; x++)
            {
                for (int y = 0; y < 64; y++)
                {
                    Map64[x, y] = Random.NextDouble();
                }
            }

            // create Map32
            Map32 = new double[32, 32];
            for (int x = 0; x < 32; x++)
            {
                for (int y = 0; y < 32; y++)
                {
                    Map32[x, y] = Random.NextDouble();
                }
            }

            // create Map16
            Map16 = new double[16, 16];
            for (int x = 0; x < 16; x++)
            {
                for (int y = 0; y < 16; y++)
                {
                    Map16[x, y] = Random.NextDouble();
                }
            }
        }

        private byte DoubleToByte(double rate)
        {
            return (byte)(rate * 255);
        }

        public void GenerateNoise(Bitmap bmp)
        {
            for (int x = 0; x < 128; x++)
            {
                for (int y = 0; y < 128; y++)
                {
                    int gray128 = DoubleToByte(Map128[x, y]);

                    int x64 = (int)Math.Round((double)(x / 8));
                    int y64 = (int)Math.Round((double)(y / 8));
                    int gray64 = DoubleToByte(Map64[x64, y64]);

                    int x32 = (int)Math.Round((double)(x / 16));
                    int y32 = (int)Math.Round((double)(y / 16));
                    int gray32 = DoubleToByte(Map32[x32, y32]);

                    int x16 = (int)Math.Round((double)(x / 32));
                    int y16 = (int)Math.Round((double)(y / 32));
                    int gray16 = DoubleToByte(Map16[x16, y16]);

                    int gray1 = (gray32 + gray16) / 2;
                    int gray2 = (gray64 + gray1) / 2;
                    int gray = (gray128 + gray2) / 2;

                    bmp.SetPixel(x, y, Color.FromArgb((byte)gray,
                                                      (byte)gray,
                                                      (byte)gray));
                }
            }
        }
    }


Das Ergebnis sieht so aus:

(Link)


Könnte mir jemand, der sich damit mehr auskennt, mir erklären was ich falsch gemacht habe, und ggf. den Code verbessern?

mfg :)

Beiträge: 1 239

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

2

19.03.2016, 20:06

Also zwei Dinge:
  1. Was du machst ist zwar auch Noise, aber nicht Perlin Noise. Perlin Noise ist ein ganz bestimmtes Verfahren.
  2. Das Problem ist dass du die einzelnen Noise Ebenen nicht interpolierst. Was du machst ist praktisch ein Nearest Neighbor Filter, wie er in Dx bzw. OpenGL heißt, und da gibt es diese störenden Kanten.
Was du brauchst ist ein besserer Filter. Der kann bilinear sein oder am Besten noch etwas Weicheres wie zum Beispiel Smoothstep oder ein Sinus. Du holst die also für einen Pixel im Endergebnis die 4 Anfangspixel um diesen herum aus einer Ebene und mischt sie dann gewichtet von X und Y Abstand zusammen.

3

19.03.2016, 20:09

Erstmal danke für deine Hilfe :thumbup:

Könntest du vielleicht noch einmal etwas genauer erklären wo und wie man einen Sinusfilter oder einen Smoothstep-Filter implementieren kann?

4

19.03.2016, 20:12

Also ich habe ja dann die 4 Pixel die interpoliert werden soll. Aber beim Smoothstep-Beispiel steht was von edge0 und edge1. Was ist das? Wo finde ich diese Dinge?

Beiträge: 1 239

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

5

19.03.2016, 20:21

Also erstmal musst du deine Koordinaten in das Koordinatensystem der "Rauschebene" umrechnen, die du gerade dazumischt.
Das machst du wie bisher, in dem du für Map64 die Koordinaten durch 8 teilst. Anstatt die Koordinaten dann zum nächst gelegenen Wert runden, nimmst du einfach "Math.Floor" und "Math.Ceil" um sowohl die Koordinaten für den oberen, linken bzw. unteren rechten Pixel zu bekommen. Damit hast du auch die vier umliegenden Eckpunkte:

C-/C++-Quelltext

1
2
3
4
double pixel00 = Map64[x64_floor, y64_floor];
double pixel01 = Map64[x64_floor, y64_ceil];
double pixel10 = Map64[x64_ceil, y64_floor];
double pixel11 = Map64[x64_ceil, y64_ceil];

Jetzt musst du die vier umliegenden Pixel nur noch nach ihrer Entfernung gewichtet zusammenrechnen.
Zum Beispiel so:

C-/C++-Quelltext

1
2
3
4
5
6
7
double weightX = x64 - x64_floor; //Wertebereich: [0, 1[
double weightY = y64 - y64_floor; //Wertebereich: [0, 1[
double pixelMixed = 
    ((1.0 - weightX) * (1.0 - weightY)) * pixel00 +
    ((1.0 - weightX) *        weightY ) * pixel01 +
    (       weightX  * (1.0 - weightY)) * pixel10 +
    (       weightX  *        weightY ) * pixel11;

Fertig ist der bilineare Filter. Das machst du einfach mit allen "Rausch-Ebenen".
Wenn du Smoothstep verwenden willst, kannst du den einfach auf die X und Y Gewichtung anwenden.

EDIT:
edge0 und edge1 ist der Start und der Endwert zwischen dem interpoliert wird. In deinem Fall befinden sich die Gewichtungen zwischen 0 und 1. Eigentlich kannst du dir die ganze erste Zeile auch ganz sparen weil x sich schon zwischen 0 und 1 befindet. "x * x * (3.0 - 2.0 * x)" reicht für dich völlig.

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »Spiele Programmierer« (19.03.2016, 20:38)


Nimelrian

Alter Hase

Beiträge: 1 262

Beruf: Softwareentwickler (aktuell Web/Node); Freiberuflicher Google Proxy

  • Private Nachricht senden

6

19.03.2016, 20:31

Aber beim Smoothstep-Beispiel steht was von edge0 und edge1. Was ist das? Wo finde ich diese Dinge?

Das steht doch im Artikel. Mit den edge Parametern kannst du die Grenzen der Interpolation festlegen. Alles über/unter den Edges wird zu 1/0
Ich bin kein UserSideGoogleProxy. Und nein, dieses Forum ist kein UserSideGoogleProxyAbstractFactorySingleton.

7

19.03.2016, 20:33

Kurze Frage:

Dieser Code:
double pixelMixed =
((1.0 - weightX) * (1.0 - weightY)) * pixel00
((1.0 - weightX) * (weightY) * pixel01
(weightX * (1.0 - weightY)) * pixel10
(weightX * weightY) * pixel11;

versucht folgende Fehler:
"pixel00 ist eine Variable wird aber benutzt wie eine Methode".

Fehlt am Ende jeder Zeile ein Malzeichen? So?:
double pixelMixed =
((1.0 - weightX) * (1.0 - weightY)) * pixel00 *
((1.0 - weightX) * (weightY) * pixel01 *
(weightX * (1.0 - weightY)) * pixel10 *
(weightX * weightY) * pixel11;

Beiträge: 1 239

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

8

19.03.2016, 20:37

Ich hatte mich vertippt, da müssen natürlich Pluse dazwischen.
Der Sinn ist es ja, die Werte nach dem dem Gewichten (Multiplikation) zusammenzumischen (Addition).

9

19.03.2016, 20:44

Ok ich habe das jetzt mit den Gewichtungen umgesetzt.
Der Code:

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void GenerateNoise(BmpProcessor bmp)
        {
            for (int x = 0; x < 128; x++)
            {
                for (int y = 0; y < 128; y++)
                {
                    int gray128 = DoubleToByte(Map128[x, y]);

                    int x64 = (int)Math.Round((double)(x / 8));
                    int y64 = (int)Math.Round((double)(y / 8));
                    int gray64 = DoubleToByte(Map64[x64, y64]);

                    double pixel00 = Map64[(int)Math.Floor((double)x64), (int)Math.Floor((double)y64)];
                    double pixel01 = Map64[(int)Math.Floor((double)x64), (int)Math.Ceiling((double)y64)];
                    double pixel10 = Map64[(int)Math.Ceiling((double)x64), (int)Math.Floor((double)y64)];
                    double pixel11 = Map64[(int)Math.Ceiling((double)x64), (int)Math.Ceiling((double)y64)];

                    double weightX = x64 - Math.Floor((double)x64);
                    double weightY = y64 - Math.Floor((double)y64);

                    double pixelMixed =
                        ((1.0 - weightX) * (1.0 - weightY)) * pixel00 +
                        ((1.0 - weightX) * weightY) * pixel01 +
                        (weightX * (1.0 - weightY)) * pixel10 +
                        (weightX * weightY) * pixel11;


                    int gray = DoubleToByte(pixelMixed);

                    bmp.SetPixel(x, y, Color.FromArgb((byte)gray,
                                                      (byte)gray,
                                                      (byte)gray));
                }
            }
        }


Ich habe das jetzt erstmal mit zwei Ebenen (also nur Map128 und Map64) gemacht, damit das übersichtlicher ist.

Als Ergebnis bekomme ich so etwas:

(Link)


Ist das richtig? Ich finde das sieht irgendwie trotzdem nicht richtig aus

Beiträge: 1 239

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

10

19.03.2016, 20:49

Nein das ist nicht richtig. Das Problem ist, dass du die Koordinaten schon ganz zu Anfang rundest und in Integer umwandelst. x64 bzw. y64 dürfen nicht gerundet werden und sollten Gleitkommazahlen sein...

Werbeanzeige