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 - Niveau de détail
III. Résumé▲
III-A. Amélioration du modèle▲
On remarque que le modèle généré dans la vidéo précédente est extrêmement plat. Pour corriger cela, nous allons ajouter une variable (heightMultiplier) agissant tel un multiplicateur appliqué sur la coordonnée Y des sommets :
using
UnityEngine;
using
System.
Collections;
public
static
class
MeshGenerator {
public
static
MeshData GenerateTerrainMesh
(
float
[,]
heightMap,
float
heightMultiplier) {
int
width =
heightMap.
GetLength (
0
);
int
height =
heightMap.
GetLength (
1
);
float
topLeftX =
(
width -
1
) /
-
2f
;
float
topLeftZ =
(
height -
1
) /
2f
;
MeshData meshData =
new
MeshData (
width,
height);
int
vertexIndex =
0
;
for
(
int
y =
0
;
y <
height;
y++
) {
for
(
int
x =
0
;
x <
width;
x++
) {
meshData.
vertices [
vertexIndex]
=
new
Vector3 (
topLeftX +
x,
heightMap [
x,
y]
*
heightMultiplier,
topLeftZ -
y);
meshData.
uvs [
vertexIndex]
=
new
Vector2 (
x /
(
float
)width,
y /
(
float
)height);
if
(
x <
width -
1
&&
y <
height -
1
) {
meshData.
AddTriangle (
vertexIndex,
vertexIndex +
width +
1
,
vertexIndex +
width);
meshData.
AddTriangle (
vertexIndex +
width +
1
,
vertexIndex,
vertexIndex +
1
);
}
vertexIndex++;
}
}
return
meshData;
}
}
public
class
MeshData {
public
Vector3[]
vertices;
public
int
[]
triangles;
public
Vector2[]
uvs;
int
triangleIndex;
public
MeshData
(
int
meshWidth,
int
meshHeight) {
vertices =
new
Vector3[
meshWidth *
meshHeight];
uvs =
new
Vector2[
meshWidth *
meshHeight];
triangles =
new
int
[(
meshWidth-
1
)*(
meshHeight-
1
)*
6
];
}
public
void
AddTriangle
(
int
a,
int
b,
int
c) {
triangles [
triangleIndex]
=
a;
triangles [
triangleIndex +
1
]
=
b;
triangles [
triangleIndex +
2
]
=
c;
triangleIndex +=
3
;
}
public
Mesh CreateMesh
(
) {
Mesh mesh =
new
Mesh (
);
mesh.
vertices =
vertices;
mesh.
triangles =
triangles;
mesh.
uv =
uvs;
mesh.
RecalculateNormals (
);
return
mesh;
}
}
De plus, vous pouvez rendre cette variable visible dans l'éditeur en modifiant le script MapGenerator.cs.
Cette méthode modifie aussi le relief pour les surfaces aquatiques. Afin de corriger ce souci, ajoutez une courbe (AnimationCurve) impactant le multiplicateur de hauteur :
using
UnityEngine;
using
System.
Collections;
public
class
MapGenerator :
MonoBehaviour {
public
enum
DrawMode {
NoiseMap,
ColourMap,
Mesh};
public
DrawMode drawMode;
public
float
noiseScale;
public
int
octaves;
[Range(
0
,
1
)]
public
float
persistance;
public
float
lacunarity;
public
int
seed;
public
Vector2 offset;
public
float
meshHeightMultiplier;
public
AnimationCurve meshHeightCurve;
public
bool
autoUpdate;
public
TerrainType[]
regions;
public
void
GenerateMap
(
) {
float
[,]
noiseMap =
Noise.
GenerateNoiseMap (
width,
height,
seed,
noiseScale,
octaves,
persistance,
lacunarity,
offset);
Color[]
colourMap =
new
Color[
width *
height];
for
(
int
y =
0
;
y <
height;
y++
) {
for
(
int
x =
0
;
x <
width;
x++
) {
float
currentHeight =
noiseMap [
x,
y];
for
(
int
i =
0
;
i <
regions.
Length;
i++
) {
if
(
currentHeight <=
regions [
i].
height) {
colourMap [
y *
width +
x]
=
regions [
i].
colour;
break
;
}
}
}
}
MapDisplay display =
FindObjectOfType<
MapDisplay>
(
);
if
(
drawMode ==
DrawMode.
NoiseMap) {
display.
DrawTexture (
TextureGenerator.
TextureFromHeightMap (
noiseMap));
}
else
if
(
drawMode ==
DrawMode.
ColourMap) {
display.
DrawTexture (
TextureGenerator.
TextureFromColourMap (
colourMap,
width,
height));
}
else
if
(
drawMode ==
DrawMode.
Mesh) {
display.
DrawMesh (
MeshGenerator.
GenerateTerrainMesh (
noiseMap,
meshHeightMultiplier,
meshHeightCurve),
TextureGenerator.
TextureFromColourMap (
colourMap,
width,
height));
}
}
void
OnValidate
(
) {
if
(
lacunarity <
1
) {
lacunarity =
1
;
}
if
(
octaves <
0
) {
octaves =
0
;
}
}
}
[System.Serializable]
public
struct
TerrainType {
public
string
name;
public
float
height;
public
Color colour;
}
using
UnityEngine;
using
System.
Collections;
public
static
class
MeshGenerator {
public
static
MeshData GenerateTerrainMesh
(
float
[,]
heightMap,
float
heightMultiplier,
AnimationCurve heightCurve) {
int
width =
heightMap.
GetLength (
0
);
int
height =
heightMap.
GetLength (
1
);
float
topLeftX =
(
width -
1
) /
-
2f
;
float
topLeftZ =
(
height -
1
) /
2f
;
MeshData meshData =
new
MeshData (
width,
height);
int
vertexIndex =
0
;
for
(
int
y =
0
;
y <
height;
y ++
) {
for
(
int
x =
0
;
x <
width;
x++
) {
meshData.
vertices [
vertexIndex]
=
new
Vector3 (
topLeftX +
x,
heightCurve.
Evaluate
(
heightMap [
x,
y]
) *
heightMultiplier,
topLeftZ -
y);
meshData.
uvs [
vertexIndex]
=
new
Vector2 (
x /
(
float
)width,
y /
(
float
)height);
if
(
x <
width -
1
&&
y <
height -
1
) {
meshData.
AddTriangle (
vertexIndex,
vertexIndex +
width +
1
,
vertexIndex +
width);
meshData.
AddTriangle (
vertexIndex +
width +
1
,
vertexIndex,
vertexIndex +
1
);
}
vertexIndex++;
}
}
return
meshData;
}
}
public
class
MeshData {
public
Vector3[]
vertices;
public
int
[]
triangles;
public
Vector2[]
uvs;
int
triangleIndex;
public
MeshData
(
int
meshWidth,
int
meshHeight) {
vertices =
new
Vector3[
meshWidth *
meshHeight];
uvs =
new
Vector2[
meshWidth *
meshHeight];
triangles =
new
int
[(
meshWidth-
1
)*(
meshHeight-
1
)*
6
];
}
public
void
AddTriangle
(
int
a,
int
b,
int
c) {
triangles [
triangleIndex]
=
a;
triangles [
triangleIndex +
1
]
=
b;
triangles [
triangleIndex +
2
]
=
c;
triangleIndex +=
3
;
}
public
Mesh CreateMesh
(
) {
Mesh mesh =
new
Mesh (
);
mesh.
vertices =
vertices;
mesh.
triangles =
triangles;
mesh.
uv =
uvs;
mesh.
RecalculateNormals (
);
return
mesh;
}
}
Cela vous permet de dessiner une courbe où l'axe des abscisses représente la hauteur du terrain et l'axe des ordonnées la hauteur que le sommet du modèle aura. Ainsi, il est possible d'aplanir les surfaces aqueuses.
Dans le code, vous pouvez utiliser la courbe grâce à la fonction Evaluate(), qui pour une coordonnée X, retourne la coordonnée Y correspondante.
III-B. Niveau de détail▲
Afin de supporter de plus grandes cartes dans le futur, un mécanisme de niveau de détail est utile afin d'alléger les calculs pour des segments moins visibles (lointains par rapport au joueur).
L'implémentation du niveau de détail modifie la génération des sommets. En effet, pour diminuer le détail de notre terrain, nous allons générer un sommet sur deux, ou un sommet sur quatre.
Toutefois, il n'est pas possible d'utiliser n'importe quelle valeur pour passer les sommets. En effet, si nous prenons un sommet sur trois, il sera impossible de créer un terrain correctement.
Le saut doit dont être un facteur de la largeur - 1.
Du coup, il est nécessaire de trouver une méthode pour calculer le nombre de sommets que nous allons avoir. Pour cela, on peut utiliser la formule suivante :
kitxmlcodelatexdvpv = \frac{w-1}/{i} +1finkitxmlcodelatexdvpOù, w est la largeur et i, le saut.
Ce qui donne l'implémentation suivante :
using
UnityEngine;
using
System.
Collections;
public
class
MapGenerator :
MonoBehaviour {
public
enum
DrawMode {
NoiseMap,
ColourMap,
Mesh};
public
DrawMode drawMode;
const
int
mapChunkSize =
241
;
[Range(
0
,
6
)]
public
int
levelOfDetail;
public
float
noiseScale;
public
int
octaves;
[Range(
0
,
1
)]
public
float
persistance;
public
float
lacunarity;
public
int
seed;
public
Vector2 offset;
public
float
meshHeightMultiplier;
public
AnimationCurve meshHeightCurve;
public
bool
autoUpdate;
public
TerrainType[]
regions;
public
void
GenerateMap
(
) {
float
[,]
noiseMap =
Noise.
GenerateNoiseMap (
mapChunkSize,
mapChunkSize,
seed,
noiseScale,
octaves,
persistance,
lacunarity,
offset);
Color[]
colourMap =
new
Color[
mapChunkSize *
mapChunkSize];
for
(
int
y =
0
;
y <
mapChunkSize;
y++
) {
for
(
int
x =
0
;
x <
mapChunkSize;
x++
) {
float
currentHeight =
noiseMap [
x,
y];
for
(
int
i =
0
;
i <
regions.
Length;
i++
) {
if
(
currentHeight <=
regions [
i].
height) {
colourMap [
y *
mapChunkSize +
x]
=
regions [
i].
colour;
break
;
}
}
}
}
MapDisplay display =
FindObjectOfType<
MapDisplay>
(
);
if
(
drawMode ==
DrawMode.
NoiseMap) {
display.
DrawTexture (
TextureGenerator.
TextureFromHeightMap (
noiseMap));
}
else
if
(
drawMode ==
DrawMode.
ColourMap) {
display.
DrawTexture (
TextureGenerator.
TextureFromColourMap (
colourMap,
mapChunkSize,
mapChunkSize));
}
else
if
(
drawMode ==
DrawMode.
Mesh) {
display.
DrawMesh (
MeshGenerator.
GenerateTerrainMesh (
noiseMap,
meshHeightMultiplier,
meshHeightCurve,
levelOfDetail),
TextureGenerator.
TextureFromColourMap (
colourMap,
mapChunkSize,
mapChunkSize));
}
}
void
OnValidate
(
) {
if
(
lacunarity <
1
) {
lacunarity =
1
;
}
if
(
octaves <
0
) {
octaves =
0
;
}
}
}
[System.Serializable]
public
struct
TerrainType {
public
string
name;
public
float
height;
public
Color colour;
}
using
UnityEngine;
using
System.
Collections;
public
static
class
MeshGenerator {
public
static
MeshData GenerateTerrainMesh
(
float
[,]
heightMap,
float
heightMultiplier,
AnimationCurve heightCurve,
int
levelOfDetail) {
int
width =
heightMap.
GetLength (
0
);
int
height =
heightMap.
GetLength (
1
);
float
topLeftX =
(
width -
1
) /
-
2f
;
float
topLeftZ =
(
height -
1
) /
2f
;
int
meshSimplificationIncrement =
(
levelOfDetail ==
0
)?
1
:
levelOfDetail *
2
;
int
verticesPerLine =
(
width -
1
) /
meshSimplificationIncrement +
1
;
MeshData meshData =
new
MeshData (
verticesPerLine,
verticesPerLine);
int
vertexIndex =
0
;
for
(
int
y =
0
;
y <
height;
y +=
meshSimplificationIncrement) {
for
(
int
x =
0
;
x <
width;
x +=
meshSimplificationIncrement) {
meshData.
vertices [
vertexIndex]
=
new
Vector3 (
topLeftX +
x,
heightCurve.
Evaluate
(
heightMap [
x,
y]
) *
heightMultiplier,
topLeftZ -
y);
meshData.
uvs [
vertexIndex]
=
new
Vector2 (
x /
(
float
)width,
y /
(
float
)height);
if
(
x <
width -
1
&&
y <
height -
1
) {
meshData.
AddTriangle (
vertexIndex,
vertexIndex +
verticesPerLine +
1
,
vertexIndex +
verticesPerLine);
meshData.
AddTriangle (
vertexIndex +
verticesPerLine +
1
,
vertexIndex,
vertexIndex +
1
);
}
vertexIndex++;
}
}
return
meshData;
}
}
public
class
MeshData {
public
Vector3[]
vertices;
public
int
[]
triangles;
public
Vector2[]
uvs;
int
triangleIndex;
public
MeshData
(
int
meshWidth,
int
meshHeight) {
vertices =
new
Vector3[
meshWidth *
meshHeight];
uvs =
new
Vector2[
meshWidth *
meshHeight];
triangles =
new
int
[(
meshWidth-
1
)*(
meshHeight-
1
)*
6
];
}
public
void
AddTriangle
(
int
a,
int
b,
int
c) {
triangles [
triangleIndex]
=
a;
triangles [
triangleIndex +
1
]
=
b;
triangles [
triangleIndex +
2
]
=
c;
triangleIndex +=
3
;
}
public
Mesh CreateMesh
(
) {
Mesh mesh =
new
Mesh (
);
mesh.
vertices =
vertices;
mesh.
triangles =
triangles;
mesh.
uv =
uvs;
mesh.
RecalculateNormals (
);
return
mesh;
}
}
IV. Commenter▲
Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.