• English
  • Français
  • Jeux de François Hautier

    Développement de OEngine (de juin 2019 à juin 2020)


    Au sein de l’équipe de développement de OEngine, j’ai eu l’opportunité d’aider à la création des projets conçus avec ce moteur : directement pour Astérix & Obélix XXL 3 et Astérix & Obélix XXL Romastered développés par OSome Studio, mais aussi indirectement pour Curse of the Dead Gods développé par Passtech Games.

    Mon rôle était de contribuer à ce que chaque membre de l’équipe travaille dans les meilleures conditions possibles : de l’amélioration des outils de l’éditeur (vue 3D, manipulateurs, cinématiques, UI…), au support des utilisateurs (correction de bugs et de crash divers).

    Durant cette période, j’ai également mis au point un système d’atlas de texture pour améliorer les performances d’Astérix & Obélix XXL Romastered, et contribué au portage Android de White Night, réalisé en partenariat avec Playdigious.



    Atlas de texture

    Les modèles 3D des environnements d’Astérix & Obélix XXL, tels qu’ils ont été conçus à l’époque, étaient composés de dizaines de meshs, avec pour chacun d’eux un matériau et une texture associée. L’utilisation d’atlas de texture pour les modèles 3D des environnements, dans le but de diminuer le nombre de draw call, fut l’une des pistes étudiées pour améliorer les performances du remaster.

    L’image ci-dessus représente le modèle 3D de l’environnement du premier secteur du niveau Normandie, qui nous servira d’exemple. Comme on peut le voir sur l’animation, celui-ci est composé de 110 meshs, et d’autant de petites textures. Une fois celles-ci rassemblées dans des atlas de texture et après quelques calculs d’UV, nous obtenons le même modèle 3D composé de seulement 10 meshs !

    La procédure prend en entrée un ou plusieurs modèles 3D. Premièrement, les matériaux qui les composent sont triés et regroupés par similitude : même shader, mêmes paramètres (double sided, …). Nous obtenons donc des groupes de matériaux similaires, où les paramètres d’entrées vont pouvoir être fusionnés pour obtenir un matériau « atlasé » commun.

    La fusion des paramètres d’entrées utilise une technique classique de texture packing. Prenons exemple sur un groupe de matériaux similaires de type PBR, chacun d’entre eux ayant en paramètres d’entrées une texture d’albédo, de glossiness, de specular et de normal. Les textures d’albédo de tous ces matériaux sont envoyées à un algorithme de texture packer, qui nous renvoie les coordonnées de chacune des textures dans l’atlas de texture final. Sur la base de ces coordonnées, nous pouvons générer un atlas de texture par paramètre d’entrée : un pour l’albédo, un autre pour le specular, etc.

    Quelques précisions s’imposent sur cette étape :

    • L’algorithme de texture packer que j’ai utilisé se nomme crunch et a été conçu par Chevy Ray Johnston. Il est hébergé sur GitHub.
    • Pour éviter d’obtenir des atlas de texture trop grands (en l’occurrence plus grand que 2048x2048px), l’algorithme de texture packer peut être exécuté plusieurs fois. Une première fois avec toutes les textures qui lui sont envoyées, puis si toutes les textures n’ont pas pu être intégrées dans le premier atlas de texture, il est exécuté une nouvelle fois, jusqu’à ce qu’il n’en reste plus. Cela signifie qu’un groupe de matériaux similaires peut être lui-même divisé en plusieurs groupes, par manque de place sur l’atlas de texture.
    • Les textures placées dans les atlas de texture sont séparées par une marge, pour éviter qu’elles ne débordent légèrement les unes sur les autres après compression. Dans la même idée, cela implique de limiter le nombre de mip maps des atlas de texture. En dessous d’une certaine taille, la marge entre les textures deviendrait insuffisante pour empêcher le bleeding.
    • Pour l’exemple, j’ai expliqué que ce sont les textures d’albédo qui sont envoyées à l’algorithme pour calculer les coordonnées. C’est une simplification : ce qui est envoyé à l’algorithme est en fait la taille du paramètre d’entrée le plus grand du matériau à intégrer dans l’atlas de texture. Si je me contentais d’envoyer la taille des textures d’albédo (qui ont, disons une taille de 128x128px), tous les autres paramètres (specular, …) auraient aussi cette taille sur l’atlas de texture. Et si par exemple, la texture de specular de ce matériau était plus grande ? Elle serait réduite à 128x128px sur l’atlas de texture, ce qui n’est pas acceptable…
    • Je n’ai évoqué jusqu’à maintenant que des paramètres d’entrées de type texture. Mais il est tout à fait possible de fusionner sur un atlas de texture des paramètres d’entrées de type couleur.
    • Pour aller plus loin, j’ai même fait en sorte de fusionner des paramètres d’entrées de type float : s’ils sont renseignés dans la procédure, celle-ci est capable de les écrire dans un des channel R G B ou A d’un atlas de texture.
    • Ces deux derniers points impliquent que le matériau « atlasé » utilise un shader capable de comprendre ces paramètres d’entrées spéciaux. L’objectif est de pouvoir grouper des matériaux entre eux, qui ne pourraient pas être regroupés par nature, et ainsi diminuer le nombre de mesh finaux.

    Une fois que le matériau « atlasé » et les atlas de texture de chaque paramètre d’entrée ont été générés, la dernière étape consiste à générer le mesh final du groupe de matériaux. Ce mesh final est l’assemblage de tous les meshs dont le matériau a été regroupé dans ce groupe de matériaux. De plus, les coordonnées UV des meshs d’origine sont modifiées, pour coïncider avec les coordonnées des textures d’origine dans l’atlas de texture.

    Un détail et pas des moindres toutefois : les coordonnées UV des modèles 3D originaux dépassent de l’espace UV, ce qui n’était pas un problème, car les textures se répétaient. Mais avec l’utilisation d’atlas de texture, les textures ne peuvent pas se répéter. Pour éviter de déborder des zones des textures dans l’atlas de texture, un calcul de répétition des coordonnées est appliqué directement dans le shader.

    Malheureusement, ce calcul de répétition a conduit à un artefact graphique, à l’endroit précis de la répétition : une ligne de pixels disgracieuse était très apparente. Toutefois, avec l’aide de Thomas Bonis et de cet article écrit par Mikola Lysenko, nous avons réussi à résoudre ce problème, qui était dû à une mauvaise sélection de la mip map de l’atlas de texture par le GPU.

    En conclusion, les modèles 3D « atlasé » n’ont pas permis d’obtenir un gain significatif de framerate : le gain CPU (moins d’objets à culler, moins de draw call), fut contrebalancé par une baisse GPU (textures plus grosses à sampler, mip maps supprimées, shader plus complexe…). En revanche, ils ont permis d’obtenir un framerate plus constant : les environnements n’étant plus composés de centaines d’objets, mais plutôt de quelques gros paquets, le nombre de draw call à l’écran est devenu stable, peu importe la direction de la caméra.

    Améliorations de la vue 3D

    Dans le but d’embellir l’expérience utilisateur de l’éditeur de OEngine, je me suis attelé à améliorer l’ergonomie de la vue 3D. À l’origine, la caméra de la vue ne pouvait être qu’en perspective classique, ce qui est bien, mais parfois insuffisant pour certains usages.

    La première pierre fut posée par Thomas Bonis, qui permit d’échanger la caméra en perspective avec une caméra orthographique. Toutefois, aucun des outils n’était fonctionnel avec cette projection (sélection, sélection rectangulaire, manipulateurs…). La première étape de mon travail fut donc de corriger ce problème, afin qu’il soit possible de travailler avec la caméra orthographique.

    J’ai aussi complètement repensé la manière dont le positionnement de la caméra était calculé. Mon but était notamment d’obtenir une transition naturelle lors du changement entre la projection perspective et la projection orthographique : il est impératif de conserver le même point de vue, pour éviter que l’utilisateur ne perde ses repères.

    Mon but consistait aussi à rendre le contrôle de la caméra le plus agréable possible, en y intégrant des transitions animées (lorsque l’on cible un objet par exemple), ce qui aide énormément à la compréhension spatiale lors des mouvements de caméra. Mais aussi par l’ajout du manipulateur de vue, qui permet de choisir facilement l’orientation de la caméra entre les 6 directions (X+ X- Y+ Y- Z+ Z-), et de changer la projection de la vue.

    Refonte de manipulateurs

    Dans l’éditeur de OEngine, ce que l’on nomme « manipulateurs » sont les outils qui permettent de déplacer, de tourner ou de redimensionner les objets. Ces outils sont utilisés quotidiennement par les level designers ou les level artists, il est donc important qu’ils soient fonctionnels ! Ce qui n’était pas le cas du manipulateur de rotation, qui était compliqué à utiliser. Je l’ai donc remis à neuf, à l’image des outils de rotation que l’on retrouve dans la plupart des éditeurs 3D (Maya, Blender, Unity…).

    En plus des manipulateurs classiques, OEngine dispose d’un manipulateur supplémentaire : le manipulateur de box ! Comme son nom l’indique, il permet de redimensionner très facilement une box, sans devoir utiliser successivement les manipulateurs de déplacement et de redimensionnement. Toutefois, celui-ci ne fonctionnait pas du tout quand l’objet que l’on souhaitait redimensionner était orienté. Je me suis donc attelé à le rendre opérationnel, peu importe l’orientation de l’objet ! Ce manipulateur connaît enfin le succès qu’il mérite auprès de nos levels designers.

    CMake x White Night

    Depuis août 2019, Google exige que toutes les applications publiées sur le Play Store proposent une version 64 bits. Rien de plus simple pour Fall of Hamster et Bubble Symphonia qui fonctionnent avec Unity… mais pas pour White Night qui fonctionne avec une vieille version de OEngine.

    Cette mission a été réalisée en partenariat avec Playdigious, spécialiste dans le portage et la publication de jeux existants sur de nouvelles plateformes, mobile inclus. Le but était de compiler White Night sous Android Studio, l’environnement de développement le plus adéquat pour une application Android. Pour cela, ma première mission fut d’écrire le CMakeList nécessaire au NDK pour compiler le code source C++ de White Night. Une tâche délicate, car nous n’utilisons pas CMake pour configurer nos projets Visual Studio. Pour simplifier les choses, j’ai écrit un script Python permettant de parser les fichiers du projet Visual Studio et de générer un fichier CMakeList correspondant.

    Enfin, je me suis assuré que le code source C++ de White Night compile correctement pour ARMv7a et ARMv8 : mise à jour de la librairie Google Play Games, passage de FMOD Ex à FMOD Studio, et autres petites broutilles (problèmes d’includes…). Playdigious a ensuite pris le relai et s’est chargé de la configuration du projet Android Studio, de l’exécution correcte du jeu, d’ajout de fonctionnalités, de sa publication, et d’innombrables autres choses !

    Éditeur de cinématiques

    OEngine possède un éditeur de cinématiques, qui comme son nom l’indique, permet d’éditer des objets de cinématiques. Basiquement, un objet de cinématique est composé de plusieurs blocs, placés sur une timeline. Chacun des blocs a un rôle, comme animer des personnages, déclencher des événements, ou… déplacer la caméra.

    Améliorer le bloc de déplacement de caméra était justement la toute première tâche que j’ai eue à accomplir en tant que Tools Programmer sur OEngine : le but étant d’aider la conception des cinématiques d’Astérix & Obélix XXL 3. L’idée était d’ajouter différents modes de déplacement de la caméra, comme un déplacement « orbital », latéral, sur une spline, ou d’autres détails comme pouvoir animer le fov…

    En plus de créer et d’améliorer les blocs nécessaires aux cinématiques de XXL 3, j’ai passé du temps à améliorer l’ergonomie de l’éditeur lui-même. Des détails qui rendent l’outil plus efficace et agréable à utiliser.

    Ajout du redimensionnement des blocs par la gauche (on ne pouvait le faire que par la droite !).

    Ajout d’un raccourci pour créer des blocs facilement sous la souris.

    Ajout de la barre de zoom, permettant de zoomer et dézoomer avec précision dans la timeline.

    Des corrections et des ajouts en pagaille

    Difficile de décrire mon travail au quotidien : en dehors des grands chantiers abordés précédemment, mes journées consistent également en une myriade de petites tâches, de correction de bugs ou de crashs, toujours dans le but d’aider autant que possible les utilisateurs du moteur. Mais c’est aussi ça qui est intéressant : cela me donne l’occasion de découvrir et de naviguer dans toutes les couches de OEngine, et d’apprendre un peu plus chaque jour.

    Si je pouvais encore lister quelques tâches, j’ai notamment amélioré l’édition des objets d’UI :

    • Ajout des options pour renverser horizontalement et verticalement une image.
    • Ajout du centrage et de l’alignement à droite des textes sur plusieurs lignes.
    • Ajout du copier/coller des objets d’UI.
    • Amélioration de la sélection des objets d’UI dans la scène 3D, qui respecte la hiérarchie. Impossibilité de sélectionner un objet d’UI désactivé. Ajout de la sélection rectangulaire pour ces objets.
    • Simplification de l’utilisation des 9 slices.
    • Amélioration de l’undo/redo pour le déplacement des objets d’UI.

    En ce qui concerne l’interface de l’éditeur, j’ai ajouté un bouton permettant d’accéder rapidement à la documentation des composants, des game objects et autres fenêtres.

    J’ai aussi amélioré quelque peu la fenêtre de hiérarchie :

    • Ajout d’un scrolling automatique quand on glisse un élément.
    • Correction d’une fermeture intempestive des branches de la hiérarchie.
    • Amélioration de la recherche d’objets dans la hiérarchie, qui n’affichait pas toujours correctement les résultats.

    Enfin, il m’arrive aussi de nettoyer certaines parties du code : j’ai par exemple centralisé les fonctions de projection (world to view point, world to screen point, et inversement) dans une seule classe, pour plus de simplicité. Bref, on ne s’ennuie pas dans l’équipe de développement de OEngine !