Éclairage de base

Modèle d’éclairage Phong

REMARQUE : Cet exemple est WIP, il sera mis à jour avec des diagrammes, des images, plus d’exemples, etc.

Qu’est-ce que Phong ?

Phong est un modèle d’éclairage très basique, mais d’apparence réelle, pour les surfaces, composé de trois parties : éclairage ambiant, diffus et spéculaire.

Éclairage ambiant:

L’éclairage ambiant est la plus simple des trois parties à comprendre et à calculer. L’éclairage ambiant est une lumière qui inonde la scène et éclaire l’objet uniformément dans toutes les directions.

Les deux variables de l’éclairage ambiant sont la force de l’ambiance et la couleur de l’ambiance. Dans votre fragment shader, ce qui suit fonctionnera pour ambient :

in vec3 objColor;

out vec3 finalColor;

uniform vec3 lightColor;

void main() {
   float ambientStrength = 0.3f;
   vec3 ambient = lightColor * ambientStrength;
   finalColor = ambient * objColor;
}

Éclairage diffus :

L’éclairage diffus est légèrement plus complexe que l’ambiance. L’éclairage diffus est une lumière directionnelle, ce qui signifie essentiellement que les faces tournées vers la source de lumière seront mieux éclairées et que les faces pointant vers l’extérieur seront plus sombres en raison de la façon dont la lumière les frappe.

Remarque : l’éclairage diffus nécessitera l’utilisation de normales pour chaque face que je ne montrerai pas comment calculer ici. Si vous voulez apprendre à faire cela, consultez la page de mathématiques 3D.

Pour modéliser la réflexion de la lumière en infographie, on utilise une fonction de distribution de réflectance bidirectionnelle (BRDF). BRDF est une fonction qui donne la relation entre la lumière réfléchie dans une direction sortante et la lumière incidente dans une direction entrante.

Une surface diffuse parfaite a une BRDF qui a la même valeur pour toutes les directions incidentes et sortantes. Cela réduit considérablement les calculs et est donc couramment utilisé pour modéliser des surfaces diffuses car cela est physiquement plausible, même s’il n’y a pas de matériaux diffus purs dans le monde réel. Cette BRDF est appelée réflexion lambertienne car elle obéit à la loi du cosinus de Lambert.

La réflexion lambertienne est souvent utilisée comme modèle pour la réflexion diffuse. Cette technique fait en sorte que tous les polygones fermés (tels qu’un triangle dans un maillage 3D) réfléchissent la lumière de manière égale dans toutes les directions lors du rendu. Le coefficient de diffusion est calculé à partir de l’angle entre le vecteur normal et le vecteur lumière.

f_Lambertian = max( 0.0, dot( N, L )

où ‘N’ est le vecteur normal de la surface, et ‘L’ est le vecteur vers la source lumineuse.

Comment ça fonctionne

En général Le produit dot de 2 vecteurs est égal au cosinus de l’angle entre les 2 vecteurs multiplié par la grandeur (longueur) des deux vecteurs.

dot( A, B ) == length( A ) * length( B ) * cos( angle_A_B ) 

Il s’ensuit que le produit dot de 2 vecteurs unitaires est égal au cosinus de l’angle entre les 2 vecteurs, car la longueur d’un vecteur unitaire est 1.

uA = normalize( A )
uB = normalize( B )
cos( angle_A_B ) == dot( uA, uB )

[![entrez la description de l’image ici][1]][1]

Si nous regardons la fonction cos(x) entre les angles -90° et 90° alors nous pouvons voir qu’elle a un maximum de 1 à un angle de 0° et elle descend à 0 aux angles de 90° et -90°.

[![entrez la description de l’image ici][2]][2]

Ce comportement est exactement ce que nous voulons pour le modèle de réflexion. Lorsque le vecteur nromal de la surface et la direction de la source lumineuse sont dans la même direction (l’angle entre est de 0°) alors nous voulons un maximum de réflexion. En revanche, si les vecteurs sont orthonormalisés (l’angle intermédiaire est de 90°), alors nous voulons un minimum de réflexion et nous voulons un parcours fonctionnel fluide et continu entre les deux frontières de 0° et 90°.

[![entrez la description de l’image ici][3]][3]

Si la lumière est calculée par sommet, la réflexion est calculée pour chaque coin de la primitive. Entre les primitives, les réflexions sont interpolées en fonction de ses coordonnées barycentriques. Voir les réflexions résultantes sur une surface sphérique :

[![entrez la description de l’image ici][4]][4]

Ok, donc pour commencer avec notre fragment shader, nous aurons besoin de quatre entrées.

  • Normales des sommets (devraient être dans le tampon et spécifiées par des pointeurs d’attributs de sommet)

  • Position du fragment (doit être sortie du vertex shader dans le frag shader)

  • Position de la source lumineuse (uniforme)

  • Couleur claire (uniforme)

    in vec3 normal; in vec3 fragPos;

    out vec3 finalColor;

    uniform vec3 lightColor; uniform vec3 lightPos; uniform vec3 objColor;

À l’intérieur de main, nous devons faire des calculs. Tout le concept d’éclairage diffus est basé sur l’angle entre la normale et la direction de la lumière. Plus l’angle est grand, moins il y a de lumière jusqu’à 90° où il n’y a pas de lumière du tout.

Avant de pouvoir commencer à calculer la quantité de lumière, nous avons besoin du vecteur de direction de la lumière. Cela peut être récupéré en soustrayant simplement la position de la lumière de la position du fragment qui renvoie un vecteur de la position de la lumière pointant vers la position du fragment.

vec3 lightDir = lightPos-fragPos;

Aussi, allez-y et normalisez les vecteurs normal et lightDir afin qu’ils aient la même longueur pour travailler avec.

normal  = normalize(normal);
lightDir = normalize(lightDir);

Maintenant que nous avons nos vecteurs, nous pouvons calculer la différence entre eux. Pour ce faire, nous allons utiliser la fonction produit scalaire. Fondamentalement, cela prend 2 vecteurs et renvoie le cos () de l’angle formé. C’est parfait car à 90 degrés, il donnera 0 et à 0 degrés, il donnera 1. Par conséquent, lorsque la lumière pointe directement sur l’objet, elle sera entièrement éclairée et vice versa.

float diff = dot(normal, lightDir);

Il y a encore une chose que nous devons faire avec le nombre calculé, nous devons nous assurer qu’il est toujours positif. Si vous y réfléchissez, un nombre négatif n’a pas de sens dans le contexte car cela signifie que la lumière est derrière le visage. Nous pourrions utiliser une instruction if, ou nous pouvons utiliser la fonction max() qui renvoie le maximum de deux entrées.

diff = max(diff, 0.0);

Cela fait, nous sommes maintenant prêts à calculer la couleur de sortie finale pour le fragment.

vec3 diffuse = diff * lightColor;
finalColor = diffuse * objColor;

Ça devrait ressembler à ça: [![entrez la description de l’image ici][5]][5]

Éclairage spéculaire :

Travail en cours, revenez plus tard.

Combiné

Travail en cours, revenez plus tard.

Le code et l’image ci-dessous montrent ces trois concepts d’éclairage combinés.

[1] : https://i.stack.imgur.com/yc5B0.png [2] : https://i.stack.imgur.com/rzKtB.png [3] : https://i.stack.imgur.com/yOx2S.png [4] : https://i.stack.imgur.com/Gyl11.png [5] : http://i.stack.imgur.com/03p1T.png