samedi 4 septembre 2010

Un shader pour les normal maps de Blender

Après une après-midi à me battre avec mon shader, j'ai enfin réussi à maîtriser la bête. Il reste encore beaucoup pour obtenir le shader ultime, mais au vu de la complexité du processus, je vais commencer par un petit tutorial simple sur la manière d'obtenir des normal maps dans OpenSceneGraph.

La plupart des shaders que l'on trouve sur le net sont des shaders en tangent space, est à dire qu'ils correspondent aux normales, ou au déplacement de normales, tangentiellement à la surface au point appliqué. L'intérêt de ces shaders est multiple, entre autres, ils fonctionnent sur des objets déformables, et ils peuvent être appliqués localement pour perturber les normales, permettant ainsi d'avoir par exemple une petite texture de sable appliquée à l'ensemble de la plage.

Je n'ai rien contre le tangent space, mais malheureusement, OSG et Blender si. Blender ne sait pas exporter les tangentes, et OSG ne sait pas les importer. Je pourrais en théorie les calculer pour chaque objet, nécessitant soit de créer mon propre format au sein d'OSG, ou d'aller méchamment tripatouiller les modèles. Mon plan est donc d'utiliser une alternative en attendant qu'une bonne âme fournisse un worflow Blender OSG complet.

En attendant, je m'intéresse aux normales en object space, qui sont très proches des normales aux faces. La différence, c'est qu'au lieu de définir une normale par face ou par vertex, on définit la normale dans la texture.

Démarrons donc Blender, et tentons de créer un objet qui ait une surface intéressante.



Supprimons le cube de départ, et ajoutons une bête UV sphère, que l'on étirera dans la direction z. Un petit coup de "Set smooth" pour faire joli. Et c'est maintenant que tout démarre. Ajoutons un material et sa texture. Je choisis par exemple du Voronoï:



De retour dans les matériaux, sélectionnons notre texture comme étant appliquée aux normales. Renforçons également la valeur "Nor" à 5, afin d'avoir un relief plus profond.



Il est maintenant temps de créer la texture. Sélectionnons l'objet, et déroulons sa texture: (Tab) pour passer en mode édition, (u) pour le dérouler, choisir "Unwrap - Smart projection", déselectionner "Stretch to boun" pour éviter les distorsions.



En changeant la fenêtre à "UV/Image editor", l'on peut voir notre modèle tout aplati. L'on peut maintenant créer l'image qui va contenir la texture, en choisissant dans ce menu (Image) -> (New). Allons y franchement pour ce modèle, avec une texture en 1024x1024.



Effectuons le rendu: dans le menu de rendu, choisir (Bake), sélectionner (Normals) pour rendre uniquement les normales, et changez le normal space vers (Object). Clic sur Bake, et voilà, la texture est complétée! Sauvez là, et admirez:



L'on remarque que les couleurs dominantes changent, car elles correspondent aux directions des normales depuis l'objet. Par comparaison, la texture en espace tangent ressemble à ça:



Ensuite, l'on peut exporter l'objet, par exemple au format Open Inventor (.iv).

J'ai créé une petite application qui me permet d'ajuster l'objet et les shaders en temps réel. Je charge l'objet, ajuste mes shaders, et voilà, un bel œuf de dinosaure!



Voici le code pour le vertex shader:


varying vec3 v;
varying vec3 n;
varying vec2 t0;

void main()
{
gl_Position = ftransform();
v = vec3(gl_ModelViewMatrix * gl_Vertex);
n = normalize(gl_Normal);
t0 = gl_MultiTexCoord0.xy;
}

et le fragment shader:

varying vec3 v;
varying vec3 n;
varying vec2 t0;

uniform sampler2D normalMap;

void main()
{
vec3 lightVec = normalize(gl_LightSource[0].position.xyz - v);
vec3 normal =
gl_NormalMatrix *
(vec3(-1, 1, -1) * normalize(texture2D(normalMap, t0).rgb * 2 - 1));
float diffuseTerm = max(dot(normal, lightVec), 0.0);
gl_FragColor = vec4(1, 1, 1, 1) * diffuseTerm;
}


Je vais maintenant affiner le shader:

  • Passage au multitexturing, pour avoir à la fois les normales et les couleurs

  • Gestion de la lumière spéculaire, pour permettre par exemple un rendu "mouillé"

  • Amélioration du pipeline, et résolution des quelques points bizarres, telle la multiplication par (-1, 1, -1)

Aucun commentaire: