Song of the Myrne: Où l'on parle de monstres et d'optimisation (le plus méchant des deux n'est pas celui qu'on croit)

Publié le 21 août 2013 par Beldarak @Beldarak
Salut les lecteurs !
Pas de changements incroyables de la mort qui tue ce soir, mais deux bonnes nouvelles:
  • La première c'est cette image:


Si vous suivez régulièrement ce blog, vous devriez savoir ce que ça signifie^^
...
...
J'utilise des atlas pour faire les mobs et je pourrai donc en créer beaucoup plus vite et facilement...
  • Et la deuxième bonne nouvelle c'est que je m'apprête à faire un truc qui devrait réduire pas mal les lags
En tout cas ça devrait ramener les performances au même niveau qu'avant la refonte graphique.
Voilà voilà, si les détails techniques ne vous intéressent pas, je vous invite à partir maintenant, parce que la suite de cet article risque de vous apprendre des trucs^^.
Mais comme je sais que quelques lecteurs utilisent Unity, je me dis que ça pourrait être sympa de leur éviter les deux jours de galère que je viens de traverser.

Les faits


Song of the Myrne est fait de pleeeeein de petits carrés (qu'on appelle quad). Sur ces quads sont appliquées des textures.
Alors tous ces petits carrés, ils ont beau être en 2D et avoir des textures de 8x8 ou 16x16 (en général), ça n’empêche que vu leurs nombres ils pèsent un peu lourd sur la carte graphique qui doit les afficher un par un (on appelle ça un Draw Call).
Si vous cherchiez sur qui râler quand votre démo de SotM lagge, ne cherchez plus, le nom du coupable principal est entouré^^
Heureusement il existe une technique fantastique que l'on appelle le batching. C'est grâce à cette technique que "la Baie des Bâtisseurs" (un petit jeu que j'ai fait) ne rame pas. Le principe est assez simple:
On prend plusieurs objets (ici nos petits carrés) qui resteront toujours immobiles et qui utilisent la même texture (le même material en fait, j'y reviens), par exemple tous les carrés de sable. Puis on les fusionne ensemble dans un seul gros objet (via un script). 
*PIF POUF MAGIE, ELECTRONIQUE ET BOTANIQUE*
Au lieu de 50 draw calls, on en a plus qu'un. La carte graphique ne doit plus passer sur chaque petit objet et lui appliquer la texture, elle passe sur le gros objet seulement et lui met la texture.
Bref, c'est ce que j'utilise et qui marchait très bien avant la refonte graphique.

Bardaf' c'est l'embardée


Qu'est-ce qui s'est passé concrètement lors de la refonte graphique du jeu ?
Au lieu d'avoir des petits carrés avec une texture dessus qui prend toute la place on a des carrés avec une très grosse textures. Pour les murs par exemple, l'atlas contient tous les cas possibles de ce type de mur: coin bas gauche, coin haut droit, ligne droite en bas,...

Et sur le quad n'est affichée qu'une partie de cette texture.
Alors quel est le problème? Tous mes carrés possèdent la même texture, donc le batching devrait marcher, non ?
La même texture oui, mais pas le même material ! Le material à la base est partagé entre tous les carrés, mais pour dire au mur quel partie de la texture il doit afficher, je dois changer le tiling (la taille de la partie affichée) et l'offset (le décalage) de la texture, ce qui se fait sur le material. Donc si je me contente de modifier le material de base, tous les murs possédant ce material afficheront la même partie de mur.
Donc je change ça au chargement d'un niveau et Unity crée un instance (une copie) du material qu'il peut modifier sans que ça affecte les autres quads qui ont eux aussi ce material. ils possèdent tous une copie du material original... 
Soit un material différent... Et donc le batching ne se fait plus, ce qui provoque une grosse chute de framerate.
Mais c'est horrible ! Que faire ?!

La solution


La solution est aussi bête que technique. Je ne vais pas me risquer à tout vous expliquer en détail, parce que pour remédier à ce problème j'ai dû toucher à mon Némésis. J'ai nommé les UV (même en informatique ils donnent le cancer :/).
L'UV map c'est ce qui dit à un modèle (dans ce cas-ci mes carrés) comment afficher une texture dessus. C'est exactement comme ce que vous obtenez quand vous découpez un cube pour en faire un objet plat.

Donc en gros, je caricature (à mort) mais, au lieu de faire glisser la texture sur mon carré, je fais glisser le carré sous la texture. Et pour le redimensionnement je préfère ne pas savoir ce qu'il se passe, moi l'UV mapping c'est un truc qui est renseigné dans mes gènes comme "truc magique qui ne marche jamais comme il faut".
Donc au final on a plusieurs objets différents (une copie du quad est créée) mais ils possèdent tous le même material, qu'on appelle sharedMaterial sous Unity. Ces objets seront batchés ensemble et les Draw Calls devraient être beaucoup moins nombreux qu'avant et réduire le lag (je touche du bois).
Edit: je confirme, on sent la différence, pour le moment je me contente de ramener le jeu à un état acceptable, c'est à dire que je ramène tout à 300 Draw Calls. Certains endroits sont actuellement à plus de 1000 ! Ce simple changement me fait gagner 10 images/seconde
Voilà. j'espère que toutes ces explications techniques vous auront intéressé (sinon vous n'êtes pas en train de lire cette phrase en fait^^).

Le bonus sympa


Si par le plus pur des hasards vous seriez amenés a devoir faire la transition tiling/Offset de material à modification d'UV map pour vos projets Unity, voilà la fonction que j'appelle à la place de mon ancien code:
http://pastebin.com/1wFJSqbQ

Conclusion


Il va sans doute me falloir encore un peu de temps pour appliquer ça aux murs, sols, etc et a réparer mes levels suite à pas mal de changements faits sur... quasiment tout. J'ai arrêté d'utiliser le plane de base d'Unity pour le remplacer par un quad fait sous Blender, parce que le plane d'Unity contient beaucoup trop de triangles, ce qui ne sert à rien et consomme inutilement de la mémoire.
Mais c'est promis, si tout fonctionne comme il faut, je sortirai une nouvelle version de la démo.