I. Introduction▲
Cette série explique comment générer de manière procédurale des continents dans Unity.
Vous pouvez retrouver les autres épisodes de cette série dans le sommaire dédié.
II. Vidéo▲
Unity - Génération procédurale de terrain - Octaves
III. Résumé▲
Dans la vidéo précédente, nous avons mis en place la génération d'une carte des hauteurs (« heightmap ») basique (une seule passe) et une méthode pour l'afficher au sein de l'éditeur. Comme vu en introduction, afin d'obtenir un terrain plus réaliste, nous réalisons plusieurs passes pour affiner la génération et augmenter les détails de la carte.
III-A. Implémentation▲
Pour cela, nous mettons à jour le script Noise.cs :
using
UnityEngine;
using
System.
Collections;
public
static
class
Noise {
public
static
float
[,]
GenerateNoiseMap
(
int
mapWidth,
int
mapHeight,
int
seed,
float
scale,
int
octaves,
float
persistance,
float
lacunarity,
Vector2 offset) {
float
[,]
noiseMap =
new
float
[
mapWidth,
mapHeight];
System.
Random prng =
new
System.
Random (
seed);
Vector2[]
octaveOffsets =
new
Vector2[
octaves];
for
(
int
i =
0
;
i <
octaves;
i++
) {
float
offsetX =
prng.
Next (-
100000
,
100000
) +
offset.
x;
float
offsetY =
prng.
Next (-
100000
,
100000
) +
offset.
y;
octaveOffsets [
i]
=
new
Vector2 (
offsetX,
offsetY);
}
if
(
scale <=
0
) {
scale =
0
.
0001f
;
}
float
maxNoiseHeight =
float
.
MinValue;
float
minNoiseHeight =
float
.
MaxValue;
// Permet que le paramètre noise provoque un zoom au centre de la carte
float
halfWidth =
mapWidth /
2f
;
float
halfHeight =
mapHeight /
2f
;
for
(
int
y =
0
;
y <
mapHeight;
y++
) {
for
(
int
x =
0
;
x <
mapWidth;
x++
) {
float
amplitude =
1
;
float
frequency =
1
;
float
noiseHeight =
0
;
for
(
int
i =
0
;
i <
octaves;
i++
) {
float
sampleX =
(
x-
halfWidth) /
scale *
frequency +
octaveOffsets[
i].
x;
float
sampleY =
(
y-
halfHeight) /
scale *
frequency +
octaveOffsets[
i].
y;
float
perlinValue =
Mathf.
PerlinNoise (
sampleX,
sampleY) *
2
-
1
;
noiseHeight +=
perlinValue *
amplitude;
amplitude *=
persistance;
frequency *=
lacunarity;
}
// Garde une trace des valeurs minimale et maximale générées
if
(
noiseHeight >
maxNoiseHeight) {
maxNoiseHeight =
noiseHeight;
}
else
if
(
noiseHeight <
minNoiseHeight) {
minNoiseHeight =
noiseHeight;
}
noiseMap [
x,
y]
=
noiseHeight;
}
}
// Normalisation
for
(
int
y =
0
;
y <
mapHeight;
y++
) {
for
(
int
x =
0
;
x <
mapWidth;
x++
) {
noiseMap [
x,
y]
=
Mathf.
InverseLerp (
minNoiseHeight,
maxNoiseHeight,
noiseMap [
x,
y]
);
}
}
return
noiseMap;
}
}
Nous ajoutons trois paramètres :
- le nombre d'octaves (de passes) ;
- la persistance ;
- la lacunarité ;
Pour chaque passe, l'amplitude et la fréquence sont modifiées afin de prendre en compte la persistance et la lacunarité. Ainsi, chaque passe aura un impact de moins en moins important mais une irrégularité de plus en plus forte.
Aussi, afin de permettre des valeurs négatives provenant du bruit de Perlin, nous multiplions la valeur retournée par la fonction par 2 et lui soustrayons 1. Sachant que ces valeurs sont comprises entre 0 et 1, après transformation nous obtiendrons des valeurs entre -1 et 1.
Toutefois, nous ne souhaitons pas avoir de valeurs négatives dans notre carte des hauteurs. Donc, après génération, nous effectuons une passe de normalisation. Pour cela, il faut connaître la plus petite et la plus grande valeur générée. La normalisation s'effectue avec la fonction Mathf.InverseLerp().
Pour tester le nouveau code, il faut modifier le script MapGenerator.cs afin de rendre visibles dans l'éditeur les nouveaux paramètres et de les passer lors de l'appel à la fonction GenerateNoiseMap().
Pour ajouter un aspect unique à notre génération, nous ajoutons un paramètre seed. Ainsi, nous pouvons générer des valeurs aléatoires utilisées pour décaler l'emplacement où l'échantillonnage du bruit de Perlin est effectué.
La fonction Mathf.PerlinNoise() possède une limite pour laquelle si vous passez des nombres trop grands (> 100000), elle retournera toujours la même valeur.
Dans le chapitre précédent, nous avons vu que la taille de la carte devait être différente de 0. Vous pouvez empêcher l'utilisateur d'utiliser une telle valeur grâce à la méthode onValidate() dans le script MapGenerator.cs :
using
UnityEngine;
using
System.
Collections;
public
class
MapGenerator :
MonoBehaviour {
public
int
mapWidth;
public
int
mapHeight;
public
float
noiseScale;
public
int
octaves;
[Range(
0
,
1
)]
public
float
persistance;
public
float
lacunarity;
public
int
seed;
public
Vector2 offset;
public
bool
autoUpdate;
public
void
GenerateMap
(
) {
float
[,]
noiseMap =
Noise.
GenerateNoiseMap (
mapWidth,
mapHeight,
seed,
noiseScale,
octaves,
persistance,
lacunarity,
offset);
MapDisplay display =
FindObjectOfType<
MapDisplay>
(
);
display.
DrawNoiseMap (
noiseMap);
}
void
OnValidate
(
) {
if
(
mapWidth <
1
) {
mapWidth =
1
;
}
if
(
mapHeight <
1
) {
mapHeight =
1
;
}
if
(
lacunarity <
1
) {
lacunarity =
1
;
}
if
(
octaves <
0
) {
octaves =
0
;
}
}
}
IV. Commenter▲
Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.