2/5
2. Modélisation

Objectif de scène 3D obtenue en fin de séance
Surface du terrain
Dans cette partie, nous allons modéliser la surface d’un terrain sous la forme d’une fonction de hauteur \(z=f(x,y)\).
Maillage d’une surface paramétrique
A la suite du code de code d’introduction de la scène par default, considérez la fonction suivante calculant l’échantillonnage d’une surface de type \(z=f(x,y)\) et construisant le maillage associé
mesh create_terrain()
{
// Number of samples of the terrain is N x N
const unsigned int N = 100;
mesh terrain; // temporary terrain storage (CPU only)
terrain.position.resize(N*N);
// Fill terrain geometry
for(unsigned int ku=0; ku<N; ++ku)
{
for(unsigned int kv=0; kv<N; ++kv)
{
// Compute local parametric coordinates (u,v) \in [0,1]
const float u = ku/(N-1.0f);
const float v = kv/(N-1.0f);
// Compute the local surface function
const float x = 5*(u-0.5f);
const float y = 5*(v-0.5f);
const float z = std::exp(-(x*x+y*y));
// Store vertex coordinates
terrain.position[kv+N*ku] = {x,y,z};
}
}
// Generate triangle organization
// Parametric surface with uniform grid sampling: generate 2 triangles for each grid cell
for(size_t ku=0; ku<N-1; ++ku)
{
for(size_t kv=0; kv<N-1; ++kv)
{
const unsigned int idx = kv + N*ku; // current vertex offset
const uint3 triangle_1 = {idx, idx+1+N, idx+1};
const uint3 triangle_2 = {idx, idx+N, idx+1+N};
terrain.connectivity.push_back(triangle_1);
terrain.connectivity.push_back(triangle_2);
}
}
return terrain;
}
Les sommets du maillages sont définis par un échantillonnage régulier dans le domaine \((u,v)\in[0,1]^2\).
-
Utilisez cette fonction pour générer et afficher la surface associée (vous devez créer une nouvelle variable de type mesh_drawable).
-
Observez la génération de la géométrie ainsi que de la connectivité dans la fonction.
-
Notez que pour des surfaces paramétriques \((u,v)\) il est souvent plus aisé de parcourir les sommets suivants deux boucles: l’une suivant \(u\), l’autre suivant \(v\).
-
Notez également que les boucles sont parcourues sur des coordonnées entières (et non pas des float).
-
-
Affichez les arêtes associées à ce maillage.
-
Modifiez la fonction de hauteur et observez le résultat.
Fonction de terrain
Considérons dans la suite le code défini dans les fichiers scenes/3D_graphics/01_modeling/modeling.cpp/hpp
-
Pour compiler le code correspondant à cette scène, il faut modifier la valeur du mot clé (#define) dans le fichier scenes/current_scene.hpp.
-
Dans le cas de cette scène: modifiez la ligne
#define SCENE_DEFAULT_3D_GRAPHICSen#define SCENE_3D_GRAPHICS. (Notez que les différents mots clés possibles sont déjà commentés).
Compilez et executez cette nouvelle scène. Un terrain/bosse devrait apparaitre. Cette fois, la fonction de création du terrain y est davantage détaillée.
-
La fonction evaluate_terrain permet le calcul d’un point du terrain pour une valeur \((u,v)\in[0,1]^2\)
-
La fonction evaluate_terrain_z permet le calcul spécifique de la hauteur \(z=f(u,v)\) du terrain à \(u,v\) donné.
-
La fonction create_terrain génère le maillage échantionnant cette fonction.
Pour l’instant, la fonction de hauteur est définie par
\(z=h_0 \, \mbox{exp}\left(-\left(\frac{\left\|p-p_0\right\|}{\sigma_0}\right)^2\right)\), avec \(p=(u,v)\), et \(p_0=(u_0,v_0)\).
Modifiez la fonction de hauteur de manière à générer une surface de type
\(\displaystyle z= \sum_i h_i \, \mbox{exp}\left(-\left(\frac{\|p-p_i\|}{\sigma_i}\right)^2\right)\).
Adaptez les valeurs \(h_i\), \(p_i\), et \(\sigma_i\) afin de générer l’apparence que vous souhaitez.
Exemple possible de fonction de hauteur avec
-
\(p_i=\{(0,0),(0.5,0.5),(0.2,0.7),(0.8,0.7)\}\)
-
\(h_i=\{3,-1.5,1,2\}\)
-
\(\sigma_i=\{0.5,0.15,0.2,0.2\}\)
2. Modélisation d’arbres sur le terrain
Nous souhaitons modéliser une série d’arbres/sapins présents sur le terrain. Pour cela, nous allons modéliser un arbre individuel à l’aide de primitives géométriques, puis dupliquer son affichage en de multiples endroits du terrain.
2. Modélisation du tronc
Le tronc est modélisé sous la forme d’un cylindre, plus précisément sont approximation par un maillage triangulaire.
Implémentez une fonction
mesh create_cylinder(float radius, float height)
créant un maillage de forme cylindrique, de rayon et de hauteur passé en paramètre. On pourra supposer l’axe du cylindre centré autour de l’axe \(z\).
Rem. Pensez au préalable à la manière dont vous allez ordonner les sommets et les triangles. Le cylindre peut rester une surface ouverte à ces extrémitées.
(Après l’avoir réalisé vous même, ou si vous éprouvez des difficultés, un code solution possible de création du cylindre est proposé ici)
2. Modélisation du feuillage
Le feuillage est constitué de 3 cones successifs (toujours sous forme de maillage).
Créez tout d’abord une fonction
mesh create_cone(float radius, float height, float z_offset)
créant un maillage de forme conique. Les paramètres d’entrées de la fonction sont respectivement: le rayon à la base du cone, la hauteur de celui-ci, un offset à appliquer entre \(z=0\) et la base du cone.
Rem. Le bas du cone représentant la base du feuillage, pensez à construire cette fois une surface fermée.
Une fois la fonction réalisée, utilisez la fonction suivante permettant de dupliquer trois cones en un seul maillage
mesh create_tree_foliage(float radius, float height, float z_offset)
{
mesh m = create_cone(radius, height, 0);
m.push_back( create_cone(radius, height, z_offset) );
m.push_back( create_cone(radius, height, 2*z_offset) );
return m;
}
(Après l’avoir réalisé vous même, ou si vous éprouvez des difficultés, un code solution possible de création du cone est proposé ici)
Peuplement du terrain
Positionnement d’un arbre unique
Essayez dans un premier temps de placer votre arbre à différents endroits du terrain. Pour cela, il est possible d’utiliser le paramètre uniforme de translation afin d’afficher l’arbre à une position donnée (voir l’approche vue dans le code d’introduction pour la sphère à appliquer sur le tronc et le feuillage).
Notez que pour connaitre la position d’un point sur le terrain, vous pouvez explicitement appeler la fonction evaluate_terrain avec un paramètre \((u,v)\in[0,1]^2\).
Positionnement de plusieurs arbres.
Pour positionner plusieurs arbres, l’idée est de pré-stocker un ensemble de \(N_a\) positions sur le terrain. On note \(p_i\) la ième position pré-stockée (\(i\in[0,N_a-1]\)). Au moment de l’affichage, on applique alors l’algorithme suivant
For all positions pi
trunk.uniform.transform.translation = pi
foliage.uniform.transform.translation = pi
draw(trunk);
draw(foliage);
Mise en pratique:
Ajouter un attribut std::vector<vcl::vec3> tree_position; à la classe scene_model, et implémentez une méthode update_tree_position (appelé depuis setup_data) qui vient remplir le vecteur tree_position avec des points échantillonnés sur le terrain.
Pour cela, on pourra calculer un ensemble de points p tels que p=evaluate_terrain(u,v), avec (u,v) échantillonnés suivant une distribution aléatoire uniforme.
Pour disposer de valeurs aléatoires entre deux valeurs a et b, on pourra utiliser la fonction rand_interval(a,b) (rem. rand_interval() sans paramètre génère un nombre entre 0 et 1).
Dans la fonction d’affichage frame_draw, utilisez ensuite les positions stockées dans tree_position pour afficher des copies d’arbre translatés à ces endroits. Pour cela, vous afficherez le tronc et le feuillage séparément en initialisant leur paramètre uniform de translation respectif.
Remarques
-
Notez que votre std::vector
tree_positionne doit être complété qu’une unique fois (par exemple dans la fonction setup_data). -
Notez également qu’une seule instance de l’arbre est nécessaire. Il s’agit d’un seul arbre affiché plusieurs fois à des positions différentes. Faites bien attention en particulier de ne pas recréer de données de type "mesh_drawable" dans la boucle d’affichage.
-
Il est possible d’ajuster légèrement la hauteur des arbres (en abaissant la coordonnée z) pour éviter qu’ils apparaissent "hors du sol" à cause de la courbure du terrain.
Extensions
Extension 1 Faites en sorte d’éviter que deux arbres puissent être trop proches l’un de l’autre - et notamment qu’ils ne puissent s’intersecter. Pour cela, vous pourrez adapter l’algorithme créant les positions stockées des arbres.
Extension 2 (optionnelle) En suivant une démarche similaire, il est possible d’ajouter d’autres objets formés de primitives simples. Exemple avec l’ajout de champignons.