2/5

2. Modélisation

goal

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_GRAPHICS en #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.

terrain wireframe
terrain

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.

tree

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\).

cylinder

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.

cone

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;
}
foliage

(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.

terrain with tree

Remarques

  • Notez que votre std::vector tree_position ne 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.