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, 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 size_t N = 100;

    mesh terrain; // temporary terrain storage (CPU only)
    terrain.position.resize(N*N);

    // Fill terrain geometry
    for(size_t ku=0; ku<N; ++ku)
    {
        for(size_t 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 index3 triangle_1 = {idx, idx+1+N, idx+1};
            const index3 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.

  • 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 le suite le code associé au code exercises/01_modelisation/

  • Pour compiler le code de ce fichier, modifiez la valeur de #define INF443_INTRODUCTION en #define INF443_01_MODELISATION dans le fichier exercise_current.hpp.

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

Créez un attribut std::vector<vcl::vec3> tree_position; à scene_exercise 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étoire uniforme.

Pour disposer de valeurs aléatoires suivant une distribution uniforme en C++, on peut utiliser le code d’exemple suivant

#include <random>

// Uniform distribution in [0,1]
std::uniform_real_distribution<float> distrib(0.0,1.0);
std::default_random_engine generator;

int main()
{
    float var = distrib(generator);

    return 0;
}

Dans la fonction d’affichage frame_draw, utilisez les positions stockées dans tree_position pour afficher des copies d’arbre à 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

Rem.

  • Il est possible d’ajuster légèrement la hauteur des arbres pour éviter qu’ils n’apparaissent "hors du sol" à cause de la courbure du terrain.

  • Faites en sorte d’éviter que deux arbres puissent être trop proches l’un de l’autre (adaptez l’algorithme de création des positions des arbres)

Autre objets

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.