CgFX - Des shaders temps réel dans le viewport Maya! - Part 3
Par Narann le mardi, 27 juillet 2010, 20:50 - Script et code - Lien permanent
Après pas mal de retard (boulot, etc...), je vous propose de découvrir la troisième partie de ma série de tutos sur le langage CgFX.
Ici nous allons ajouter une couleur de base à notre shader (la diffuse), de l'ambiant, ainsi qu'un effet très intéressant pour déboucher les bords: Le falloff. Avec cet effet, vous pourrez déjà donner un look sympa à vos playblasts alors n'hésitez pas à expérimenter. Je vous invite à partager vos liens et créations dans les commentaires. :youplaBoum:
Sommaire:
Ajout de la diffuse
Actuellement, le shader n'a aucune couleur. Il se contente d'additionner les couleurs des deux pointLights (en fait, il se comporte comme si il était blanc). :reflechi:
Pour ajouter une couleur de base (que j'appellerai "diffuse"), il faut déjà ajouter les paramètres de diffusion (Ici, une couleur et un coefficient):
// Diffuse float3 diffuseColor < string type = "color"; string UIName = "Diffuse Color"; > = {0.07, 0.07, 0.07}; float diffuseFactor < string UIName = "Diffuse Factor"; string UIWidget = "slider"; float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.001; > = 1;
Passons au calcul de la diffuse. C'est assez simple. Dans le "program" du Pixel Shader (MainPS()):
//Calcul la valeur de la couleur de la Diffuse float3 diffuseColorResult = diffuseColor.rgb; // On la rend de la couleur de nos paramètres diffuseColorResult *= diffuseFactor; // Et on multiplie le tout par le coefficient
Maintenant que nous avons calculé la valeur de diffuseColorResult, allons à la ligne de résultat:
float3 result = illumPointLight0 + illumPointLight1;
Et multiplions le tout par notre diffuseColorResult:
float3 result = (illumPointLight0 + illumPointLight1) * diffuseColorResult;
Voila, c'est un peu "bizarre" l'effet (car parfait) mais ne vous inquiétez pas, quand on passera aux textures, ça sera tout de suite plus joli! ^^
Maintenant, ajoutons de l'ambiant.
Ajout de l'ambiant
Le principe de l'ambiant, c'est d'ajouter de l'illumination. C'est donc une addition mais uniquement sur l'illumination, pas sur le résultat total.
Si on "ajoutait" au résultat total, ça serait l'équivalent de l'incandescence dans Maya (ou Additional Color dans un shader mia_x). L'effet ne serait pas du tout le même!
C'est tellement simple que vous pourriez le faire vous-même:
// Ambiant float3 ambiantColor // On créé l'attribut < string type = "color"; string UIName = "Ambient Color"; > = {0.07, 0.07, 0.07};
float3 result = (illumPointLight0 + illumPointLight1 + ambiantColor) * diffuseColorResult;
Encore une fois, si vous avez des doutes, essayez par vous même d'ajouter l'ambiant au total.. ^^ (Une modif, un save, un reload et on a le résultat direct! Profitez-en pour expérimenter!)
Vous verrez de suite la subtilité. :sourit:
Ajout du falloff
Maintenant nous allons attaquer un effet que je trouve très intéressant (et qui est particulièrement à la mode dans le jeux vidéo): Le falloff
Que serait une intro sans une image... (tiré de la galerie du thread: Wii emulator can do 720p HD)
Voyez l'effet sur la casquette et le pantalon, ça débouche les contours, et bien c'est ce que nous allons faire! :sourit:
Ajout de la matrice et modification de la strucure
Et c'est maintenant que ça ce complique. Vous vous rappelez comment on à fait notre illumination à partir des deux pointLights? Et bien grossièrement ici, c'est la même chose, sauf que notre pointLight, c'est la "view", la camera... On éclaire par la caméra, mais en négatif... :zinzin:
Nous allons dans un premier temps, ajouter, tout en haut du fichier, une matrice appelée ViewInverse. (Je ne suis pas trop calé sur les différentes matrices de CgFX. Je ne sais donc pas ce qu'elles représentent mathématiquement. Cela dit, j'ai trouvé une doc intéressante sur les matrices utilisées en temps réel: http://code.google.com/intl/fr-FR/a... Cette doc n'est pas spécifique au langage CgFX.):
float4x4 viMatrix : ViewInverse;
Puis on ajoute un paramètre "WorldView" à notre structure vOUT:
struct vOUT { float4 Position : POSITION; float3 PointLight0Vec : TEXCOORD0; float3 PointLight1Vec : TEXCOORD1; float3 WorldNormal : TEXCOORD2; float3 WorldView : TEXCOORD3; // ajout d'une variable à la structure };
Modification du Vexter Shader
Là c'est bcp moins classe: Dans MainVS(), on "copie-colle" le morceau de code que j'ai moi même copié-collé d'un des shaders de NVidia... :nevroz:
OUT.WorldView = normalize(float3(viMatrix[0].w,viMatrix[1].w,viMatrix[2].w) - PositionWorld.xyz);
Je suis incapable d'expliquer ce que ça fait ormis que ça remplit votre "OUT.WorldView" bien comme il faut! :sourit:
Notre WorldView est maintenant remplit par le Vertex Shader: MainVS().
Modification du Pixel Shader
Passons a MainPS(), on normalise notre valeur (comme les autres):
float3 Vn = normalize(IN.WorldView);
Et on s'en sert pour générer le falloff:
//FallOff float ndv = dot(Nn,Vn);
A ce stade vous pouvez essayer de n'afficher "que" la valeur de ndv dans votre shader pour voir le résultat:
float3 result = ndv;
On y est presque on dirait! :sourit:
Ajout des attributs
On va ajouter deux paramètres:
- Un pour contrôler la visibilité (opacité) du falloff (Un float de 0 à 1)
- L'autre pour contrôler "sa compression"
Le tout, calé en dessous de l'ambiant. Ce qui donne, pour les attributs:
// FallOff float fallOffCompression < string UIName = "FallOff Compression"; string UIWidget = "slider"; float UIMin = 0.0; float UIMax = 10.0; float UIStep = 0.001; > = 1.0; float ambiantFallOffFactor < string UIName = "Ambient FallOff Factor"; string UIWidget = "slider"; float UIMin = 0.0; float UIMax = 1.0; float UIStep = 0.001; > = 1;
Voyez que j'ai appelé le coefficient: "Ambient FallOff Factor". Vous verrez la relation entre l'Ambiant et le Falloff plus tard...
Retour au Pixel Shader
Maintenant retournons dans notre program MainPS() et on ajoute:
//FallOff float ndv = dot(Nn,Vn); float fallOffRamp = pow(ndv, fallOffCompression);
En fait c'est comme si on faisait ndv^fallOffCompression: Si votre fallOffCompression = 2, alors nous auront ndv au carré!
C'est ce fallOffRamp qui va être multiplié au ambiantFallOffFactor. Je vous propose, pour bien voir le résultat de le faire de suite:
float3 result = fallOffRamp * ambiantFallOffFactor;
Amusez vous avec les paramètres, change le "result", bidouillez! Il n'y a que comme ça que je suis arrivé et je vous invite à le faire!
Pour inverser la couleur (pour avoir du blanc sur les contours):
float fallOffRamp = pow(1-ndv, fallOffCompression);
Dilemme de l'ambiant et du falloff
A ce stade nous avons, un falloff dont nous contrôlons "la compression" et l'opacité. L'idée est que ce falloff agisse de la même façon que l'ambiant, à savoir, comme un ajout à l'illumination.
Simple me diriez vous. Il suffit de faire ça:
float3 result = (illumPointLight0 + illumPointLight1 + fallOffRamp * ambiantFallOffFactor) * diffuseColorResult; // Vous n'oubliez pas quelque chose?
Oui... Et ambiantColor on en fait quoi? :sourit:
Je m'explique: Il nous faut s'arranger pour que les valeurs d'ambiant et de falloff ne fassent plus qu'une. Mais essayez de visualisez ce que nous avons:
- Une ambiant (ambiantColor), qui est donc juste une couleur.
- Un falloff (fallOffRamp * ambiantFallOffFactor), dont vous voyez très bien à quoi ça ressemble.
Si nous additionnons bêtement les valeurs, cela donnera quelque chose d'un peu chiant à régler.
Essayez (comme toujours...):
float3 result = ambiantColor + fallOffRamp * ambiantFallOffFactor;
Et jouez avec les paramètres. Vous allez vite vous rendre compte du problème. :reflechi:
De même qui si nous les multiplions, l'ambiant jouera le rôle d'un "simple" coefficient:
float3 result = ambiantColor * fallOffRamp * ambiantFallOffFactor;
Ici, ambiantColor et ambiantFallOffFactor jouent le même role au final... Donc inutile.
Encore une fois, petite prise de tête en perspective... :siffle:
Simplifions un peu: Ce qui serait bien, ça serait que ambiantFallOffFactor soit une sort de "switch" entre l'ambiantColor color et le fallOffRamp:
- Si ambiantFallOffFactor = 0, alors c'est l'ambiantColor qui à le dessus (ambiantColor*1 et fallOffRamp*0).
- Si ambiantFallOffFactor = 1, alors c'est la fallOffRamp qui est à fond (ambiantColor*0 et fallOffRamp*1).
Enoncé comme ça, ça doit commencer à vous parler un peu...
J'ai mis pas mal de temps à trouver la combine mais la voici:
//Ambiant float3 AmbiResult = ambiantColor * (1-ambiantFallOffFactor) + fallOffRamp * ambiantFallOffFactor;
Regardez bien cette ligne (Vous -> :gne: )et comparé là à ce que j'ai dis juste au dessus, ça devrait faire tilte! Une fois qu'on a compris le raisonnement, ça saute aux yeux!
Ca marche! Modifiez et faites vos tests: Le ambiantFallOffFactor fait bien office de "switch".
On y est presque! Le dernier souci c'est concernant la couleur. En effet, rien pour l'instant ne permet de choisir la couleur du falloff...
Il y a plusieurs façons de procéder. La plus simple serait de créer un autre paramètre que vous pouvez appeler fallOffColor et le multiplier (plus haut) à la fallOffRamp.
Je n'ai pas choisi cette option et je vais vous expliquer pourquoi :redface: :
Pour moi, le principe est que le falloff ne fait que régir le comportement de l'ambiant:
- Si il est désactivé (ambiantFallOffFactor = 0), nous avons une ambiant "de base": Une addition linéaire ambiantColor sur l'illumination.
- Si il est activé (ambiantFallOffFactor = 0.5), nous avons une ambiant qui se "falloffise", mais qui doit conserver la couleur de l'ambiant...
Et pour faire ça, on multiplie ambiantColor au falloff:
//Ambiant float3 AmbiResult = ambiantColor * (1-ambiantFallOffFactor) + ambiantColor * fallOffRamp * ambiantFallOffFactor;
Mais dites donc! Ça ne vous rappelle rien? Mais si! Vos anciens cours de math! Nous avons ambiantColor qui est multiplié deux fois... Il faut Factoriser! :baffed:
//Ambiant float3 AmbiResult = ambiantColor * ((1-ambiantFallOffFactor) + fallOffRamp * ambiantFallOffFactor);
Testez avec:
float3 result = AmbiResult;
En gros, quand l'ambiant est à fond mais que l'ambiantFallOffFactor est à fond aussi, c'est le falloff qui prend le dessus:
Par contre, si l'ambiantFallOffFactor est nul, aucun falloff pour le shader, c'est l'ambiant color qui prend le dessus:
Le tout, avec une belle transition, propre, et colorée!
Et pour finir, on réécrit le "result" en ajoutant AmbiResult au reste de l'illumination:
float3 result = (illumPointLight0 + illumPointLight1 + AmbiResult) * diffuseColorResult;
Il commence à y avoir un petit effet de matière intéressant :sourit: .
Pour télécharger le shader -> cgfx_tuto_005.7z
Conclusion
Je m'arrête ici pour cette troisième partie. Je sais que le plus intéressant (texture et bump) n'a pas été abordé mais la prochaine partie sera dédiée aux textures :sauteJoie: .
En attendant, n'hésitez pas à laisser un commentaire si vous avez des questions et/ou suggestions.
A bientôt!
Dorian