La fonction a une apparence pseudo-aléatoire, et pourtant tous ses détails visuels sont de taille égale, ce qui nous permet notamment de pouvoir répéter la texture à l'infini.

Je vais aujourd'hui vous proposer dans ce tutoriel une implémentation optimisée pour le langage C# de cet algorithme (basée sur la version la plus récente datant de 2003), que vous pourrez réutiliser dans vos projets personnels.

Il existe de nombreux tutoriels sur le net au sujet de l'algorithme de Perlin Noise (notamment ici et ). Seulement, la plupart de ces algorithmes sont basés sur l'ancienne version du bruit de Perlin, et les tutoriels indiqués ci-dessus font passer le Fractionnal Brownian motion pour du Perlin Noise (alors que c'en est une évolution). Tous les tutoriels sur le bruit de Perlin n'utilisent donc pas la même méthodologie, et ne sont pas adaptables à celui-ci.

Fonctionnement du Perlin Noise

Comme énoncé plus haut, la fonction du bruit de Perlin est une fonction pseudo-aléatoire; cet effet est nécessaire pour obtenir une répétition au niveau de la texture générée par la fonction.

Ce coté pseudo-aléatoire est réalisé grâce à une table fixe de valeurs aléatoires, que l'on répète indéfiniment grâce à une opération de modulo.

La table des valeurs aléatoires est arbitrairement dimensionnée à 256 valeurs. Pourquoi utiliser 256? Tout simplement parce que c'est le nombre maximum de valeurs possibles pour un byte, et étant une puissance de 2, cela nous permet aussi de simplifier les opérations de modulo en effectuant une opération logique de type ET sur un entier plutôt que de faire appel à la fonction modulo du langage C#.

  • Mode texte
  • Copier
  1. namespace XnaConnection.Tools.Maths
  2. {
  3. #region Using Statements
  4.  
  5. using System;
  6. using Microsoft.Xna.Framework;
  7.  
  8. #endregion
  9.  
  10. /// <summary>
  11. /// This is a class able to generate pseudo coherent random noise based on Ken Perlin's algorithm
  12. /// <see cref="http://mrl.nyu.edu/~perlin/noise/"/>
  13. /// <seealso cref="http://www.siafoo.net/snippet/144"/>
  14. /// </summary>
  15. public sealed class PerlinNoise
  16. {
  17. #region Constants
  18.  
  19. /// <summary>
  20. /// Stores the size of the random value array
  21. /// </summary>
  22. private int RANDOM_SIZE = 256;
  23.  
  24. #endregion
  25.  
  26. #region Fields
  27.  
  28. /// <summary>
  29. /// Stores the random values used to generate the noise
  30. /// </summary>
  31. private readonly int[] values;
  32.  
  33. #endregion
  34.  
  35. /// <summary>
  36. /// Initializes a new instance of the PerlinNoise class.
  37. /// </summary>
  38. /// <param name="seed">The seed used to generate the random values</param>
  39. public PerlinNoise(int seed)
  40. {
  41. // Initialize the random values array
  42. this.values = new int[RANDOM_SIZE*2];
  43.  
  44. // Initialize the random generator
  45. Random generator = new Random(seed);
  46.  
  47. // Initialize an array for storing 256 random values
  48. byte[] source = new byte[RANDOM_SIZE];
  49.  
  50. // Generate 256 random byte values
  51. generator.NextBytes(source);
  52.  
  53. // Copy the source data twice to the generator array
  54. for (int i = 0; i < RANDOM_SIZE; i++)
  55. this.values[i+RANDOM_SIZE] = this.values[i] = source[i];
  56. }

L'initialisation de la classe se charge d'initialiser les nombres aléatoires qui serviront à générer le bruit de Perlin. Les valeurs aléatoires sont stockées dans un tableau de 512 valeurs (et non pas 256 comme on pourrait le penser), dont les 256 valeurs initiales sont répétées deux fois de suite.

Pourquoi cela? Tout simplement parce que, pour avoir des valeurs aléatoires différentes pour une combinaison de valeurs (X,Y et Z), nous allons moduler la valeur aléatoire initiale (fournie par la première coordonnée, ici X) par la seconde valeur en additionnant la valeur aléatoire au modulo de la seconde coordonnée, pour obtenir une nouvelle valeur, qui sera additionnée à la troisième valeur.

Ce système permet d'augmenter l'aspect aléatoire du bruit de Perlin (on obtient une valeur différente pour chaque coordonnée),  tout en gardant sa cohérence (ainsi, pour une coordonnée X, Y, Z donnée, on obtiendra toujours la même valeur pseudo-aléatoire).

Les fonctions d'aide pour l'algorithme de Perlin

Avant de s'attaquer au vif du sujet, et de programmer les fonctions de bruits, nous allons avoir besoin de plusieurs petites fonctions d'aide qui n'ont rien de compliqué d'un point de vue code, mais qui recèlent quelques subtilités mathématiques.

La fonction d'interpolation linéaire Lerp

La fonction Lerp() (Linear intERPolation) est une fonction qui donne une interpolation linéaire entre deux nombres (A et B), en fonction d'un d'une valeur X (souvent) comprise entre 0 et 1. Plus la valeur est proche de 0, plus le résultat sera proche de la valeur A, et plus la valeur est proche de 1, plus le résultat sera proche de la valeur B. La signature de la fonction est la suivante:

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Interpolate linearly between A and B.
  3. /// </summary>
  4. /// <param name="t">The amount of the interpolation</param>
  5. /// <param name="a">The starting value</param>
  6. /// <param name="b">The ending value</param>
  7. /// <returns>The interpolated value between A and B</returns>
  8. private static float Lerp(float t, float a, float b)
  9. {
  10. return a + t * (b - a);
  11. }

Cette fonction va nous permettre d'interpoler les valeurs aléatoires dans la grille de travail courante pour remplir les valeurs entre les bords de cette grille de travail.

Il faut en effet savoir que l'aspect général du bruit de Perlin est obtenu en accolant plusieurs courbes différentes dont l'intervalle maximum est compris entre [0;1]; chaque intervalle unitaire est donc nommé cellule, et chaque cellule aura son traitement particulier.

La fonction d'interpolation cubique Fade

La fonction Fade() est la fonction qui donne son aspect "rondouillard" à l'algorithme de Perlin. L'image sur la droite montre le résultat de l'algorithme sans la fonction Fade().

La courbe de la fonction Fade() ressemble énormément à une courbe de fonction sinusoïdale. Toutefois, les fonctions trigonométriques telles que Sin() et Cos() sont très lentes dans la plupart des langages, et le langage C# ne déroge pas à cette règle. C'est pourquoi Ken Perlin utilise une fonction polynomiale 6t5 - 15t4 + 10t3 dont la courbe se rapproche énormément d'une fonction sinusoïdale, comme en témoigne le tracé ci-dessous:

lkj

La signature de la fonction est donc:

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Smooth the entry value
  3. /// </summary>
  4. /// <param name="t">The entry value</param>
  5. /// <returns>The smoothed value</returns>
  6. private static float Fade(float t)
  7. {
  8. return t * t * t * (t * (t * 6 - 15) + 10);
  9. }

Vous constaterez que je n'utilise pas la méthode Math.Pow() pour faire les puissances, mais une factorisation qui se révèle être optimisée à la compilation, et permettant des gains de l'ordre de 40% par rapport à la forme énoncée précédemment.

La fonction Grad

La fonction Grad() est la fonction qui se charge d'éviter les cohérences dans l'algorithme de Perlin. C'est elle qui est à la base de la génération du bruit. En effet, sans elle, on obtiendrait un maillage régulier comme sur l'image ci-contre. Pas très utile, n'est ce pas?

La fonction Grad() est différente suivant le nombre de dimensions du bruit de Perlin. En effet, celle-ci va se baser sur une partie du chiffre aléatoire pour donner une "direction" au contenu interpolé de la cellule.

Pour chaque dimension, nous allons donc analyser les n derniers bits du nombre aléatoire de la table des valeurs aléatoires afin d'obtenir une combinaison positive et négative pour chaque dimension du bruit de Perlin. La fonction effectue ce qu'on appelle un biais directionnel. Un peu de code sera sans doute plus explicite:

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Modifies the result by adding a directional bias
  3. /// </summary>
  4. /// <param name="hash">The random value telling in which direction the bias will occur</param>
  5. /// <param name="x">The amount of the bias on the X axis</param>
  6. /// <returns>The directional bias strength</returns>
  7. private static float Grad(int hash, float x)
  8. {
  9. // Result table
  10. // ---+------+----
  11. // 0 | 0000 | x
  12. // 1 | 0001 | -x
  13.  
  14. return (hash & 1) == 0 ? x : -x;
  15. }

Dans ce premier cas, le plus trivial, le biais directionnel peut avoir deux valeur possibles: soit dans le sens de X (lorsque nous avons un chiffre pair), soit dans le sens opposé à X si le chiffre est impair. le code hash & 1 == 0 est équivalent au code hash % 2 == 0, mais bien plus rapide car le code effectue une comparaison bit à bit (algèbre de Boole).

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Modifies the result by adding a directional bias
  3. /// </summary>
  4. /// <param name="hash">The random value telling in which direction the bias will occur</param>
  5. /// <param name="x">The amount of the bias on the X axis</param>
  6. /// <param name="y">The amount of the bias on the Y axis</param>
  7. /// <returns>The directional bias strength</returns>
  8. private static float Grad(int hash, float x, float y)
  9. {
  10. // Fetch the last 3 bits
  11. int h = hash & 3;
  12.  
  13. // Result table for U
  14. // ---+------+---+------
  15. // 0 | 0000 | x | x
  16. // 1 | 0001 | x | x
  17. // 2 | 0010 | x | -x
  18. // 3 | 0011 | x | -x
  19.  
  20. float u = (h & 2) == 0 ? x : -x;
  21.  
  22. // Result table for V
  23. // ---+------+---+------
  24. // 0 | 0000 | y | y
  25. // 1 | 0001 | y | -y
  26. // 2 | 0010 | y | y
  27. // 3 | 0011 | y | -y
  28.  
  29. float v = (h & 1) == 0 ? y : -y;
  30.  
  31. // Result table for U + V
  32. // ---+------+----+----+--
  33. // 0 | 0000 | x | y | x + y
  34. // 1 | 0001 | x | -y | x - y
  35. // 2 | 0010 | -x | y | -x + y
  36. // 3 | 0011 | -x | -y | -x - y
  37.  
  38. return u + v;
  39. }

Dans le cas d'un bruit bi-dimensionnel, nous devons combiner les deux valeurs x et y pour obtenir les quatre biais directionnels différents résultant de la combinaison des deux axes. Pour des raisons de simplicité de compréhension, je vous ai mis les tables de vérité, ainsi que les résultats découlant en fonction de la valeur du hash.

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Modifies the result by adding a directional bias
  3. /// </summary>
  4. /// <param name="hash">The random value telling in which direction the bias will occur</param>
  5. /// <param name="x">The amount of the bias on the X axis</param>
  6. /// <param name="y">The amount of the bias on the Y axis</param>
  7. /// <param name="z">The amount of the bias on the Z axis</param>
  8. /// <returns>The directional bias strength</returns>
  9. private static float Grad(int hash, float x, float y, float z)
  10. {
  11. // Fetch the last 4 bits
  12. int h = hash & 15;
  13.  
  14. // Result table for U
  15. // ---+------+---+------
  16. // 0 | 0000 | x | x
  17. // 1 | 0001 | x | -x
  18. // 2 | 0010 | x | x
  19. // 3 | 0011 | x | -x
  20. // 4 | 0100 | x | x
  21. // 5 | 0101 | x | -x
  22. // 6 | 0110 | x | x
  23. // 7 | 0111 | x | -x
  24. // 8 | 1000 | y | y
  25. // 9 | 1001 | y | -y
  26. // 10 | 1010 | y | y
  27. // 11 | 1011 | y | -y
  28. // 12 | 1100 | y | y
  29. // 13 | 1101 | y | -y
  30. // 14 | 1110 | y | y
  31. // 15 | 1111 | y | -y
  32.  
  33. float u = h < 8 ? x : y;
  34.  
  35. // Result table for V
  36. // ---+------+---+------
  37. // 0 | 0000 | y | y
  38. // 1 | 0001 | y | y
  39. // 2 | 0010 | y | -y
  40. // 3 | 0011 | y | -y
  41. // 4 | 0100 | z | z
  42. // 5 | 0101 | z | z
  43. // 6 | 0110 | z | -z
  44. // 7 | 0111 | z | -z
  45. // 8 | 1000 | z | z
  46. // 9 | 1001 | z | z
  47. // 10 | 1010 | z | -z
  48. // 11 | 1011 | z | -z
  49. // 12 | 1100 | x | x
  50. // 13 | 1101 | z | z
  51. // 14 | 1110 | x | -x
  52. // 15 | 1111 | z | -z
  53.  
  54. float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
  55.  
  56. // Result table for U+V
  57. // ---+------+----+----+-------
  58. // 0 | 0000 | x | y | x + y
  59. // 1 | 0001 | -x | y | -x + y
  60. // 2 | 0010 | x | -y | x - y
  61. // 3 | 0011 | -x | -y | -x - y
  62. // 4 | 0100 | x | z | x + z
  63. // 5 | 0101 | -x | z | -x + z
  64. // 6 | 0110 | x | -z | x - z
  65. // 7 | 0111 | -x | -z | -x - z
  66. // 8 | 1000 | y | z | y + z
  67. // 9 | 1001 | -y | z | -y + z
  68. // 10 | 1010 | y | -z | y - z
  69. // 11 | 1011 | -y | -z | -y - z
  70.  
  71. // The four last results already exists in the table before
  72. // They are doubled because you must get a result for all
  73. // 4-bit combinaisons values.
  74.  
  75. // 12 | 1100 | y | x | y + x
  76. // 13 | 1101 | -y | z | -y + z
  77. // 14 | 1110 | y | -x | y - x
  78. // 15 | 1111 | -y | -z | -y - z
  79.  
  80. return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
  81. }

Au niveau de trois dimensions, nous sommes obligés de prendre 12 valeurs différentes pour la combinaison des trois dimensions. Problème: 12 n'est pas une puissance de 2, la valeur la plus proche étant 16. L'algorithme triche donc un peu et pour les 4 dernières valeurs (de 12 à 15), nous répétons des combinaisons déjà existantes.

Pour quatre dimensions le code se complexifie encore plus. Je laisserai au lecteur le soin d'implémenter la méthode, si vraiment l'envie lui prend.

La fonction de bruit Perlin à une dimension

Maintenant que nous avons expliqué toutes les fonctions d'aide à la génération du bruit Perlin, nous pouvons commencer notre première fonction de bruit de Perlin en implémentant un bruit à une seule dimension, même si  ce dernier n'est que très rarement utilisé.

Pour ainsi dire, à part piloter des effets de brillance (halo de lumière) autour d'une lumière (le bruit indique donc l'intensité du halo), je n'en vois pas. Toutefois, la méthode est particulièrement simple à expliquer et est une bonne introduction au bruit de Perlin à deux ou trois dimensions.

  • Mode texte
  • Copier
  1. public float Noise(float x)
  2. {
  3. // Compute the cell coordinates
  4. int X = (int)Math.Floor(x) & 255;
  5.  
  6. // Retrieve the decimal part of the cell
  7. x -= (float)Math.Floor(x);
  8.  
  9. float u = this.Fade(x);
  10.  
  11. return this.Lerp(u, this.Grad(this.values[X], x), this.Grad(this.values[X+1], x - 1));
  12. }

On va commencer la fonction par découvrir dans quelle "cellule" l'on se trouve. En effet, l'algorithme de Perlin travaille dans des cases définies par des coordonnées entières.

Ainsi pour chaque cellule unitaire, nous aurons les même valeurs initiales aléatoires. Par exemple si l'on appelle la fonction avec x = 1.2f ou x = 1.4f, vu que la valeur aléatoire est calculée en fonction de la partie entière de x, nous allons donc pour ces deux valeurs piocher dans la table des valeurs aléatoires à X et à X+1 (pour obtenir une  variation).

Pour cette explication, je suppose que values[X] vaut 175 et values[X+1] vaut 100, pour X = 1. On voit ensuite que l'on prend le Grad() de ces deux valeurs aléatoires, avec une fois en paramètre x, et une fois en paramètre x - 1 (l'opposé par rapport au 0 de x, vu que x représente la partie décimale de notre valeur d'entrée grâce à x -= (float)Math.Floor(x);).

La fonction Grad(this.values[X], x) s'évalue donc en Grad( 175, x ) ce qui nous donne -x tandis que Grad( values[ X + 1], x - 1) s'évalue en x - 1. On peut en déduire la formule générale, avec l'interpolation linéaire, en fonction de la parité des éléments de la table aléatoire:

Parité à XParité à X+1Résultat de l'interpolation linéaire Lerp
PairPairx + u * (x - 1 - x) = x - u
PairImpairx + u * (-x + 1 - x) = x + u * ( 1 - 2x )
ImpairPair-x + u * (x - 1 - (-x) ) = -x + u * (2x - 1)
ImpairImpair-x + u * (-x + 1 - (-x) ) = -x + u

Le membre u correspond au résultat de la fonction Fade() vu ci-dessus. On peut donc remplacer u par 6x5 - 15x4 + 10x3, ce qui nous donne au final:

Parité à XParité à X+1Résultat de l'interpolation linéaire Lerp
PairPairx + u * (x - 1 - x) = x - (6x5 - 15x4 + 10x3)
PairImpairx + u * (-x + 1 - x) = x + (6x5 - 15x4 + 10x3) * ( 1 - 2x )
ImpairPair-x + u * (x - 1 - (-x) ) = -x + (6x5 - 15x4 + 10x3) * (2x - 1)
ImpairImpair-x + u * (-x + 1 - (-x) ) = -x + (6x5 - 15x4 + 10x3)

Les graphiques associés, pour l'intervalle [0;1] nous donnent, pour ces quatres fonctions:

graphique-pair-pair.png graphique-pair-impair.png graphique-impair-pair.png graphique-impair-impair.png

Vous constaterez que toutes les fonctions ci-dessus ont une dérivée égale à soit  1, soit -1 aux bornes extrêmes de la validité de la fonction (0 et 1). C'est ce qui va nous permettre de ne pas remarquer de jointures entre deux différentes cellules de la fonction de bruit de Perlin (voir ci-dessous). Si l'on prend le résultat de ces fonctions, et qu'on les accole les unes au autres, en fonction de la valeur aléatoire, cela nous donne le bruit de Perlin définitif:

graphique-bruit-perlin-une-dimension.gif

On constate deux choses: primo, la théorie énoncée ci dessus est validée: il n'y a pas de jointures entre les différentes cellules (représentées en rouge, bleu, vert et jaune suivant la fonction appliquée). On constate aussi qu'il n'y a pas de "pic" saillant dans la fonction et que la courbe continue tout naturellement dans la même direction que la courbe de la cellule précédente.

Comment est ce possible? C'est tout simplement possible parce que la valeur aléatoire de fin de la cellule précédente sert de valeur de début de la cellule courante. Ainsi, une cellule qui se fini par un hashage pair commence forcément par un hashage pair, vu que la même valeur est utilisée.

Or, si l'on regarde les différentes courbes obtenues par la combinaison des hashages, on constate que toutes les courbes ayant un hashage impair au départ commencent avec une courbe descendante (la dérivée valant -1), comme toutes les courbes finissant par un hashage impair.

La règle est aussi vraie pour les hashages pairs; c'est cette règle qui nous permet d'éviter d'avoir des saillies, et par ainsi, de créer le bruit pseudo cohérent.

La fonction de bruit Perlin à deux dimensions

La fonction de bruit de Perlin à deux dimensions va suivre les même principes que la fonction à une dimension, en effectuant une interpolation linéaire entre les résultats données par la fonction de hashage Grad().

Le coté un petit peu plus compliqué provient du fait qu'il faut, comme pour la fonction à une dimension, assurer la continuité entre les valeurs aléatoires pour chaque point de la cellule.

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Generates a bi-dimensional noise
  3. /// </summary>
  4. /// <param name="x">The X entry value for the noise</param>
  5. /// <param name="y">The Y entry value for the noise</param>
  6. /// <returns>A value between [-1;1] representing the noise amount</returns>
  7. public float Noise(float x, float y)
  8. {
  9. // Compute the cell coordinates
  10. int X = (int)Math.Floor(x) & 255;
  11. int Y = (int)Math.Floor(y) & 255;
  12.  
  13. // Retrieve the decimal part of the cell
  14. x -= (float)Math.Floor(x);
  15. y -= (float)Math.Floor(y);
  16.  
  17. // Smooth the curve
  18. float u = Fade(x);
  19. float v = Fade(y);
  20.  
  21. // Fetch some randoms values from the table
  22. int A = this.values[X] + Y;
  23. int B = this.values[X + 1] + Y;
  24.  
  25. // Interpolate between directions
  26. return Lerp(v, Lerp(u, Grad(this.values[A], x, y),
  27. Grad(this.values[B], x - 1, y)),
  28. Lerp(u, Grad(this.values[A + 1], x, y - 1),
  29. Grad(this.values[B + 1], x - 1, y - 1)));
  30. }

Nous avons donc ici pour cellule une case, représentée par 4 points, comme dans le schéma de principe à droite.

Comme vous l'avez constaté dans le code, nous avons calculé deux valeurs intermédiaires A et B, basées sur les coordonnées de cellule, obtenues de la même manière que précédemment, via l'utilisation de la méthode Math.Floor().

Nous reprenons le même algorithme que précédemment, c'est à dire que nous interpolons linéairement entre A et B grâce à la valeur u. Nous reprenons de même pour A+1 et B+1 qui constituent l'autre coté de la cellule, ce qui nous donne deux nouvelles valeurs.

Nous effectuons maintenant une nouvelle interpolation sur l'axe des Y entre les deux nouvelles valeurs pour obtenir au final le niveau de la fonction de bruit au point donné par v à l'intérieur de la cellule.

Il ne faut pas oublier que la fonction Grad() a été modifiée pour la version 2D, et que celle ci nous donne jusqu'à 16 types de cellules différentes; les dessiner ici prendrait trop de temps et il est facile, pour les plus curieux, de reprendre la démonstration utilisée pour le bruit à une dimension, et de l'appliquer à deux dimensions pour voir les résultats des différentes fonctions suivant le hashage.

Le bruit de Perlin à trois dimensions (3D)

Reprenant les principes énoncés ci-dessus, on peut en déduire naturellement la fonction de Perlin pour trois dimensions. Je pourrai refaire toute la démonstration pour la 3D, mais je m'expose à des pages et des pages de calculs juste pour montrer l'évidence même.

Je laisse donc ici juste le code, qui est suffisamment explicite (et au pire, si vous n'avez rien compris, rien ne vous empêche de l'utiliser tel quel).

  • Mode texte
  • Copier
  1. /// <summary>
  2. /// Generates a tri-dimensional noise
  3. /// </summary>
  4. /// <param name="x">The X entry value for the noise</param>
  5. /// <param name="y">The Y entry value for the noise</param>
  6. /// <param name="z">The Z entry value for the noise</param>
  7. /// <returns>A value between [-1;1] representing the noise amount</returns>
  8. public float Noise(float x, float y, float z)
  9. {
  10. // Compute the cell coordinates
  11. int X = (int)Math.Floor(x) & 255;
  12. int Y = (int)Math.Floor(y) & 255;
  13. int Z = (int)Math.Floor(z) & 255;
  14.  
  15. // Retrieve the decimal part of the cell
  16. x -= (float)Math.Floor(x);
  17. y -= (float)Math.Floor(y);
  18. z -= (float)Math.Floor(z);
  19.  
  20. // Smooth the curve
  21. float u = Fade(x);
  22. float v = Fade(y);
  23. float w = Fade(z);
  24.  
  25. // Fetch some randoms values from the table
  26. int A = this.values[X] + Y;
  27. int AA = this.values[A] + Z;
  28. int AB = this.values[A + 1] + Z;
  29. int B = this.values[X + 1] + Y;
  30. int BA = this.values[B] + Z;
  31. int BB = this.values[B + 1] + Z;
  32.  
  33. // Interpolate between directions
  34. return Lerp(w, Lerp(v, Lerp(u, Grad(this.values[AA], x, y, z),
  35. Grad(this.values[BA], x - 1, y, z)),
  36. Lerp(u, Grad(this.values[AB], x, y - 1, z),
  37. Grad(this.values[BB], x - 1, y - 1, z))),
  38. Lerp(v, Lerp(u, Grad(this.values[AA + 1], x, y, z - 1),
  39. Grad(this.values[BA + 1], x - 1, y, z - 1)),
  40. Lerp(u, Grad(this.values[AB + 1], x, y - 1, z - 1),
  41. Grad(this.values[BB + 1], x - 1, y - 1, z - 1))));
  42. }

Encore plus de dimensions?

Comme on l'a vu, rien ne nous empêche d'augmenter indéfiniment le nombre de dimensions du bruit de Perlin. Par exemple, le bruit 4D est souvent utilisé pour faire des nuages qui évoluent (la quatrième dimension représentant alors le temps).

Mathématiquement, rien n'interdit d'utiliser encore plus le nombre de dimensions; toutefois je ne vois actuellement aucune utilité à un bruit généré par 5, ou même plus dimensions. Nous nous cantonnerons donc, pour ce tutoriel, au  trois dimensions spatiales.

Ceci conclut donc ce tutoriel sur le bruit de Perlin amélioré. Nous verrons bientôt comment s'en servir pour augmenter la qualité graphique de nos jeux, mais aussi pour générer des terrains aléatoirement.

Merci à GuiHome pour la correction de quelques coquilles qui s'étaient glissées ici et là.