Navigation▲
Tutoriel précédent : lumière diffuse |
Tutoriel suivant : lumières ponctuelles |
I. Contexte▲
Lorsque nous avons calculé l'éclairage ambiant, le seul facteur était l'intensité de la lumière. Ensuite, nous avons ajouté la lumière diffuse, qui prenait en compte la direction de la lumière dans l'équation. La lumière spéculaire inclut ces facteurs et ajoute un nouvel élément au tout : la position de l'observateur.
L'idée est que lorsque la lumière atteint une surface avec un angle donné, elle est aussi réfléchie avec le même angle (du côté opposé par rapport à la normale). Si l'observateur est situé quelque part le long du rayon reflété, il reçoit une plus grande quantité de lumière qu'un observateur placé ailleurs.
Le résultat final de la lumière spéculaire rendra les objets plus clairs sous certains angles et cette clarté diminuera si l'observateur se déplace. L'exemple parfait de la lumière spéculaire dans le monde réel est un objet métallique. Ce type d'objets peut parfois être tellement clair qu'au lieu de voir la couleur naturelle de l'objet on voit une tache de lumière blanche directement reflétée vers vous. Cependant, ce type de caractéristique, naturelle pour les métaux, est absente pour de nombreux autres matériaux (par exemple : le bois). De nombreux objets ne brillent tout simplement pas, quel que soit l'endroit d'où vient la lumière et quelle que soit la position de l'observateur. La conclusion est que le facteur spéculaire dépend plus de l'objet que de la lumière elle-même.
Voyons comment nous pouvons amener la position de l'observateur dans le calcul de la lumière spéculaire. Regardons l'image suivante :
Il y a cinq choses auxquelles il faut prêter attention :
- « I » est la lumière incidente qui atteint la surface (et génère la lumière diffuse) ;
- « N » est la normale à la surface ;
- « R » est le rayon de lumière qui est réfléchi par la surface. Il est symétrique à « I » par rapport à N, mais sa direction est inversée (il pointe vers le « haut » et pas vers le « bas »).
- « V » est le vecteur qui va du point d'intersection entre « I » et la surface jusqu'à l'« œil » (qui représente l'observateur).
- « α » est l'angle créé par les vecteurs « R » et « V ».
Nous allons modéliser le phénomène de lumière spéculaire en utilisant l'angle « α ». L'idée derrière la lumière spéculaire est que la force du rayon réfléchi est à son maximum le long du vecteur « R ». Dans ce cas, « V » est identique à « R » et l'angle vaut zéro. Quand l'observateur commence à s'éloigner de « R », l'angle grandit. Nous voulons que l'effet de la lumière diminue quand l'angle augmente. À présent, nous pouvons deviner que nous allons utiliser le produit scalaire, une nouvelle fois, afin de calculer le cosinus de « α ». Cela servira de facteur spéculaire dans la formule d'éclairage. Quand « α » vaut zéro, son cosinus vaut un, ce qui est le facteur maximum que nous puissions obtenir. Quand « α » diminue, son cosinus devient plus petit jusqu'à ce que « α » atteigne 90 degrés, où le cosinus vaut zéro, et où il n'y a pas d'effet spéculaire. Quand « α » vaut plus de 90 degrés, le cosinus devient négatif et il n'y a pas non plus d'effet spéculaire. Cela signifie que l'observateur n'est pas du tout dans le chemin du rayon réfléchi.
Pour calculer « α », nous allons avoir besoin de « R » et de « V ». « V » peut être calculé en soustrayant la position du point d'impact de la lumière, dans l'espace monde, à la position de l'observateur (lui aussi dans l'espace monde). Comme notre caméra se situe déjà dans l'espace monde, nous n'avons qu'à passer sa position au shader. Comme l'image ci-dessus est simplifiée, il n'y a qu'un seul point que la lumière atteint. En réalité, le triangle complet est éclairé (si tant est qu'il fait face à la lumière). Donc, nous calculons l'effet spéculaire pour chaque pixel (comme nous le faisons déjà avec la lumière diffuse) et pour cela, nous avons besoin de la position de chaque pixel dans l'espace monde. Là aussi, c'est simple : nous transformons les sommets dans l'espace monde, puis nous laissons le rasterizer interpoler la position monde du pixel et nous fournir le résultat dans le fragment shader. En fait, c'est le même travail que celui que nous avons effectué sur la normale dans le tutoriel précédent.
La seule chose qu'il nous reste à faire est de calculer le rayon réfléchi « R », en utilisant le vecteur « I » (fourni au shader par l'application). Regardons l'image suivante :
Souvenons-nous qu'un vecteur n'a pas vraiment de point de départ et que tous les vecteurs ayant la même direction et la même norme sont égaux. Par conséquent, le vecteur « I » a été copié « en dessous » de la surface et la copie est identique à l'original. L'objectif est de calculer le vecteur « R ». Selon les règles de l'addition de vecteurs, « R » est égal à « I » + « V ». « I » est déjà connu donc nous n'avons qu'à calculer « V ». Notons que l'opposée à la normale « V » apparaît aussi comme « -N » et l'utilisation d'un produit scalaire entre « I » et « -N » nous permet de trouver la norme du vecteur créé lorsqu'on projette « I » sur « -N ». Cette norme est exactement la moitié de la norme de « V ». Comme « V » a la même direction que « N », nous calculons « V » en multipliant « N » (dont la norme vaut 1.0) par deux fois la valeur de cette norme. En résumé :
kitxmlcodelatexdvpR = I + Vfinkitxmlcodelatexdvp kitxmlcodelatexdvpV = 2 \times N \times (-N.I)finkitxmlcodelatexdvp kitxmlcodelatexdvp\Rightarrow R = I + 2 \times N \times (-N.I)=I-2\times N \times(N.I)finkitxmlcodelatexdvpMaintenant que vous comprenez ces mathématiques, il est temps de vous révéler un petit secret : GLSL fournit une fonction interne appelée « reflect » qui fait exactement ce calcul. Nous verrons plus bas comment l'utiliser dans un shader.
Finalisons la formule de la lumière spéculaire :
kitxmlcodelatexdvp\begin{pmatrix} R_{spéculaire} \\ G_{spéculaire} \\ B_{spéculaire} \\ \end{pmatrix} = \begin{pmatrix} R_{lumière} \\ G_{lumière} \\ B_{lumière} \\ \end{pmatrix} \times \begin{pmatrix} R_{surface} \\ G_{surface} \\ B_{surface} \\ \end{pmatrix} \times M \times (R.V)^PfinkitxmlcodelatexdvpNous commençons en multipliant la couleur de la lumière par la couleur de la surface. C'est la même chose qu'avec les lumières ambiante et diffuse. Le résultat est multiplié par l'intensité spéculaire du matériau (« M »). Un matériau qui n'a pas de propriété spéculaire (par exemple : du bois) aura une intensité spéculaire de zéro, ce qui mettra à zéro le résultat de l'équation. Des objets plus brillants, comme le métal, peuvent avoir des niveaux de plus en plus élevés d'intensité spéculaire. Après cela, nous multiplions ce résultat par le cosinus de l'angle entre le rayon réfléchi et le vecteur vers l'observateur. Notons que cette dernière partie est élevée à la puissance « P ». « P » est appelée « puissance spéculaire » ou « facteur de brillance ». Son travail est d'intensifier et d'affiner les bords dans les zones où la lumière spéculaire est présente. L'image suivante montre l'effet de la puissance spéculaire quand elle est mise à 1 :
Alors que la suivante montre un exposant spéculaire de 32 :
La puissance spéculaire est aussi considérée comme un attribut du matériau donc différents objets peuvent avoir différentes valeurs de puissance spéculaire.
II. Explication du code▲
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
class
LightingTechnique : public
Technique
{
public
:
...
void
SetEyeWorldPos(const
Vector3f&
EyeWorldPos);
void
SetMatSpecularIntensity(float
Intensity);
void
SetMatSpecularPower(float
Power);
private
:
...
GLuint m_eyeWorldPosLocation;
GLuint m_matSpecularIntensityLocation;
GLuint m_matSpecularPowerLocation;
Il y a trois nouveaux attributs dans la classe LightingTechnique : la position de l'observateur, l'intensité spéculaire et la puissance spéculaire du matériau. Les trois sont indépendantes de la lumière elle-même. La raison est que lorsque la même lumière atteint deux matériaux différents (par exemple : du métal et du bois), chacun brille d'une manière différente. L'utilisation de ces deux attributs pour le matériau impose certaines limites. Tous les triangles faisant partie du même appel à la fonction de dessin auront la même valeur pour ces attributs. Cela peut être gênant quand les triangles représentent des parties différentes du modèle, avec différentes propriétés de matériau. Quand nous atteindrons les tutoriels de chargement de maillage, nous verrons que nous pouvons générer différentes valeurs de spéculaire dans les applications de modelage et les intégrer au tampon de sommets (au lieu de paramètres de shader). Cela nous permettra de dessiner des triangles avec différents éclairages spéculaires dans le même appel. Pour l'instant, l'approche simple que nous en avons faite suffira (comme exercice, vous pouvez essayer d'ajouter l'intensité spéculaire et la puissance spéculaire au tampon de sommets et y accéder dans le shader).
13.
14.
15.
16.
17.
18.
19.
20.
out
vec3
WorldPos0;
void
main
(
)
{
gl_Position
=
gWVP *
vec4
(
Position, 1
.0
);
TexCoord0 =
TexCoord;
Normal0 =
(
gWorld *
vec4
(
Normal, 0
.0
)).xyz;
WorldPos0 =
(
gWorld *
vec4
(
Position, 1
.0
)).xyz;
}
Le vertex shader ci-dessus intègre une seule nouvelle ligne (la dernière). La matrice monde (que nous avons ajoutée dans le tutoriel précédent pour transformer la normale) est maintenant utilisée pour passer la position monde du sommet au fragment shader. Nous voyons une technique intéressante, qui est de transformer la même position du sommet (qui est fournie dans l'espace local) en utilisant deux matrices différentes et de passer les résultats indépendamment au fragment shader. Le résultat de la transformation complète (matrice de transformation monde-vue-projection) va dans la variable du GLSL « gl_Position », le GPU s'occupe de la transformer dans l'espace-écran et l'utilise dans le procédé de rastérisation. Le résultat de la transformation « partielle » (uniquement dans l'espace monde) va dans un attribut défini par l'utilisateur, qui est simplement interpolé durant la rastérisation, afin que chaque pixel pour lequel le fragment shader est invoqué reçoive sa propre position dans l'espace monde. Cette pratique est très utile et est souvent employée.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
in
vec3
WorldPos0;
.
.
.
uniform
vec3
gEyeWorldPos;
uniform
float
gMatSpecularIntensity;
uniform
float
gSpecularPower;
void
main
(
)
{
vec4
AmbientColor =
vec4
(
gDirectionalLight.Color, 1
.0f
) *
gDirectionalLight.AmbientIntensity;
vec3
LightDirection =
-
gDirectionalLight.Direction;
vec3
Normal =
normalize
(
Normal0);
float
DiffuseFactor =
dot
(
Normal, LightDirection);
vec4
DiffuseColor =
vec4
(
0
, 0
, 0
, 0
);
vec4
SpecularColor =
vec4
(
0
, 0
, 0
, 0
);
if
(
DiffuseFactor >
0
) {
DiffuseColor =
vec4
(
gDirectionalLight.Color, 1
.0f
) *
gDirectionalLight.DiffuseIntensity *
DiffuseFactor;
vec3
VertexToEye =
normalize
(
gEyeWorldPos -
WorldPos0);
vec3
LightReflect =
normalize
(
reflect
(
gDirectionalLight.Direction, Normal));
float
SpecularFactor =
dot
(
VertexToEye, LightReflect);
SpecularFactor =
pow
(
SpecularFactor, gSpecularPower);
if
(
SpecularFactor >
0
) {
SpecularColor =
vec4
(
gDirectionalLight.Color, 1
.0f
) *
gMatSpecularIntensity *
SpecularFactor;
}
}
FragColor =
texture2D
(
gSampler, TexCoord0.xy) *
(
AmbientColor +
DiffuseColor +
SpecularColor);
}
Il y a plusieurs changements dans le vertex shader. On y trouve maintenant trois nouvelles variables uniformes, qui stockent les attributs requis pour le calcul de la lumière spéculaire (position de l'observateur, intensité et puissance spéculaire). La couleur ambiante est calculée de la même manière que dans les deux tutoriels précédents. Puis les couleurs diffuse et spéculaire sont créées et initialisées à zéro. Elles ont toutes les deux une valeur différente de zéro uniquement lorsque l'angle entre la lumière et la surface vaut moins de 90 degrés. La vérification est faite en utilisant le facteur de diffusion (de la même manière que dans le tutoriel sur la lumière diffuse).
L'étape suivante est le calcul du vecteur allant du sommet en position monde à la position de l'observateur (elle aussi dans l'espace monde). Nous faisons ce calcul en soustrayant la position monde du sommet à la position de l'observateur, qui est une variable uniforme et est identique pour tous les pixels. Ce vecteur est normalisé pour le rendre utilisable dans le produit scalaire. Après cela, le vecteur lumière réfléchi est calculé en utilisant la fonction interne « reflect » (nous pourrions aussi le calculer manuellement, en suivant la description plus haut). Cette fonction accepte deux paramètres : le vecteur lumière et la normale à la surface. L'important ici est d'utiliser le vecteur lumière original, celui qui va vers la surface, pas celui qui a été utilisé pour le calcul du facteur de diffusion. C'est évident lorsqu'on regarde le diagramme ci-dessus. Ensuite, nous calculons le facteur spéculaire, à partir du cosinus de l'angle entre le rayon de lumière réfléchi et le vecteur du sommet à l'observateur (là encore avec un produit scalaire).
L'effet spéculaire est visible uniquement si cet angle est inférieur à 90 degrés. Par conséquent, nous vérifions que le résultat du dernier produit scalaire est supérieur à zéro. La couleur spéculaire finale est calculée en multipliant la couleur de la lumière par l'intensité spéculaire du matériau et le facteur spéculaire. Nous ajoutons les couleurs ambiante, diffuse et spéculaire pour calculer la couleur complète de la lumière. Elle est multipliée par la couleur échantillonnée depuis la texture et fournit la couleur finale du pixel.
135.
136.
m_pEffect->
SetEyeWorldPos(m_pGameCamera->
GetPos());
m_pEffect->
SetMatSpecularIntensity(1.0
f);
m_pEffect->
SetMatSpecularPower(32
);
Utiliser la couleur spéculaire est très simple. Dans la boucle de rendu, nous récupérons la position de la caméra (qui est déjà définie dans l'espace monde) et la passons à la technique de lumière. Nous définissons aussi l'intensité spéculaire et sa puissance. Tout le reste est géré par le shader.
Jouez avec les différentes valeurs spéculaires et la direction de la lumière pour voir leur effet. Vous devrez probablement vous déplacer autour de l'objet pour vous placer dans une position où la lumière spéculaire est visible.
III. Sources▲
Vous pouvez télécharger les sources de ce projet en suivant ce lien :
IV. Remerciements▲
Merci à Etay Meiri de nous permettre de traduire son tutoriel.
Merci à LittleWhite pour ses corrections et à ced pour sa relecture.
V. Navigation▲
Tutoriel précédent : lumière diffuse |
Tutoriel suivant : lumières ponctuelles |