CgFX - Des shaders temps réel dans le viewport Maya! - Part 5
Par Narann le samedi, 28 août 2010, 16:21 - Script et code - Lien permanent
La voila enfin, celle que vous attendiez depuis le début, celle que je vous promet depuis maintenant quatre chapitres, celle qui finit en beauté, celle qui va filer des migraines à tout ceux qui n'ont pas fait math sup' math spé' (moi au passage :baffed: ): Le bump mapping.
Autant le dire tout de suite, il va falloir s'accrocher un peut pour visualiser comment ça marche. Vous allez voir qu'une fois le "truc" pigé, les choses vont vous sembler plus simple, et vous pourrez passer à d'autres choses, plus compliquées, tout seul, comme un grand! :sourit: . Allez, on y va pour cette ultime partie!
Bon, il n'est pas vraiment utile que je remette une image de ce qu'on va obtenir... Si? Bon, ok... :youplaBoum:
Fini de rire, on attaque. :grenadelauncher:
Sommaire:
- Prérequis
- Théorie
- Les attributs
- Les structures
- Le vertex shader
- Le pixel shader
- Resultat!
- Hey mais c'est l'arnaque ton truc! C'est quoi ce bump pourrave là?!
- Ajout des attributs
- Conclusion
Prérequis
Si vous avez bien suivi le tuto, vous devriez avoir ceci:
Vous pouvez télécharger le shader ici ->cgfx_tuto_007.7z<-
Note: Si vous êtes équipé d'une carte ATI, je vous invite fortement à lire ce billet
Maintenant, le bump mapping!
Théorie (anagain, anagainanagain!) :mitraille:
Une fois n'est pas coutume, je ne trouve pas la définition de wikipedia très clair (un peu trop "large").
Dans notre cas, le bump mapping va consister à appliquer une texture de normal (normal map) sur un objet. Cette texture servira à faire varier les normales, au niveau du pixel. Je vais expliquer tout ça en détail.
La texture que nous allons utiliser est livrée avec le shader d'exemple de Nvidia de la partie 1 et s'appelle: Default_bump_normal.dds:
Mais tout d'abords, quoi-qu'-c'est-qu'-ce-truc-de-normal-fack? :nevroz:
Comme vous le savez, chaque vertex de notre objet a une normale. Pour afficher celles de votre objet, allez dans Display/Polygons/Vertex Normals:
Vous pouvez modifier leurs tailles via:
Ces "traits" verts que vous voyez sont en fait des vecteurs XYZ qui indiquent la direction dans laquelle est placée les vertex. Si vous avez compris ça, c'est déjà pas mal. :sourit:
La question que l'on se pose naturellement c'est: "Ok, je connais la direction de chaque vertex, mais comment connaitre la direction de ce qu'il y a "entre" les vertex?
Et bien on interpole entre les vertex, tout simplement! :siffle:
Là, on rentre dans des problématiques de temps réel "pur". En effet, Pour récupérer la normale au niveau de chaque pixel, nous allons nous servir du... Vertex shader.
Il faut que vous sachiez qu'entre le Vertex shader et le Pixel shader se produit une étape: La rasterisation. Cette étape comporte pas mal de choses (dont on ne parlera pas ici) mais surtout, cette étape interpole, au niveau de chaque pixel, les valeurs stockées dans le Vertex shader (que ce soit de la couleur, des positions, des normales...)
Pardonnez mon minable exemple mais c'est pour visualiser...Ici, toute la sphère est bleue. Seul un vertex est rouge. Mais les valeurs numériques qui définissent les couleurs de chaque vertex (RGB) sont interpolées le long des arêtes, entre chaque vertex qui l'entoure.
Cas identique avec deux couleurs:
Pour la normale du vertex, le principe est le même. Chaque valeur de la normal (vecteur XYZ) est interpolée le long de l'arête vers la normal suivante.
Une fois la rasterisation terminée, vous avez des valeurs de normal (XYZ) au niveau de chaque pixel.
Ayez, vous commencez à saisir le truc? :sourit:
Et oui, c'est maintenant qu'intervient la normal map. Une normal map, c'est, comme son nom l'indique, une texture qui stocke des vecteurs de normal. Chaque pixel (couleur RGB) correspond à un axe (XYZ), ce qui donne cette teinte si particulière aux normal maps.
Un vecteur 0,0,0 est équivalent en RGB à 128, 128, 128. A partir de là, une normale "standard" (0,0,1, Z étant face la profondeur de la map) est 128, 128, 255. Soit un espèce de mauve dégueulasse. :pasClasse:
Bon, à partir d'ici vous avez compris le truc. Pour résumer, il va falloir additionner les valeurs XYZ de la normale du pixel avec la normal de la normal map.
Les attributs
Comme d'hab', on créé les attributs.
//Bump float bumpPower < string UIName = "Bump Strength"; string UIWidget = "slider"; float UIMin = 0.0; float UIMax = 3.0; float UIStep = 0.001; > = 1.0;
Puis on créé la texture de bump.
texture normalTexture < string ResourceName = "default_bump_normal.dds"; string UIName = "Normal-Map Texture"; string ResourceType = "2D"; >; sampler2D normalSampler = sampler_state { Texture = <normalTexture>; MinFilter = LinearMipMapLinear; MagFilter = Linear; WrapS = Repeat; WrapT = Repeat; MaxAnisotropy = 16.0; };
Les structures
Vous devez aussi modifier la structure d'entrée pour stocker les valeurs de tangents et de "binormals" (je ne sais pas exactement ce que c'est... :jdicajdirien: ).
struct vIN { float3 Position : POSITION; // La position du vertex en entrée float4 Normal : NORMAL; // La normal du vertex en entrée float4 UV : TEXCOORD0; // un set d'uv float4 Tangent : TEXCOORD1; float4 Binormal : TEXCOORD2; };
Le vertex shader
On multiplie les matrices de tangent et binormal par la "world view matrice inversé" (qui, si je me rappelle, projette ces informations sur l'espace écran).
OUT.WorldTangent = mul(wvMatrixIT, IN.Tangent).xyz; OUT.WorldBinormal = mul(wvMatrixIT, IN.Binormal).xyz;
Ça, c'est fait! :hehe:
Le pixel shader
C'est là que c'est le gros morceau...
On commence simplement par une normalisation en bon et due forme des vecteurs:
// On normalise les vecteurs float3 Nn = normalize(IN.WorldNormal); float3 pL0n = normalize(IN.PointLight0Vec); float3 pL1n = normalize(IN.PointLight1Vec); float3 Vn = normalize(IN.WorldView); float3 Tn = normalize(IN.WorldTangent); float3 Bn = normalize(IN.WorldBinormal);
La on a un gros paquet de vecteurs normalisés avec lesquels on va faire plein de cochonnerie... :baffed:
Comme dis plus haut, on va modifier la normale (le vecteur Nn) au pixel, grâce a la texture de normal map. J'ai trouvé plusieurs exemples sur le net mais ils étaient tous différents... J'ai donc pris celui qui me semblait le plus simple et qui marchait (chez moi :seSentCon: )...
// Modifie la normale avec le bump float3 bump = tex2D(normalSampler, IN.UV).rgb; bump -= float3(0.5, 0.5, 0.5); //On décale de 0.5 les valeurs de normal half3 wNormal; wNormal = bump.x * Tn*bumpPower; wNormal += bump.y * Bn*bumpPower; wNormal += bump.z * Nn; Nn = normalize(wNormal);
La première ligne:
float3 bump = tex2D(normalSampler, IN.UV).rgb;
On récupère la couleur de la texture au pixel.
bump -= float3(0.5, 0.5, 0.5); //On décale de 0.5 les valeurs de normal
Ensuite on décale les valeurs de normal de 0.5 (je ne sais pas trop pourquoi en fait... Surement car dans une normal map, un vecteur de 0 équivaut à 128,128,128).
half3 wNormal; wNormal = bump.x * Tn*bumpPower; wNormal += bump.y * Bn*bumpPower; wNormal += bump.z * Nn;
Et la c'est le pavé. J'ai pas mal galéré pour le "sortir". :pasClasse:
Bon, on créer un vecteur qui est la normal projetée sur l'écran (wNormal). On multiplie la composante rouge (X) par la normal de la tangente. Idem pour Y et la binormal et la normal de base (Nn). En revanche, on ne multiplie que le coefficient de bump que par la composante X et Y. Je suppose que la logique derrière est qu'il n'y aurait pas vraiment de sens à multiplier la profondeur (Z) par le coefficient de bump.
Resultat! :marioCours:
Si vous avez bien tout suivi, vous devriez obtenir quelque chose comme ça:
Si ce n'est pas le cas et que vous ne comprenez pas pourquoi, n'hésitez pas à me le dire, j'ai peut-être oublié quelque chose... :siffle:
Vous pouvez télécharger le shader ici: ->cgfx_tuto_008.7z<-
Hey mais c'est l'arnaque ton truc! C'est quoi ce bump pourrave là?!
Bien observé! :sourit:
Vous aurez peut être remarqué en bougeant les pointLights (parce que bien sûr, vous avez bidouillé un peu hein? Vous attendez pas que ça vous tombe tout cuit dans le bec non plus! Les jeunes aujourd'hui... :papi: ), que le bump réagit bizarrement:
C'est exact! En fait, il peut arriver que la composante rouge ou verte de votre normal map soit inversée (salaud!). Pour inverser la composante rouge et/ou verte, nous allons créer un attribut qui nous permettra de switcher "l'orientation" des couleurs Rouge et Verte.
Ajout des attributs
Et bien oui! On ajoute encore des attributs:
bool bReverseRedNormal < string UIName = "Reverse Red Normal"; string UIWidget = "RadioButton"; > = false; bool bReverseGreenNormal < string UIName = "Reverse Green Normal"; string UIWidget = "RadioButton"; > = false;
Et dans la partie "modification de la normale":
half3 wNormal; wNormal = bump.x * Tn * (1+(-2*bReverseRedNormal)) * bumpPower; wNormal += bump.y * Bn * (1+(-2*bReverseGreenNormal)) * bumpPower; wNormal += bump.z * Nn; Nn = normalize(wNormal);
Ne paniquez pas, vous allez voir que c'est en fait assez simple à comprendre. :sourit:
Ce que je veux c'est que:
- Quand bReverseRedNormal est coché (et donc, quand il est à "1"), j'ai un multiplicateur de "1".
- Quand bReverseRedNormal est décoché (valeur à "0"), j'ai un multiplicateur de "-1".
f(x) = 1+(-2*x) f(0) = 1+(-2*0) = 1 f(1) = 1+(-2*1) = 1-2 = -1
Cette petite gymnastique évite de jouer avec les "if" (quoique, ça serait peut être plus rapide... Mais pour une fois que je fais des maths). Il y avait surement moyen de faire plus simple et plus optimisé mais pour tout dire, je n'ai pas cherché (mais je vous invite à me corriger! :baffed: )
Grâce à ce "trick", nous pouvons contrôler l'orientation des composantes Rouge et Verte de notre normal map:
Vous pouvez télécharger le shader ici: ->cgfx_tuto_009.7z<-
Conclusion
Et voila, ce tuto sur les shaders CgFX est enfin terminé. J'espère avoir été assez clair. Que vous avez plus ou moins compris des choses et que ça vous sera utile durant vos productions. :jdicajdirien:
N'hésitez pas à me laisser un commentaire si j'ai foiré quelque chose (code, fichiers) ou si vous trouvez d'autres cas d'utilisation de shader CgFX (ou GLSL, HLSL, etc...) appliqué à notre métier.
A très bientôt pour de nouvelles aventures!