Jump to: navigation, search

Génération de décors 3D avec proclipse

Landgen-cover.jpg

Introduction

Cette page est un support au cours Processing avancé donné dans le cadre des ateliers Créactifs! à l'UMONS. Ces cours donnés en soirée sont gratuits et accessibles à tous. Ce cours nécessité néanmoins certains prérequis en programmation, qui ont pu être acquis grâce à l'introduction à Processing (par exemple).

Les séances de cours sont diffusées en temps réel sur twitch.tv.

Un dépôt sur bitbucket permet de suivre l'évolution du code. Le dépôt contient quelques images qui serviront de base de travail au premier cours (voir source > data > greymap-*.png).

Prérequis

Le cours sera donné dans Eclipse, un environnement de développement Java, en utilisant le framework processing sans son IDE. Avant le début du cours, veuillez vous rendre sur la page Proclipse et suivre les instruction d'installation.

Horaire

Les lundis:

  • 16 novembre,
  • 23 novembre,
  • 30 novembre,
  • 7 décembre et
  • 14 décembre

de 18 à 20h (GMT+1).

Le plan d'accès est disponible sur le site Créactifs!.

Cours #1

Commit correspondant à ce cours: corrections du premier cours - révision des bases

Installations

  1. Installation de Java 1.8 nécessaire à proclipse: téléchargement et "next, next".
  2. Installation d'eclipse. Sélectionner le premier choix de la liste, Eclipse for Java developpers, ensuite "next, next"
  3. Démarrage d'eclipse. Par défaut le workspace est créé dans le dossier utilisateur, quelque soit l'OS. Une fois lancé, cliquer sur "workbench" pour accéder à la vue de travail.
  4. Déploiement de proclipse. Voir le tutoriel d'installation.
  5. Déploiement de LandGen. Reproduire le tutoriel Aprojet en adaptant le nom du projet et du dossier dans le workspace.

Démarrage

Une fois le projet présent dans le "Project explorer", ouvrez-le (click sur la flèche noire), et descendez dans le dossier "src/(default package)/LandGen.java". Double-cliquez pour l'ouvrir dans l'éditeur.

  1. explication de la structure de la classe principale d'un projet proclipse:
    • la méthode settings()
    • les méthodes setup() et draw()
    • la méthode main()
  2. chargement et affichage d'une image avec PImage
  3. l'auto-completion, comment ça marche
  4. chargement des pixels de l'image
  5. rapide explication de la récupération des pixels dans processing
  6. deux manières de lire tous les pixels d'une image:
    • grâce à la taille du tableau de pixels ( PImage.pixels.length )
    • grâce à une double-boucle sur Y et sur X
  7. explication du système de coordonées dans processing
  8. utilisation des transformations de repère translate(), rotateX(), rotateY() et rotateZ()
  9. affichage des 3 axes


La source développée durant ce cours est largement commentée dans le dépôt.

Cours #2

Ce cours se concentre sur la création d'une classe dédiée à la transformation d'une image en un ensemble de points 3D formant une grille. L'objet Ground est instancié et configuré depuis la classe principale et se comporte ensuite de manière autonome.

Première classe

  1. Création de la classe Ground dans eclipse
  2. Constructeur et variables d'objet.
  3. Instanciation dans la classe principale
  4. Utilisation des méthodes de PApplet dans la classe Ground, passage d'un parent dans le constructeur.

Image vers points 3d

  1. Création d'une liste de points 3D, classe PVector
  2. Transformation des pixels de l'image en points 3D
  3. Optimisation de la grille (partie 1)
  4. Affichage de la grille dans la fenêtre.

Cours #3

Pour créer un mesh à partir de la grille de points générée, il faut trouver une manière de décrire les faces de l'objet.

GFace

  • Cette classe est très simple: elle contient les références des 3 PVectors desquels elle dépend.
  • Le constructeur nous oblige à définir ces 3 points lors de la création de l'objet.
  • Les points (pt1, pt2 et pt3) sont en public. Ceci pour pouvoir y accéder facilement lors du dessin dans la classe Ground.

Génération des faces

  • Dans la méthode init() de Ground (Ground.init), calcul du nombre de faces. Si les faces sont triangulaires, il y a 2x plus de triangles que de cellules dans la grille.
  • L'ordre d'attribution des points dans la face est très important: il va définir l'orientation de la face. En OpenGL, une face n'est dessinée que quand le "dessus" de la face est dirigée vers la caméra. Dans le cas présent, nous utilisons le sens horlogique.

3d-faces-points-order.png

  • Une fois l'initialisation finie, nous passons au dessin. Grâce à la méthode PApplet.beginShape( TRIANGLES ), nous pouvons imprimer tous les points des faces dans une simple boucle, la carte graphique interprétera cette suite de position comme des groupes de 3 points définissant chacun un triangle.

Commit correspondant à ce cours.

Cours #4

Jusqu'ici, la position des points stockés dans notre grille est défini une fois pour toute. L'édition ultérieure, comme par exemple la modification globale de l'altitude du paysage, est difficile à accomplir.

Pour rendre notre modèle plus facile à manipuler, nous allons normaliser toutes les mesures lors de l'initialisation.

GPoint

  • La classe PVector ne permet pas de stocker plus de 3 valeurs (x, y, z). Nous créons donc une classe GPoint qui hérite de PVector. Grâce à cet héritage, les GPoints ont les mêmes variables et méthodes que les PVectors, en nous permettant d'ajouter des variables spécifiques à nos besoins.
  • Un PVector privé appelé norm va nous permettre de stocker une fois pour toute les 3 coordonnées dans une forme "abstraite". Nous n'utiliserons jamais directement norm pour le dessin, il servira à recalculer le positionnement absolu de notre point dans le monde.

Modification d'altitude

Pour modifier l'altitude de notre modèle, il nous faut maintenant mettre en place une chaîne d'appel de méthodes.

  1. Dans LandGen, appel à une méthode de Ground.setHeight( float ) lui demandant d'adpater l'altitude globale. Les GPoint étant stockés dans le ground, il est logique de demander au "ground" de faire le nécessaire.
  2. Dans Ground.setHeight( float ), après une vérification de l'existence des points, on boucle sur tous les points et on demande à chacun des points une adaptation d'altitude. Comme pour la première étape, on demande à l'objet d'accomplir une action. Le Ground n'ayant pas accès au PVector "norm", c'est le GPoint qui doit s'en occuper.
  3. Dans GPoint.setHeight( float ), la variable z est recalculée en fonction de l'altitude demandée et de la position normalisée.

Cette manière de procéder peu paraître contre-intuitive. Néanmoins, elle assure un maximum de sécurité lors de l'exécution du programme, chaque objet étant responsable de l'intégrité de ses données. Chacun peut en effet refuser d'effectuer l'opération si son état ne lui permet pas.

Cette découpe de l'intelligence du programme en petits morceaux est une des forces de la programmation orientée-objet!

coordonées UV

Pour pouvoir appliquer une texture sur notre modèle 3D, nous devons générer des coordonnées UV pour chaque points. Ces coordonnées n'ont aucun rapport avec la position du points dans le monde 3D. Elles concernent la position de ces mêmes points dans l'espace texture. Dans notre cas, ces 2 systèmes sont proches, puisque le modèle est issu d'une image.

3d-uv-coordinates.png

Pour pouvoir découpler les coordonnées texture de la position, nous ajoutons deux variables à notre classe GPoint: u et v.

Commit correspondant à ce cours

Cours #5

Normales

Surface normal.png

Dans la classe GFace, ajout du calcul des normales dans la méthode GFace:update().

  • A partir du point 1 de la face, calcul des 2 vecteurs pointant vers les 2 autres points de la face. - dir12 et dir13.
  • Normalisation de ces 2 vecteurs.
  • Caclul du produit vectoriel.
  • Normalisation de la normale, par acquis de conscience.

En même temps, le centre de la face est calculé. La normale est ensuite affichée par Ground.

La modification des dimensions de l'objet ayant une influence directe sur les normales des faces, nous avons ajouté une méthode dans Ground qui est appelée à chaque fois qu'une opération de redimenssionnement est demandée.

Génération de texture

  • Ajout d'un argument à la méthode Ground:loadTexture. Deux textures différentes peuvent maintenant être chargées.
  • Dans la méthode Ground:loadTexture, chargement des deux images.
  • Mise à l'échelle des deux textures (512x512 dans le cas présent).
  • Mélange des deux textures en fonction du z des normales.


Le code du mélange peut paraître curieux. Les coordonnées UV sont utilisées pour dessiner les triangles sur la texture. Juste après la création de la texture tex, il y a mise à jour de ces coordonnées pour toutes les faces.

  1. Le u est donc maintenant proportionnel à la largeur de la texture le v à sa hauteur.
  2. Pour simplifier la génération de la texture, un test est effectuer pour chaque face.
  3. Si le z de la normale de la face est supérieur à une valeur, la première texture est utilisée.
  4. Dans le cas contraire, la deuxième est utilisée.
  5. Comme les 2 textures sources et la texture finale ont la même taille, et que les coordonnées uv ont elles aussi été ajustée à la taille, nous pouvons utiliser le u et le v comme position x et y des triangles ET comme référence texture.


Commit correspondant à ce cours

Resources

--Frankiezafe (talk) 11:50, 25 October 2015 (CET)