Des aNormales

Suite à toute cette énergie de réactivation sur TARS, j’en suis venu à me remettre à rêver et à me demander quels résultats j’aimerai présenter à d’éventuels joueurs (un jour).

Nous sommes dans un univers 2D à l’apparence 3D, donc nous appliquons des principes, des formules, propre à l’univers 2D pour afficher un monde qui se veut 3D. Cependant, et j’en ai déjà parlé précédemment, la lumière est un calvaire. Alors vous me direz que les autres s’en sortent plutôt bien, certes, mais ils « trichent intelligemment », avec un décors en un morceau et plat sur lequel on peut ajouter les éléments qui vont vous immerger (ombre plate rotative sur sol par exemple ou effet de lumière par un calque d’obscurité); alors que TARS se veut proposer une gestion de la hauteur [Z] ce qui est super casse-couille on ne va pas se mentir, mais permet de donner beaucoup plus cette impression de « vraie » 3D.

J’ai donc repensé à mon éclairage et à notre élément de bloc :

Le bloc test

L’image, et c’est important, est rendue uniformément sur base de données calculées au changement dans la scène (déplacement, mouvement de caméra), c’est à dire que virtuellement on va tenir compte d’éléments éclairant, calculer qui est soumis à ces lumières et appliquer un modificateur de couleur, in fine. S’en suit une application par le shader qui va prendre cette information et la mélanger à la couleur d’origine de chaque pixel, en gros.

MAIS, les flancs de ce bloc, ou les côtés des arbres, coffre, murs ou tables, sont éclairés de la même façon que le sol ou le reste de l’image, on n’augmente pas le relief du bloc lui-même mais plutôt sur une vue d’ensemble de la scène.

L’idée est donc de renforcer la notion 3D de l’élément, son volume que l’on devrait percevoir. On le « lit » grâce aux lignes de l’image, notre cerveau comprend la forme, son relief, mais son éclairage est plat. Ce problème existe depuis de nombreuses années dans l’univers des jeux 3D, rappelez vous ces murs plats à la texture de mur, lisse alors que l’image nous indique un relief. Je ne vais pas vous révéler un grand scoop, mais depuis on utilise différentes techniques, comme le displacement mapping ou, dans notre cas, le normal mapping.

Le but du normal mapping est de pouvoir prendre une surface texturée et de vous donner l’impression, par son éclairage, qu’il est en relief. Une application serait de prendre un modèle très détaillé et donc très couteux au rendu, de faire une texture détaillée, de simplifier le maillage drastiquement, et d’appliquer cette texture détaillée, là on a un modèle simple avec un beau niveau de détails, une belle économie de calcul, mais la lumière ? Comment ce modèle va-t-il être éclairé ? Grâce à la texture détaillée, elle donnera, après traitement, des informations permettant de savoir dans quelle direction chaque pixel est orienté, la normal map; et au moment du rendu, grâce à un shader, la texture détaillée sera éclairée pixel par pixel correctement, le résultat sera bluffant et rapide. Pour les détails, internet regorge de ressources à ce sujet, ici je synthétise :).

Ce que la théorie nous apprends sur les textures de normales et leurs directions (source OpenGameArt)
La texture de normales de notre bloc

Alors moquez vous, c’est fait avec paint car je n’avais rien d’autre sous la main. Et, pour accélérer l’article et son mystère, vous l’aurez vite remarqué mais ça n’a pas l’air de coller à la théorie (on y reviendra, moment de solitude). En lisant la théorie vous lirez que l’on va utiliser les valeurs RGB de chaque pixel pour donner une direction spatiale au pixel (son vecteur, sa normale), RGB devient XYZ (la suite plus tard), donc j’ai transposé la théorie à mon plan isométrique, et avec mon cube la couleur est sans appel, on a un X pur (255,0,0 = rouge) sur le franc droite, un Y pur (0,255,0 = vert) sur le flanc gauche et un Z bien plat au dessus (0,0,255 = bleu).

On modifie le code pour charger, stocker, référencer et définir notre texture de normales et l’envoyer jusqu’à notre shader existant (ça a déjà pris pas mal de boulot).

Premier essai de contrôle pour voir que la texture est bien là

Mouai, il y a une couille dans le pâté. Les objets « non cube de sol » ont la texture aussi. Vous le verrez à la déformation (les arbres). Sinon on voit que le coffre éclaire toujours en rouge (la petite part de tarte rouge et bleue).

Premier essai en ne tenant pas compte des objets qui n’ont pas de texture de normales.

Maintenant que j’ai les bonnes infos (si seulement) je me dit qu’on peut tenter d’appliquer ce que les grands esprits ont fait avant moi, je vous épargne le code et les modifs TARS.

Premier essai de rendu avec normales

On a donc une scène qui ne veut pas prendre les flancs des blocs en compte et cumule trop de lumière avec un assombrissement au niveau du coffre qui est étrange. Le reste est noir, non impacté par une lumière, et là on se dit qu’une lumière globale serait intéressant, mais ça ne l’est que dans l’esprit de la scène (un extérieur boisé), donc à laisser à la discrétion de chaque scène.

On isole tout et on essaye de comprendre ce que l’on a

Je retire tout, je laisse le sol et notre héro porteur de la lumière (voq) et on va tenter de manipuler notre shader pour vérifier, car on ne peut pas débugger un shader « pas à pas », malheureusement.

On remet le coffre pour valider la seconde source de lumière
On discrimine les affectés pour afficher et contrôler la portée des effets

Bon, qu’est-ce qu’on essaye de faire ? On a une liste de lumières qui affecte l’éclairage et la couleur de chaque pixel d’un élément rendu (ici notre bloc), ce qui se calcul avec la valeur normalisée de notre pixel de la texture de normales (à la même position bien sur) que l’on veut comparer au vecteur de la source lumineuse (une à une) pour en déduire l’impacte d’éclairage (un peu, beaucoup, passionnément, …) et ainsi composer, lumière après lumière, la couleur finale de notre pixel venant de la texture. Ce sont les normales de la texture de normales qui donneront l’effet de variation d’intensité des flancs.

La théorie 3D nous parle de matrice de l’univers, mais nous sommes à plat, et donc on ne peut pas appliquer simplement la formule, on doit la tordre, et je ne suis pas matheux, alors ça se complique. De plus, comme dit plus haut, on a fait une erreur, notre image représente une dimension isométrique et la texture de normal a été faite en ce sens, après tout, comparer des vecteurs ça devrait rester équivalent. Encore un point qui ne nous aide pas avec les formules (voir les sources en fin d’articles pour les curieux).

Différents tests et autant d’échecs. J’avoue, là, j’ai fait une pause de 2 jours après 2 jours dessus. Il me manque un truc mais je ne sais pas quoi. Les couleurs sont violentes ou noires, les flancs ne se colorient pas comme attendu, tout disparait (noir), … Foutu vecteurs.

Troisième essai où l’impacte des flancs se montre

On arrive enfin à quelque chose, mais en contre partie, les objets sans texture de normales sont noirs, peu grave, ça viendra plus tard. Le truc ? Tout reprendre et reconstruire le shader avec les 2 principes communs aux articles parcouru : normalisation et produit scalaire.

Pour faire simple, on définit notre pixel de texture et notre pixel de la texture de normales (texel), on a 2 vec3 (rgb), on normalise le texel, on crée un vec3 à 0.0 pour notre couleur résultante (donc noir par défaut, cf. la mise en noir cité plus haut), on boucle sur la liste des lumières et pour chaque on calcule un facteur lumière qui est le résultat du produit scalaire de notre normale et de la position de la lumière par rapport au bloc, ce qui nous donne un coefficient que l’on va utiliser pour multiplier le pixel de la texture, la couleur de la lumière, la force de notre lumière avec ce coefficient, ceci ajouté à notre vec3 couleur. Au final on indique d’utiliser ce résultat sans oublier l’alpha de la texture d’origine.

Comme vous le voyez ça clignote, c’est dû à l’ancienne manière de faire le passage de la lumière d’un tile vers un autre, en passant un delta de 0 à 50% jusqu’à changer de tile, pour se faire on ajoute un seconde lumière pour la différence du delta sur l’autre tile. Ce qui nous amène à constater ce qu’il nous manque encore :

  • Transformer le delta et l’envoyer au shader pour le gérer là, sinon nous en revenons au changement sec d’éclairage de tile pendant le déplacement, au lieu du côté doux que l’on a développé.
  • Gérer la hauteur de l’éclairage (Z), mais cela va demander à TARS de passer l’information et d’en tenir compte.
  • J’ai pensé à l’occlusion et au barrage mais ça me parait compliqué à ce stade.
  • Ajouter un éclairage global ? Ça peut-être intéressant pour sa différence.
  • Créer les textures de normales pour les autres objets.
  • Un des articles parle de la lumière spéculaire et je me demande si nous saurions l’ajouter nous aussi.

Voilà donc la semaine de travail sur les normales, avec, heureusement, un résultat encourageant.

Sources