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 - Modèle 3D du terrain
III. Résumé▲
Dans cet épisode, vous allez créer un modèle 3D à partir de la carte des hauteurs afin de créer le relief de votre terrain.
III-A. Implémentation▲
III-A-1. Explications de la méthode▲
Le modèle est créé à partir de triangles. Pour constituer les triangles, vous devez passer à Unity une liste (tableau à une dimension) des sommets que vous souhaitez afficher.
Si vous avez un tableau de neuf cases comme suit :
0 1 2
3 4 5
6 7 8
Le premier triangle sera :
0, 4, 3
L'ordre des sommets est important. En effet, l'ordre par défaut est un ordre allant dans le sens des aiguilles d'une montre. En réalité, cette convention est utilisée par la carte graphique afin de connaître quelle est la face avant et la face arrière. Aussi, si vous utilisez la suppression de la face arrière (« backface culling »), l'ordre est d'autant plus important à suivre, sans quoi vous ne verrez pas votre triangle.
Le deuxième triangle sera :
4, 0, 1
Et ainsi de suite.
Aussi, vous devez être en mesure de calculer :
- le nombre de sommets : largeur * hauteur ;
- le nombre de triangles : (largeur -1) * (hauteur -1) * 6.
III-A-2. Script▲
La création du modèle de terrain se fait dans un nouveau script, appelé MeshGenerator.cs :
using
UnityEngine;
using
System.
Collections;
public
static
class
MeshGenerator {
public
static
MeshData GenerateTerrainMesh
(
float
[,]
heightMap) {
int
width =
heightMap.
GetLength (
0
);
int
height =
heightMap.
GetLength (
1
);
// Permet de centrer le modèle
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],
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;
}
}
Évidemment, la fonction de génération GenerateTerrainMesh() prend en paramètre la carte des hauteurs. Elle produit les sommets du terrain ainsi que les triangles pour l'affichage.
La fonction génère aussi les coordonnées de texture pour le terrain, ici appelés uvs. Leurs valeurs sont comprises entre 0 et 1.
Pour des soucis de simplicité, une classe MeshData est créée afin de regrouper les sommets et les triangles :
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;
}
}
Celle-ci permet de facilement créer les tableaux qui seront passés à Unity, ajouter des triangles et de créer le modèle à partir de ceux-ci.
C'est un MeshData qui est retourné par la fonction GenerateTerrainMesh() afin de permettre l'implémentation du multithread par la suite et ainsi permettre de ne pas bloquer le jeu.
Aussi, il n'est pas possible de créer un modèle (Mesh) au sein d'un thread et de l'utiliser dans un autre.
III-A-3. Affichage▲
Pour permettre l'affichage du modèle, nous complétons l'énumération DrawMode et ajoutons une fonction DrawMesh() :
public
void
DrawMesh
(
MeshData meshData,
Texture2D texture) {
meshFilter.
sharedMesh =
meshData.
CreateMesh (
);
meshRenderer.
sharedMaterial.
mainTexture =
texture;
}
Son appel, réalisé dans le fichier MapGenerator.cs est réalisé comme suit :
display.
DrawMesh (
MeshGenerator.
GenerateTerrainMesh (
noiseMap),
TextureGenerator.
TextureFromColourMap (
colourMap,
mapWidth,
mapHeight));
Actuellement, il n'est pas possible d'afficher un modèle trop grand. Cela sera corrigé dans un prochain épisode, dans lequel la carte sera générée par morceau.
IV. Commenter▲
Vous pouvez commenter et donner vos avis dans la discussion associée sur le forum.