Le moteur de rendu 2.5D passe à la vitesse supérieure

Semaine du 19 au 24 novembre 2018 :
Cette semaine, j’ai fais plus de développement de mon moteur de rendu 2.5D que du robot trader. Je continuerai et essaierai de terminer la première version du robot cette semaine. Après, je ferai de la simulation pour voir ce que ça donne, et ça me libérera du temps pour d’autres choses. Je vais donc parler un peu du moteur de rendu old-school 🙂

Renderer 2.5D

La première chose que j’ai fais, c’est l’ajout du dessin de la hauteur de l’espace. La fois dernière mon image montrait un mur étiré sur la hauteur. Comme l’image suivante le montre, on voit 3 murs en 3D (enfin non en 2.5D, mais à ce stade, l’illusion est la même) 🙂

Mode3DWithHeight-3Walls_2018-11-19_23-51-27

Autre chose, j’ai enfin trouvé une façon simple et élégante pour dessiner les murs dans le  bon ordre à l’écran. En 3D ou 2.5D, les objets dans l’espaces doivent respecter une cohérence visuelle. Un objet lointain ne doit pas être dessiner devant un objet proche, autrement l’image est aberrante. La technique la plus naïve qui soit consiste à dessiner les objets sans se soucier de l’ordre et d’éventuellement redessiner au-dessus les objets les plus proches en regardant la distance par rapport à la caméra. C’est simple, mais l’ennui est que l’ordinateur repasse plusieurs fois sur certains pixels et augmente donc le temps d’affichage. Difficile d’avoir du temps réel avec cette méthode, à moins d’avoir un processeur puissant. Dans les vieilles machines, la puissance n’était pas élevée, et donc il valait mieux passer une seule fois sur chaque pixel. L’idée que j’avais eu lors de mon premier prototype présenté en début d’année, était de calculer des intersections entre les droites de projection (le rayon envoyé de la caméra, vers un point d’un mur, en passant par chaque pixel) et les murs en conflit avec d’autres murs. Sur un ordinateur puissant, ce n’est pas trop un problème, mais là encore, sur du matériel plus ancien ou si plusieurs programmes doivent fonctionner en même temps, ou que votre programme (un jeu-vidéo) doit calculer d’autres choses comme des personnages, des effets-spéciaux, etc… ça peut devenir peu performant. De plus, l’algorithme que j’avais écrit était trop compliqué à comprendre et donc à maintenir. Je me retrouvais finalement avec des bugs graphiques dans certains angles ou dans certaines configurations des murs. Je vous montre ici une salle entièrement dessinée, avant de gérer l’ordre des objets à l’écran puisque pour cette image ce n’étais pas encore nécessaire.

Mode3DFloorAndCeil_2018-11-21_22-25-16

J’ai trouvé une solution élégante et simple à comprendre et à programmer. Elle fonctionne comme la technique naïve, mais la distance permet de décider de ne pas dessiner. Profitant de la 2.5D, pendant le calcul préparatoire du dessin, on parcours certains pixels plusieurs fois, mais seulement sur une seule dimension, et non sur les 2 dimensions de l’écran. Le gain en performance est considérable et cela même sur du matériel ancien. Le principe est tout simplement un tampon de profondeur (depth buffer) comme on le retrouve dans la 3D moderne (OpenGL par exemple). L’idée est de calculer les intersections des rayons de projection et les murs pour en déduire les distances, puis de les stocker dans un tableau à une dimension faisant la largueur de l’écran. J’en profite pour aussi y stocker l’identifiant des murs. Une fois arrivé dans l’étape du dessin, le programme dessine à la seule condition que l’identifiant du mur actuel soit celui stocké pour une colonne de pixel actuelle (la colonne Y pour la position X actuelle). Autrement, il se contente de passer vite fait dessus sans rien dessiner et sans parcourir la colonne. La distance permet de déterminer simplement l’ombrage qui donne une impression de 3D old-school comme vous pouviez le voir dans l’image de la fois dernière (le dégradé gris clair). Voici une image montrant la salle avec dedans, une colonne dessinée à la bonne place, et avec l’ombrage de profondeur réduit à 32 nuances 🙂

Mode3DRetroShading_2018-11-24_05-35-46

L’étape suivante sera d’abord de restructurer plus proprement le code et ajouter des commentaires. Le programme ne comporte aucun bug, et je le comprends relativement bien, mais il est proche de l’usine à gaz si je ne fait pas cette étape d’amélioration de sa qualité. C’est comme cela que je fonctionne maintenant, et c’est efficace 🙂 Ensuite, je ne coderais pas tout de suite les textures, même si ça me tente bien. Avant, je veux coder les portals, en utilisant les secteurs que j’ai écrit mais qui ne servent pas encore pour l’instant. C’est ce qui va permettre de faire des architectures complexes, des bancs, des comptoirs, des tables, etc… comme dans DukeNukem3D, Blood, etc… 🙂 Bien sûr ce n’est pas suffisant pour faire de vraies tables, mais je verrai plus tard pour les subtilités qui profiteront sûrement d’un ajout de vraie 3D ou peut-être de voxel 🙂

Ce que j’ai expliqué en résumant et sans schémas à propos de la technique de dessin des objets dans l’espace 2.5D n’est pas évident à se représenter. Je le comprends, parce que je l’ai codé hier soir, mais pour vous le transmettre ça ne suffit pas. Je ferai des tutoriels écrit ici et sûrement des vidéos à l’avenir avec tout ce qu’il faut pour comprendre comment ça fonctionne. Je veux que ce type de technique soit accessible pour tou.x et notamment les francophones. Car j’ai beau chercher des créations maisons sur le net de tels programmes, je ne trouve que du raycasting, car plus simple et mieux documenté. Éventuellement, on peut trouver des techniques à base d’arbre BSP car là encore bien documenté, même si je n’en voit pas tant que ça en fait maison (Anecdote, Demiurge utilise du BSP pour la physique, mais c’est buggé parce que pas bien adapté au boxel). Mon moteur présenté ici n’est ni un moteur de raycasting, ni un moteur de BSP, tout comme le moteur Build de Ken Silverman. Wolfenstein3D utilise par contre le raycasting et donc à de grosses limitations, et Doom utilise le BSP ce qui donne plus de possibilités, mais plus contraignant que la technique utilisée par Build ou mon moteur. Mon moteur ne prétend pas reproduire exactement les techniques utilisées par Build, mais donne un résultat similaire avec une tendance à privilégier l’utilisation d’unités entières. Pour l’instant, je ne me passe pas encore tout à fait des unités flottantes, mais il est probable que je puisse arriver à ne plus en utiliser du tout comme à l’époque de Build avec les processeurs qui ne les géraient pas encore. La technique de dessin cité ici n’utilise les flottants que pour calculer les intersections sur des fonctions affines. Le reste du système, notamment, le stockage des distances et identifiants est totalement adapté aux entiers. Notez que l’élimination des murs ne faisant pas face à la caméra (backface culling) a été réalisé en une seule ligne de code, avec un produit en croix (cross product). Ceci permet d’utiliser uniquement des entiers, et en plus, est rapide à calculer 🙂

Si vous avez aimé cet article, vous pouvez me faire un don en Ethereum 🙂
Ethereum : 0xab7dD988aD7348C75db90343591596974A435803

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *