Je spécule un scapulaire

J’ai décidé de me pencher sur la question du spéculaire, c’est à dire ajouter un éclat de lumière à certains pixels. Quand on regarde ce que l’on a déjà fait et ce que l’on a trouvé sur le net cela semble facile, du coup, challenge !

Une fois que l’on a calculé la valeur de notre pixel, on a la valeur diffuse, ce qui sera la couleur affichée. Sur base des données que l’on a déjà, comme la lumière (position, vecteur), la normale de notre pixel et l’atténuation (facteur de notre lumière), alors il nous suffit d’une petite formule.

On crée un vecteur (vec3) représentant le point de vue (le nôtre), on le combine au vecteur de la lumière, on normalise, on fait le produit scalaire avec la normale du pixel, on restreint la valeur dans l’intervalle [0, 1] et on l’augmente par un exposant (32, qui peut être de 2 à n suivant l’effet voulu), enfin on l’ajoute à la couleur de manière équivalente aux 3 couleurs RGB pour le tendre vers le blanc, mais on pourrait appliquer un modificateur suivant cette documentation d’Unreal Engine.

Le résultat est blanc ! Sans dégradé ni distinguo, juste blanc ! Là commence une bataille en heures sur des variantes de la formule ou des valeurs des vecteurs. Je vous épargne, c’était « impossible », dans le sens où encore une fois, notre contexte n’est pas le contexte classique 3D et que mes notions de super matheux sont fictives.

Ce blanc est dû à un vecteur, celui du point de vue, et selon ce que l’on met, c’est tout noir ou tout blanc, et comme j’ai chipoté longtemps sans versionner les résultats, je ne peux que vous le raconter. Ensuite j’ai recomposé la couleur diffuse et isolé le spéculaire. Notre ennemi c’est Z, il est imposé à 1 et nous ne gérons pas la hauteur relative actuellement, donc tout est à 1 mais du coup seule la face de « sol » (les normales 0,0,1 soit bleues) sont violemment surchargées.

Démonstration de l’effet spéculaire incorrect pour les normales verticales 0,0,1/bleue

Comme pour la couleur diffuse il nous faudra manipuler le concept. J’en ai été à lire au sujet des produits scalaire et normalisation de vecteur et refaire les calculs à la main, ce qui a aidé, un peu quand même. Ce que j’ai obtenu est un nombre toujours en dessous de 1 (et > 0) ce qui fait qu’élever un 0,x à la puissance 32, le réduira toujours plus (0,0…00x).

La solution, remultiplier ce résultat par 255 pour le renvoyer dans l’espace de valeurs RGB. C’est empirique, mais le résultat donne quelque chose d’intéressant, donc pour une fois qu’on approche d’un truc passable…

Spéculaire sur le coffre, sur sa partie « métallique »

J’en ai profité pour faire quelques textures de normales, ce qui prends, à la main et par pixel, un temps fou. Et vu qu’on fait des modifications de shader et du système de rendu, pourquoi ne pas faire un truc qui n’était pas dans la liste et ajouter une texture de spéculaire, c’est-à-dire une image représentant les zones où appliquer l’effet spéculaire à notre image. Par exemple sur le coffre, ne prendre que les parties métallique.

Ainsi j’ai créé cette image, en utilisant la couche rouge qui peut varier de 0 à 255, ce qui se ramène à une valeur [0,1] permettant de multiplier le résultat spéculaire calculé, tel un facteur spécifique au pixel.

Ainsi, en une seule texture additionnelle et en utilisant les couches RGB de l’image, ainsi que l’alpha (transparence), nous pouvons stocker jusqu’à 4 valeurs permettant d’ajouter des modificateurs visuels à notre texture de base, tel que l’information de hauteur qui nous manque, le spéculaire dont on a parlé, peut-être une information d’ombrage, plutôt que de l’avoir dans l’image rendue, telle qu’actuellement. C’est à voir et investiguer mais ça reste intéressant.

Évidemment charger une énième texture méritait une factorisation côté sprite. On ajoute un type de texture (diffuse, normale, speculaire) en enum et on rend tout le monde meilleur. C’est encore fort « recherche et développement », pas très fini et très améliorable, tout est discutable, mais le résultat apparait 🙂 et ça, c’est bien !

Dernière amélioration, qui se voit sur les murs, ceux en position Est ou Sud, c’est à dire ceux que l’on voit mais qui n’appartiennent pas à notre bloc courant, mais également ceux en Nord et Ouest, mais cette fois sur le bloc courant, ces 4 cas, ne recevaient pas la lumière adéquatement.

Dans le cas du mur à gauche de la porte au début de la vidéo (donc un mur Est, sur le bloc x:1,y:3) on marche à côté sur x:2,y(2,3,4). Le mur était noir car la source de lumière partait de x:2,y3 (par exemple si on est à droite du mur avant la porte), le chemin de la lumière passait à x2:y4, x1:y:4, x1:y:3 et comme c’est un cas Est (ou Sud), ceux-ci ne prennent pas la lumière car « ils tournent le dos » à la case. Sauf que si je suis à côté du mur, d’une case adjacente, je m’attends à ce qu’il reçoive la lumière.

Dans le cas du mur Nord ou Ouest (le mur rouge face au coffre par exemple), vers la fin de la vidéo, coffre fermé, le squelette marche devant, si nous sommes dessus, le mur est noir, du fait que l’origine de la lumière et le mur sont sur la même position et annulent par conséquent leurs vecteurs.

La solution a été de déplacer virtuellement la coordonnée de la lumière par rapport à la zone du bloc occupée (N, S, E, O), ainsi l’effet est correcte.

On est loin d’avoir fini, il reste des textures de normales à faire (et c’est looong), des affinages sur le shader (encore et toujours), et sinon le reste de la liste :

  • Ajouter le tri des lumières, sur l’intensité j’imagine dans un premier temps, et réfléchir au cumul/fusion ensuite,
  • L’axe Z, les hauteurs et peut être les occlusions (on peut rêver),
  • L’éclairage global de la scène (facultatif), c’est déjà elle qui pilote, donc ça devrait être facile.