Lumière Spéculaire
Adapté à XNA 4.0 par A. Nadif
Bonjour, et bienvenue à l'occasion du tutorial 3 de ma série sur les shaders dans XNA. Aujourd'hui, nous allons implémenter un autre algorithme d'éclairage, appelé Eclairage Spéculaire. Cet algorithme se base sur les tutoriaux sur la lumière ambiante et diffuse, ainsi, si vous ne les avez pas lus, maintenant serait une bonne occasion de le faire. :)
Avant de commencer :
Dans ce tutorial, vous aurez besoin des connaissances basiques de la programmation de shaders, les vecteurs et les matrices. De plus, le projet et fait pour XNA 2.0 et Visual Studio 2005.
Avant de commencer :
Dans ce tutorial, vous aurez besoin des connaissances basiques de la programmation de shaders, les vecteurs et les matrices. De plus, le projet et fait pour XNA 2.0 et Visual Studio 2005.
Lumière Spéculaire
Jusque là, on a un model assez pratique pour faire un bel éclairage d'objets. Mais, que faire si on a un objet blanc, polit ou brilliant à rendre ? Par exemple une surface métallique, plastique, un verre, une bouteille etc.
Pour simuler cela, on a besoin d'implémenter un nouveau vecteur dans notre algorithme d'éclairage : le vecteur oeil.
Vous pouvez vous demander : mais qu'est ce que le vecteur "oeil" ? La réponse est assez simple. C'est le vecteur qui pointe depuis notre caméra jusqu'à sa cible.
On a déjà ce vecteur dans le code de notre application :
Pour simuler cela, on a besoin d'implémenter un nouveau vecteur dans notre algorithme d'éclairage : le vecteur oeil.
Vous pouvez vous demander : mais qu'est ce que le vecteur "oeil" ? La réponse est assez simple. C'est le vecteur qui pointe depuis notre caméra jusqu'à sa cible.
On a déjà ce vecteur dans le code de notre application :
viewMatrix = Matrix.CreateLookAt(new Vector3(x, y, zHeight), Vector3.Zero, Vector3.Up);
La position de "l'oeil" est localisée ici :
Vector3(x, y, zHeight)
Donc, prenons ce vecteur, et stockons le dans une variable :
Vector4 vecEye = new Vector4(x, y, zHeight, 0);
Regardons d'un peu plus près comment créer et utiliser le shader.
La formule de la lumière spéculaire est :
I=Ai*Ac+Di*Dc*N.L+Si*Sc*(R.V)^n
où R=2*(N.L)*N-L
La formule de la lumière spéculaire est :
I=Ai*Ac+Di*Dc*N.L+Si*Sc*(R.V)^n
où R=2*(N.L)*N-L
Comme vous pouvez le voir, on a V, le nouveau vecteur œil, et aussi R, un vecteur de réflexion.
Pour calculer la lumière spéculaire, on a besoin de prendre la produit scalaire de R et V et de l'utiliser dans la puissance de n où n contrôle combien l'objet brille.
Pour calculer la lumière spéculaire, on a besoin de prendre la produit scalaire de R et V et de l'utiliser dans la puissance de n où n contrôle combien l'objet brille.
Implémentation du shader
Il est temps d'implémenter le shader.
Comme vous pouvez le voir, cet objet semble polit/brillant, et ce uniquement en utilisant le shader que nous sommes sur le point d'implémenter !
Plutôt cool, hein ?
Commençons par déclarer quelques variables dont nous aurons besoin pour ce shader :
Plutôt cool, hein ?
Commençons par déclarer quelques variables dont nous aurons besoin pour ce shader :
float4x4 matWorldViewProj; float4x4 matWorld; float4 vecLightDir; float4 vecEye; float4 vDiffuseColor; float4 vSpecularColor; float4 vAmbient;
Et après la structure de sortie pour notre Vertex Shader. Le shader va retourner la position transformée, le vecteur de la lumière, le vecteur normal, et le vecteur de la vue (le vecteur œil) pour une vertice donnée.
struct OUT { float4 Pos : POSITION; float3 L : TEXCOORD0; float3 N : TEXCOORD1; float3 V : TEXCOORD2; };
Pas grand chose de neuf dans le vertex shader depuis la dernière fois, sauf pour le vecteur V. V est calculé en soustrayant la position au vecteur œil.
Comme V est une partie de la structure OUT, et on a définit OUT Out, on peut calculer V avec le code suivant :
Comme V est une partie de la structure OUT, et on a définit OUT Out, on peut calculer V avec le code suivant :
float4 PosWorld = mul(Pos,matWorld); Out.L = vecEye - PosWorld;
où vecEye est un vecteur passé dans le shader à travers un paramètre du shader (la position de la caméra).
OUT VS(float4 Pos : POSITION, float3 N : NORMAL) { OUT Out = (OUT)0; Out.Pos = mul(Pos, matWorldViewProj); Out.N = mul(N, matWorld); float4 PosWorld = mul(Pos, matWorld); Out.L = vecLightDir; Out.V = vecEye - PosWorld; return Out; }
Et puis il est temps d'implémenter le pixel shader. On commence par normaliser les vecteurs Normal, LightDir et ViewDir pour rendre un peu plus simples les calculs.
Le pixel shader va retourner un float4, qui représente la couleur finale du pixel courant, I, basée sur la formule de la lumière spéculaire, décrite plus tôt.
Puis, on calcule la direction de la lumière diffuse comme on l'a fait lors du Tutorial 2.
La nouveauté dans le Pixel Shader pour la lumière spéculaire est qu'il faut calculer et utiliser un vecteur de réflexion de L par N, et utiliser ce vecteur pour calculer la lumière spéculaire.
donc, on commence oar calculer le vecteur réflexion de L par N :
R = 2 * (N.L) * N – L
Comme on peut le voir, on a déjà calculé le produit scalaire N.L quand on a calculé la lumière diffuse. Utilisons ceci et écrivons le code suivant :
Le pixel shader va retourner un float4, qui représente la couleur finale du pixel courant, I, basée sur la formule de la lumière spéculaire, décrite plus tôt.
Puis, on calcule la direction de la lumière diffuse comme on l'a fait lors du Tutorial 2.
La nouveauté dans le Pixel Shader pour la lumière spéculaire est qu'il faut calculer et utiliser un vecteur de réflexion de L par N, et utiliser ce vecteur pour calculer la lumière spéculaire.
donc, on commence oar calculer le vecteur réflexion de L par N :
R = 2 * (N.L) * N – L
Comme on peut le voir, on a déjà calculé le produit scalaire N.L quand on a calculé la lumière diffuse. Utilisons ceci et écrivons le code suivant :
float3 Reflect = normalize(2 * Diff * Normal - LightDir);
Note: On peut aussi utiliser la fonction de réflexion qui est construite en HLSL à la place, prenant un vecteur incident et un vecteur normal comme paramètres, retournant un vecteur de réflexion :
float3 ref = reflect(L, N);
float3 ref = reflect(L, N);
Maintenant, tout ce qui reste est de calculer la lumière spéculaire. On sait qu'elle est calculée en prenant la puissance du produit scalaire du vecteur de réflexion et du vecteur de vue, par n : (R.V)^n
Vous pouvez penser à n comme un facteur de combien l'objet brille. Plus n est grand, moins il brille, donc jouez avec n pour obtenir le résultat qui vous plait.
Comme vous pouvez l'avoir ramarqué, on utilise une nouvelle fonction HLSL pow(a,b). Ce qu'elle fait est assez simple, elle retourne a^b.
float Specular = pow(saturate(dot(Reflect, ViewDir)), 15);
Ouf, on est enfin prêt à assembler tout ceci et calculer la dernière couleur du pixel :
return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular;
Cette formule ne devrait plus être une surprise pour personne, non ?
On commence par calculer les lumières Ambiante et Diffuse, et les assembler. Puis, on prend la couleur de la lumière spéculaire et on la multiplie avec le composant Specular qu'on vient de calculer, et on l'ajoute à la couleur des lumières Ambiante et Diffuse.
Le pixel shader pour ce tutorial peut ressembler à ceci :
On commence par calculer les lumières Ambiante et Diffuse, et les assembler. Puis, on prend la couleur de la lumière spéculaire et on la multiplie avec le composant Specular qu'on vient de calculer, et on l'ajoute à la couleur des lumières Ambiante et Diffuse.
Le pixel shader pour ce tutorial peut ressembler à ceci :
float4 PS(float3 L: TEXCOORD0, float3 N : TEXCOORD1, float3 V : TEXCOORD2) : COLOR { float3 Normal = normalize(N); float3 LightDir = normalize(L); float3 ViewDir = normalize(V); float Diff = saturate(dot(Normal, LightDir)); // R = 2 * (N.L) * N – L float3 Reflect = normalize(2 * Diff * Normal - LightDir); float Specular = pow(saturate(dot(Reflect, ViewDir)), 15); // R.V^n // I = A + Dcolor * Dintensity * N.L + Scolor * Sintensity * (R.V)n return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular; }
Et bien sur, on doit spécifier une technique pour ce shader, et compiler le Vertex et le Pixel shader :
technique SpecularLight { pass P0 { // compile shaders VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 PS(); } }
L’intégralité du code pour le fichier shader (.fx) est :
float4x4 matWorldViewProj; float4x4 matWorld; float4 vecLightDir; float4 vecEye; float4 vDiffuseColor; float4 vSpecularColor; float4 vAmbient; struct OUT { float4 Pos : POSITION; float3 L : TEXCOORD0; float3 N : TEXCOORD1; float3 V : TEXCOORD2; }; OUT VS(float4 Pos : POSITION, float3 N : NORMAL) { OUT Out = (OUT)0; Out.Pos = mul(Pos, matWorldViewProj); Out.N = mul(N, matWorld); float4 PosWorld = mul(Pos, matWorld); Out.L = vecLightDir; Out.V = vecEye - PosWorld; return Out; } float4 PS(float3 L: TEXCOORD0, float3 N : TEXCOORD1, float3 V : TEXCOORD2) : COLOR { float3 Normal = normalize(N); float3 LightDir = normalize(L); float3 ViewDir = normalize(V); float Diff = saturate(dot(Normal, LightDir)); // R = 2 * (N.L) * N – L float3 Reflect = normalize(2 * Diff * Normal - LightDir); float Specular = pow(saturate(dot(Reflect, ViewDir)), 15); // R.V^n // I = A + Dcolor * Dintensity * N.L + Scolor * Sintensity * (R.V)n return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular; } technique SpecularLight { pass P0 { // compile shaders VertexShader = compile vs_2_0 VS(); PixelShader = compile ps_2_0 PS(); } }
Utilisation du shader
Il n'y a quasiment rien de nouveau en ce qui concerne l'utilisation du shader dans une application depuis mon dernier tutorial, sauf pour fixer le paramètre vecEye du shader.
On prend simplement la position de la caméra et on la passe à notre shader. Si vous utilisez une classe caméra, il pourriez avoir une fonction pour prendre la position de la caméra. C'est vraiment à vous de décider comment l'extraire.
Dans mon exemple, j'utilise les mêmes variables pour fixer la position de la caméra, et créer un vecteur qui est passé au shader.
On prend simplement la position de la caméra et on la passe à notre shader. Si vous utilisez une classe caméra, il pourriez avoir une fonction pour prendre la position de la caméra. C'est vraiment à vous de décider comment l'extraire.
Dans mon exemple, j'utilise les mêmes variables pour fixer la position de la caméra, et créer un vecteur qui est passé au shader.
Vector4 vecEye = new Vector4(x, y, zHeight, 0);
et passez le au shader :
effect.Parameters["vecEye"].SetValue(vecEye);
Fixer des paramètres dans un shader depuis l'application et comment implémenter le shader ne devrait pas être nouveau pour vous si vous avez suivi jusque là, donc, je ne vais pas détailler plus ce sujet. S'il vous plait, référez vous au Tutorial 1 et 2 pour cela, ou bien envoyez moi un e-mail.
On doit aussi ce rappeler de fixer la technique au "SpecularLight".
On doit aussi ce rappeler de fixer la technique au "SpecularLight".
Exercices
1. Faites une nouvelle variable globale dans le shader, qui spécifie la "brilliance" de l'objet. Vous devriez pouvoir fixer cette variable depuis l'application qui utilise ce shader.
2. Dans ce tutorial, vous n'avez pas beaucoup de cntrôle sur les options de la lumière (ex : fixer Ai et Ac, Di et Dc). Faites en sorte de supporter les options Ai, Ac, Di, Dc, Si et Sc où Si et Sc sont la couleur et l'intensité pour la lumière spéculaire.
Merci pour avoir lut ce tutorial, j'espère l'avoir couvert assez pour vous permettre de comprendre ce que tout ca veut dire !
Si vous avez des commentaires, des impressions ou des questions, s'il vous plait, demandez moi à petriw(at)gmail.com.
La prochaine fois je vais couvrir le mapping Normal, et comment utiliser des textures dans les shaders.
Téléchargez le code source
2. Dans ce tutorial, vous n'avez pas beaucoup de cntrôle sur les options de la lumière (ex : fixer Ai et Ac, Di et Dc). Faites en sorte de supporter les options Ai, Ac, Di, Dc, Si et Sc où Si et Sc sont la couleur et l'intensité pour la lumière spéculaire.
Merci pour avoir lut ce tutorial, j'espère l'avoir couvert assez pour vous permettre de comprendre ce que tout ca veut dire !
Si vous avez des commentaires, des impressions ou des questions, s'il vous plait, demandez moi à petriw(at)gmail.com.
La prochaine fois je vais couvrir le mapping Normal, et comment utiliser des textures dans les shaders.
Téléchargez le code source