! Update ! :
L'article ayant commencé sur ce forum, le texte complet et mis à jour avec le code source est disponible sur ma page web :
En Français :
Moteur de terrain en voxel et C++ - première page (LeGreg)
Moteur de terrain en voxel et C++ - deuxième page (LeGreg)
et en Anglais :
Voxel terrain engine in C++ - first part (LeGreg)
Voxel terrain engine in C++ - second part (LeGreg)
Merci de vous y réferer. Je garde ce thread ouvert pour raisons historiques pour ceux qui veulent s'y référer.
-------------------------------------
Chose promis, première partie du tutoriel sur les terrains en voxel. La suite avec le source code et l'exécutable dans pas longtemps.
Qu'est-ce qu'un voxel ?
Commençons par le commencement, qu'est-ce qu'un voxel ? Le mot voxel est la contraction de "volume element". En fait pas vraiment mais c'est pour que le nom sonne pareil que pixel (picture element). Il y a plusieurs sortes de voxels. Celui que nous ne considèrerons pas ici est l'unité indivisible d'un objet volumétrique. C'est l'équivalent pour les objets en 3D du pixel pour l'image en 2D. Un bitmap tridimensionnel à la place des vecteurs habituels (utilisés dans les objets polygonaux). Ce sont les briques de legos. Les petites briques constituent un objet plus grand, de la même façon que les pixels forment une grande image.
Mais notre moteur de terrain en voxel est d'un autre genre. Celui qui a été mis en oeuvre dans des jeux classiques comme Outcast ou Comanche. Le moteur de terrain en voxel est un moteur 2,5D il lui manque quelques degrés de liberté par rapport à un moteur 3D classique. Il a les mêmes degrés de liberté qu'un moteur de raycasting (comme dans Doom). Ils sont si proches qu'en fait on pourrait utiliser la méthode du raycasting pour visualiser notre terrain en voxel. Mais ce n'est pas la méthode utilisée ici, en fait on va plutot faire une rasterisation avec quelques astuces pour que ce soit rapide. Pourquoi ça s'appelle aussi voxel ? Simplement parce que notre terrain est également constitué de petites briques que l'on extirpe d'une image en 2D et que l'on étire verticalement. C'est bien un voxel, juste une définition un peu restrictive par rapport la première.
Introduction
J'avais débuté le projet de rendre un moteur de terrain similaire aux screenshots de Outcast que j'avais découvert en 1999 en même temps que tout le monde. C'était un projet assez important pour quelqu'un qui débutait et qui ne connaissait rien au C++ ou à la programmation 3D mais les maths semblaient largement faisables. La version détaillée ici ne change que peu de choses par rapport au design original, j'ai juste converti le projet pour tourner en Win32 plutot qu'en direct draw (d'ailleurs j'utilise beaucoup de notations Win32 et je m'en excuse auprès de ceux qui y sont allergiques. Bon on peut passer à autre chose maintenant..). Les algos sont indépendants de la plateforme utilisée et peuvent être retranscrits facilement.
Voxel terrain engine in C++ - algorithms and source code
Ci dessus c'est la version actuelle et ci dessous c'est la version de l'époque avec du "progammer art" du plus bel effet (et un pentium peu performant et sous utilisé).
Voxel terrain engine in C++ - algorithms and source code
Pourquoi auriez vous besoin de programmer un moteur de rendu de terrain par voxel à l'heure actuelle ? Réponse courte, vous n'en avez pas besoin. Si vous possédez une carte accélératrice 3D comme tout le monde sur PC à l'heure actuelle, vous pouvez rendre des terrains super détaillés avec du multitexturing, de l'occlusion avec un z-buffer, des langages de shaders puissant et le travail est déjà maché (clipping, rasterisation, transformation, etc.). C'est probablement plus rapide que tout ce que vous pouvez espérer faire en software. Tout ce qui suit est un simple exercice pour le fun : il y a de meilleurs outils servez-vous en.
Voilà pour l'avertissement, on peut commencer à s'amuser.
Au commencement il y avait la height map
Le height map ou carte d'élévation est la façon la plus simple et la plus compact de représenter un terrain quelconque. Cette carte associe implicitement une élévation fixe à un jeu de coordonnée 2D. On ne peut pas représenter toutes sortes de terrain de cette manière; aucun moyen d'obtenir une arche, une grotte ou toute formation de terrain qui viole la règle d'"une seule élévation par position sur la carte". Si vous avez besoin de choses plus complexes, passez votre chemin, notre moteur de voxel n'est pas pour vous. Vous aurez problablement besoin d'un moteur un peu plus générique comme un moteur polygonal (et de l'accélération 3D).
Voxel terrain engine in C++ - algorithms and source code
L'Utah est trop complexe pour notre moteur de terrain par voxel ( photos et images copyright Grégory Massal)
Créer une carte d'élévation (CE) est un peu complexe, bien entendu il y a des données de terrain réélles disponibles gratuitement ou pour un peu d'argent, mais que se passe-t-il si l'on veut rendre une région qui n'existe que dans notre imagination ? Demander à un artiste de faire un simple terrain avec des failles, montagnes, crevasses, plaines, peut être douloureux. Heureusement il y a des outils disponibles qui vous permettent de générer des terrains inédits de manière aléatoire. Pour cette article j'ai utilisé la version gratuite de World Machine et Terragen. Avec un peu d'huile de coude ou l'aide de scripts préexistants vous pouvez générer des zones suffisament réalistes (allez voir leurs galleries pour plus d'infos)
À chaque position sur la carte, on a une élévation encodée sous forme d'un entier de 8 bits. Les valeurs de 0 à 255 peuvent sembler ne pas être assez pour votre vision initiale mais on peut bien entendu faire varier l'intervalle d'élévations représentées par ces nombres sur 8 bits (compression), et si ça n'est vraiment pas assez il reste la possibilité d'éncoder la hauteur du terrain sous forme d'entiers sur 16 bits, ou encore des nombres à virgule flottante sur 32 bits. C'est une décision à prendre en fonction de vos données originales et n'oubliez pas qu'il y a des façons de compresser qui réduiront votre encombrement disque/mémoire. Je ne rentre pas trop dans les détails. On continue.
Voxel terrain engine in C++ - algorithms and source code
Assurez vous que la carte que vous avez créé utilise le plein intervalle de nombres de de 0 à 255, c'est suffisamment petit pour que vous n'ayez pas envie de perdre de précieux bits d'infromation dans le processus. Pour les besoins de ce moteur je me suis aussi arrangé pour que ma carte soit répétable ("tileable" ). Cela veut dire que si je dépasse la limite de ma carte je veux pouvoir me retrouver à l'origine de ma carte sur l'autre bord sans tomber d'une falaise, pour cela il faut que le bord droit soit une parfaite copie du bord gauche et le bord haut une parfaite copie du bord bas. Je ne rentre pas trop dans les détails ici mais il y a plusieurs moyen de s'assurer de cela, la première est de prendre une carte non répétable et d'en faire une copie mirroir de chaque coté (carte finale 4 fois plus grande). De cette manière chaque bord est exactement égal à celui qui lui fait face puisque c'est sa copie conforme. L'inconvénient c'est que l'info est hautement redondante de cette façon puisque chaque quadrant de la carte est une copie de tous les autres. Une autre solution est de prendre juste une bande sur les bords haut et gauche, d'en faire une copie en fondant le résultat sur le coté droit et bas. Utiliser un gradient progressif comme terme de blending comme ça les images seront parfaitement intégrée l'une dans l'autre (en supposant que la différence ne soit pas trop marquée) et les bords se correspondront à cent pour cent également. Pour cette article, j'ai tout fait à la main et c'est pas exactement parfait mais avec un peu de pratique vous pouvez avoir un résultat où vous devrez vraiment bien chercher pour y trouver des défauts. Il y a également des algorithmes complexes qui essaient de préserver les caractéristiques de l'image en en générant une entièrement nouvelle qui minimise les artefacts liés à la répétitions mais c'est un peu trop lourd pour ce petit projet.
Le deuxième jour vint la visibilité
Avant de tracer tous les éléments constitutifs de notre terrain vous devrez déterminer ce qui est visible et ce qui ne l'est pas. C'est à la fois un problème de performance et de correction. Si un élément de terrain qui est partiellement visible ou totalement caché est tracé, alors il y a un problème de correction. Si vous demandez à tracer un élément de terrain qui est situé derrière votre caméra ou dehors de votre champ de vision alors vous avez dépensé de précieux cycles de votre processeur pour rien.
Voxel terrain engine in C++ - algorithms and source code
L'image ci dessus représente ce que vous pouvez voir depuis un point de vue donné (en rouge), la plupart des zones noires seront en dehors de votre écran et quelques parties qui peuvent être sur votre écran à l'intérieur de votre champ de vision (frustum) sont en fait caché par des parties du terrain qui se trouvent en face d'elles. Ce n'est pas réaliste en temps de calcul de prendre chaque petit élément de notre carte et déterminer s'ils sont clippés en dehors du frustum. À la place on va utiliser une représentation hiérarchique de notre terrain à deux niveau. Un niveau détaillé (detailed level) et un niveau grossier (coarse level). Je considèrerai les pixels du niveau détaillé que lorsque j'aurai déterminé au niveau grossier qu'ils sont totalement visibles ou partiellement visibles (ambigu).
On pourrait avoir plus de niveaux mais le bénéfice de l'ajout d'un niveau, n'est plus aussi intéressant quand on a déjà une structure hiérarchique en place. Bien entendu, le niveau grossier doit être suffisamment petit pour ne pas avoir à faire tout le travail de clipping au niveau le plus bas (limiter le cas ambigu) et doit être suffisamment grand pour que tout clipping d'une zone grossière apporte un gain en performance suffisant. J'ai opté pour des grandes zones de 256x256 éléments de terrain dont je peux déterminer rapidement la visibilité.
Voxel terrain engine in C++ - algorithms and source code
En rouge pur, vous avez les zones qui sont à 100% à l'intérieur du frustum (le frustum est le cone de visibilité, ou encore la zone délimitée par nos deux lignes en vert où se trouve la partie potentiellement visible de notre monde pour une position et rotation donnée). En rose ce sont les zones qui ne sont pas entièrement à l'intérieur du frustum mais que l'on doit inclure parce que certains de leurs éléments seront visibles. Dans le cas le pire cela veut dire que seul un élément de terrain peut être visible sur les 65536 que constitue notre zone. C'est mal mais on est obligé de vivre avec à moins d'aller pour une grille de départ moins grossière.
Voxel terrain engine in C++ - algorithms and source code
Le clipping est une opération assez simple, vous avez à tester chaque angle d'un quadrangle qui délimite une zone contre les trois droites que j'ai appelé "left, right et near" (gauche droite proche). J'ai mis la droite "near" à une courte distance de notre position de caméra, parce que je devrai diviser plus tard par z (la distance le long de l'axe de vue) et je veux éviter les divisions par zéro. Remarquez que plus on pousse notre droite "near" loin, meilleures seront les performances mais le risque est d'augmenter les artefacts de clipping.
Avant de clipper il faut déterminer les équations de chacune de ces droites. Trouver l'équation est équivalent à chercher les normales à chacune des droites dans le plan 2D. Les normales sont définies en terme de fov et rho, les deux angles caractéristiques de notre configuration.
Voxel terrain engine in C++ - algorithms and source code
Si je pose x1, y1, x2, y2 les quatre coordonnées de notre boite d'encadrement (bounding box) aligné sur les axes. Alors nous utiliserons le code suivant pour clipper selon le plan proche :
Code :
- dist1 = m_nearplane.a * x1 + m_nearplane.b * y1 + m_nearplane.c;
- dist2 = m_nearplane.a * x2 + m_nearplane.b * y1 + m_nearplane.c;
- dist3 = m_nearplane.a * x2 + m_nearplane.b * y2 + m_nearplane.c;
- dist4 = m_nearplane.a * x1 + m_nearplane.b * y2 + m_nearplane.c;
- if (dist1 <= 0 && dist2 <= 0 && dist3 <= 0 && dist4 <= 0)
- {
- // clipped by near plane
- }
|
On répète l'opération pour chaque ligne (plan). Nous avons travaillé uniquement en 2D jusqu'ici. Nous n'aborderons la troisième dimension que quand on commencera à projeter des choses sur notre écran (temporairement avant de revenir à la 2D de notre écran).
Message édité par LeGreg le 29-02-2008 à 20:30:33
---------------
voxel terrain render engine | animation mentor