top of page
Rechercher

Création procédural de Cliff Village

Dernière mise à jour : 14 mai 2021


Ce projet a été accompli dans le cadre d'un de mes cours universitaires. L'objectif fut de créer un environnement au choix en mélangeant les différentes techniques apprises durant le cours et nos compétences personnelles. Étant un aspirant artiste FX, je me suis mis au défi d'apporter une touche procédural. Dans ce texte, je tenterai d'expliquer sommairement l'organisation de mon premier projet de ce genre sur Houdini.


Dans le cadre d'un de nos cours universitaires, Ariane Tremblay-Lecomte, Max Spiegel et moi-même nous sommes donné la tâche de créer un village qui serait suspendu au flanc d'une falaise. Les maisons perchées seront des variations aléatoires de différentes maisons de bases modélisées dans Maya. Chacune de ses maisons est divisée en modules qui peuvent être facilement réarrangés dans une nouvelle configuration pour avoir une nouvelle forme générale.

Les deux maisons types et leurs modules.


Une fois la modélisation terminée. C'est le temps de passer dans Houdini. Le plan de match est de préparer des Compiled Blocks pour chaque maison type qui prendront, en entrée, une valeur seed et retourneront une version aléatoire de la maison basée sur ce seed.


 

La technique est fondamentalement identique pour les deux maisons. Générer plusieurs versions de la maison en arrangeant différemment les modules prédéfinis. Ces modules ont subi eux-mêmes une génération aléatoire à l'aide de sous-modules, comme des fenêtres ou des extensions de bâtiments pour avoir différents niveaux d'aléatoires dans la génération procédurale et ainsi avoir un minimum de dizaines de maisons uniques.


Pour choisir aléatoirement la position unique de chaque module, j'ai pris le temps de développer un snippet de code en VEX dans Attribute Wrangle qui permet de définir un nombre arbitraire de positions pour la géométrie et choisir aléatoirement une de ces positions. Le tout est lié à une interface personnalisée sur le node Wrangle, qui permet de facilement tester les positions et de définir une distribution personnalisée dans la sélection aléatoire.

Interface personnalisée du Attribute Wrangle


Par contre, cette technique fonctionne bien seulement pour déplacer un module sur un autre. Mais, si plusieurs modules sont déplacés en même temps, cette façon de faire ne tente pas d'éviter l'interpénétration. Pour l'éviter, tout en générant des assemblages plus complexes, j'ai dû me rabattre sur une technique plus simple : simplement arranger les modules à la main avec des nodes Transform en plusieurs versions. Ces différentes versions sont choisies à l'aide d'un node Switch qui lit la valeur aléatoire d'un node Attribute Wrangle. Ce choix d'adopter une technique "moins intelligente" a été motivé avant tout par la contrainte de temps.


 

Chaque module comporte des cubes qui sont des marqueurs pour les fenêtres. Ces marqueurs sont remplacés par une version aléatoire de fenêtre à l'intérieur d'une boucle ForEach Named Primitive. Dans cette boucle, chaque cube trouve son vecteur de direction en échantillonnant la normal de la surface du module. La position du cube, combiné au vecteur de direction, permet de générer une matrice de transformation 4x4 qui est appliquée à la géométrie d'une des variations de fenêtre.


 

Une fois qu'une nouvelle génération d'une maison est créée. Elle est passée à un autre Compiled Block qui s'occupe de choisir quelle maison est sélectionnée (toujours aléatoirement) et de déplacer la géométrie à sa position finale dans la scène. Cette position est donnée au Compiled Block par un point attribué d'un vecteur de normal pour indiquer la position et l'orientation. Similairement aux fenêtres, une matrice de transformation est créée puis appliquée.


Pour définir la position de chacun de ses points. Je n'ai qu'utilisé le blocking de mon collègue Max. Les maisons temporaires qu'il avait placées ont été converties en ces fameux points de référence. Le vecteur de normal provient de la moyenne du vecteur de normal des 20 points de la falaise les plus proches. Le tout est calculé dans un Attribute Wrangle. Mais, pour laisser plus de contrôle à l'utilisateur, j'ai ajouté des paramètres d'interface pour modifier la position, la direction de la normal et le seed de chaque point.


Interface pour modifier chacune des maisons procédurales






Ensemble des maisons créées procéduralement.










 

Une fois que chaque maison du projet est générée, on entre dans la deuxième section du projet : la génération procédurale des ponts suspendus. Chaque pont doit réunir deux maisons qui sont généralement à la même hauteur. Difficulté supplémentaire, les ponts de cordes seront simulés avec le module Vellum. Pour y arriver, les ponts doivent être généré en deux étapes. D'abord, la structure du pont doit être définie sommairement en simples lignes et points faciles à simuler. Puis, il faut créer la géométrie des ponts selon la définition déformée du pont.


Donc, créer la définition du pont a nécessité de placer des marqueurs qui servent à indiquer les points d'attache des ponts sur les modules des bâtiments. Ces marqueurs sont des polygones rectangulaires où chaque vertex est un point d'attache d'un élément spécifique. À l'aide d'un node Sort, les numéros des points sont réordonnés pour être dans le même ordre d'un marqueur à l'autre. Ainsi, lorsqu'il est temps de tirer une ligne d'un marqueur à l'autre avec un node Add, Houdini peut simplement chercher les points qui ont le même numéro (précédemment stockés dans un attribut). Les lignes créées sont divisées selon la distance entre les deux marqueurs pour avoir des sections équidistantes entre les différents ponts. Enfin, chaque division est itérée dessus pour connecter les différentes lignes ensemble, et ainsi former la définition d'un pont.

Un pont défini par de simples lignes géométriques


 

Avant de générer la géométrie finale, les ponts doivent être simulés. Le processus de simulation est composé de simplement 2 nodes. Le node Vellum Configure Hair et le node Vellum Solver. Difficile de faire plus simple. Les extrémités du pont ont été précédemment stockées dans un groupe pour servir de points d'attache dans la simulation. Le tout est encadré de nodes Transform pour adapter la taille de la simulation selon la règle que 1u = 1m.



Pro-tip que j'ai appris durant ce projet qui m'a ouvert les yeux. Lors d'une simulation Vellum, l'attribut de masse a un impact majeur sur le calcul des contraintes de stiffness. Une masse trop faible (<1) peut rendre l'objet simulé anormalement élastique. Dans mon cas, augmenter la masse du pont a réduit énormément l'élasticité et ainsi divisait la distance entre le point le plus bas et la position initiale d'un facteur de 2!


 

En tout cas, une fois les ponts simulés, c'est le temps de passer à la deuxième étape, créer la géométrie finale. Chaque segment d'un pont est à remplacer par un élément différent. Les lignes transversales inférieures deviendront des planches, les transversales de côté seront une géométrie spéciale qui servira de lien entre les planches et les lignes longitudinales qui seront de simples cordes.


Pour les cordes, un simple node Polywire fait très bien le travail. Par contre, les planches nécessitent de déterminer la bonne orientation pour chacune d'entre elles, en plus de les déplacer. En mettant en relation la position des différents points qui entoure un segment de planche, il est possible de définir les vecteurs d'orientation N (normal/direction de la planche) et Up (orientation sur l'axe défini par N). Il ne reste qu'à rendre les vecteurs orthogonaux l'un par rapport à l'autre pour créer une matrice de transformation à l'aide de la fonction maketransform().


Pour le lien entre les longues cordes et les planches. Je déforme une géométrie très simple qui passera dans un Polywire pour obtenir un assemblage de corde. Pour déformer la géométrie, la technique est très similaire aux planches. Par contre, puisqu'il s'agit d'une géométrie qui doit relier deux morceaux qui se déplacent indépendamment l'un de l'autre, le calcul de l'orientation doit être effectué pour les deux extrémités et appliqué indépendamment.



1. La géométrie non déformée.

2. La géométrie déformée avec les transformations.

3. Avec le node Polywire pour lui donner une épaisseur.





 

Une fois tous les éléments réunis, nous obtenons des ponts dynamiques et procéduraux! Combinés avec les maisons modulaires, un village entier peut être créé.


 

Chaque élément de ce village est attribué d'un matériau qui sera récupéré dans Maya pour avoir une texture projetée. Les seuls éléments de ce projet qui ont nécessité une génération d'UVs furent l'ensemble des cordes créées à l'aide du node Polywire. À la sortie du node, la géométrie a des UVs. Par contre, ces UVs prennent le même espace sur la grille d'UV pour chaque câble, peu importe leur longueur. Ce qui résulte à des textures déformées à travers l'ensemble des câbles de la scène. C'est un problème aisément réglé avec une transformation d'UV basé sur la longueur totale du câble dans un autre Attribute Wrangle.


Enfin, on arrive à la fin de mon projet. J'ai volontairement omis plusieurs procédés et détails techniques pour écourter ce texte aux principes majeurs de ce projet. Si je devais m'étendre sur l'intégralité de mes 357 nodes et mes 681 lignes de VEX (réparties sur 89 Attribute Wrangle), je ne finirais jamais ce texte. Par contre, c'est un exercice que je serai heureux d'entreprendre pour un autre projet plus simple.



Collage des 357 nodes


bottom of page