gKit2 light
|
Tous les modèles de matières s'utilisent de la même manière. Ils permettent de connaitre la lumière réfléchie vers une direction, l'observateur, par exemple :
\[L_r(p, \vec{o})= f_r(\vec{l}, \vec{o}) \cdot L_i(p, \vec{l}) \cos \theta_l \]
La lumière \( L_i(p, \vec{l}) \) arrive sur le point p depuis la direction \(\vec{l}\) et le modèle de matière, la brdf \(f_r(\vec{l}, \vec{o})\) indique quelle quantité \(L_r(p, \vec{o})\) est réfléchie par p dans la direction \(\vec{o}\).
\[ f_r(\vec{o}, \vec{l})= \frac{k}{\pi}\]
remarque : pourquoi le \( \frac{1}{\pi} \) ? les modèles de matières doivent vérifier quelques propriétés. Les détails sont dans la partie modèles réalistes.
donc oui, ce "modèle" de réflexion est une constante k comprise entre 0 et 1 qui représente à quel point la matière réfléchit la lumière incidente. Et comme il est indépendant de la direction d'observation, la même quantité de lumière est réfléchie dans toutes les directions.
on doit calculer :
\[L_r(p, \vec{o})= \frac{k}{\pi} \cdot L_i(p, \vec{l}) \cos \theta_l \]
il suffit de calculer \( \cos \theta_l \), le cosinus de l'angle entre la normale en p et la direction vers la lumière, et de se rappeler que les calculs sur les points et les vecteurs doivent se faire avec des coordonnées dans le même repère.
en résumé, il faut connaitre, dans le même repère :
premier constat, les normales et les positions doivent faire partie de la description des sommets de l'objet. elles sont donc accessibles par le vertex shader, mais pas directement par le fragment shader.
la position de la camera, la position de la source de lumière, son emission et la constante k, seront des uniforms
du fragment shader.
comment obtenir p et n dans le fragment shader ?
il suffit de se rappeler que les sorties du vertex shader sont interpolées avant d'être accessibles par le fragment shader. l'interpolation des positions des sommets du triangle calcule, pour chaque pixel, la position dans l'espace du point du triangle se projettant sur le centre du pixel. pour les normales, il ne faut pas oublier que l'interpolation change la longueur des vecteurs. le vertex shader déclare 2 sorties, des varyings : out vec3 p; out vec3 n;
il ne reste plus qu'à calculer leurs coordonnées dans un repère commun...
quel repère pour les calculs ? n'importe quel repère avant la projection : repère objet, scène ou camera. la projection ne préserve par les angles, et comme on doit en calculer plusieurs, autant choisir un repère dans lequel c'est simple à faire.
remarque : repassez dans introduction api 3d, openGL et pipeline graphique ou le cm d'intro openGL, si nécessaire.
les exemples suivants font les calculs dans le repère du monde, mais ce n'est peut être pas le plus efficace. pourquoi ?
on suppose que l'on connait la position de la source et la position de la camera dans le repère du monde, donc pas de transformation. pour les sommets et les normales, par contre, on connait leur coordonnées dans le repère de l'objet, il faut donc les transformer, en utilisant la matrice model
.
il ne reste plus qu'à écrire le fragment shader qui doit calculer les directions \( \vec{o} \) et \( \vec{l} \) et \( \cos \theta_l \) :
une relation utile : \( \cos \angle(\vec{u}, \vec{v})= \frac{\vec{u} \cdot \vec{v}}{||\vec{u}|| \cdot ||\vec{v}||} \), ce qui s'écrit directement : float cos_theta= dot(normalize(u), normalize(v));
autre remarque : les normales devraient être transformées par une matrice différente de celles des sommets. dans les cas simples, lorsqu'il n'y a pas d'étirement ou de changement d'échelle dans la transformation des sommets, les normales subissent la même transformation. sinon Transform::normal() renvoie la transformation à utiliser sur les normales.
ce shader suppose que le flux émis par la source arrive intégralement en p, ce qui est faux, il manque un terme \( 1 / ||\vec{l}||^2 \) et un cosinus si la source n'est pas un point. cf le cours simulation et integration numérique.
vous pouvez tester avec shader_kit, cf prototyper un shader program :
bin/shader_kit tutos/brdf_lambert.glsl data/bigguy.obj
La démarche est identique, on doit calculer :
\[L_r(p, \vec{o})= \frac{\alpha+8}{8\pi} \cos^\alpha \theta_h \cdot L_i(p, \vec{l}) \cos \theta_l \]
il faut connaitre, dans le même repère :
le fragment shader doit calculer les directions \( \vec{o} \), \( \vec{l} \), et \( \vec{h} \), puis \( \cos \theta_l \) et \( \cos \theta_h \) :
Un modèle à microfacettes se présente classiquement sous cette forme :
\[ f_r(\vec{o}, \vec{l})= \frac{F(\vec{o}, \vec{h}) \, D(\vec{h}) \, G_2(\vec{o}, \vec{l}, \vec{h}) }{4 |\vec{n} \cdot \vec{o}| |\vec{n} \cdot \vec{l}|} \]
Le terme \( F \), le coefficient de Fresnel, représente le reflet crée par une facette, \( D \) correspond au nombre de facettes orientées dans la direction \( \vec{h} \) et qui créent l'intensité du reflet (plus il y a de micro-facettes orientées dans la bonne direction, plus le reflet est intense). Le reste, \( G_2 \) et le cosinus \( |\vec{n} \cdot \vec{o}| \) permettent de normaliser \( D \) pour garantir la conservation d'énergie.
\[ D( \vec{h} ) = \frac{\chi^+(\vec{h} \cdot \vec{n})}{\pi \alpha^2 \cos^4 \theta_m \left( 1 + \frac{\tan^2 \theta_h}{\alpha^2} \right)^2 }, \textrm{ cf Heitz 2014, eq 71}\\ \]
\begin{eqnarray*} G_2( \vec{o}, \vec{l}, \vec{h} ) & = & \frac{\chi^+(\vec{o} \cdot \vec{h}) \chi^+(\vec{l} \cdot \vec{h})} {1 + \Lambda(\vec{o}) + \Lambda(\vec{l})}, \textrm{ cf Heitz 2014, eq 99}\\ \textrm{ avec}& : &\\ \Lambda(\vec{\omega}) & = & - \frac{1}{2} + \frac{1}{2} \sqrt{1 + \alpha^2 \tan^2 \theta}, \textrm{ cf Heitz 2014, eq 72}\\ \textrm{ et}& : &\\ \chi^+(x) & = & 0 \textrm{ si } x < 0\\ & = & 1 \textrm{ sinon } \end{eqnarray*}
Bon, ca fait pas mal de termes à évaluer, première étape, lesquels peut-on calculer facilement ?
rappel : on sait calculer facilement \( cos \angle(\vec{u}, \vec{v}) \) :
après quelques manipulations on arrive à :
\[ D( \vec{h} ) = \chi^+(\vec{h} \cdot \vec{n}) \frac{\alpha^2}{\pi \left( 1 + \cos^2 \theta_h (\alpha^2 -1) \right)^2} = \chi^+(\vec{h} \cdot \vec{n}) \frac{1}{\pi} \left( \frac{\alpha}{1 + \cos^2 \theta_h (\alpha^2 -1)} \right)^2 \]
ensuite on peut re-écrire \( \Lambda(\vec{\omega}) = - \frac{1}{2} + \frac{1}{2} \sqrt{1 + \alpha^2 \tan^2 \theta_\omega} = - \frac{1}{2} + \frac{1}{2} \sqrt{1 + \alpha^2 \left( \frac{1}{\cos^2\theta_\omega} -1 \right)} \) et :
\[ G_2( \vec{o}, \vec{l}, \vec{h} ) = \chi^+(\vec{o} \cdot \vec{h}) \chi^+(\vec{l} \cdot \vec{h}) \frac{2}{\sqrt{1 + \alpha^2 \tan^2 \theta_o} + \sqrt{1 + \alpha^2 \tan^2 \theta_l}} \]
La démarche est toujours la même, on doit calculer :
\[L_r(p, \vec{o})= \frac{F(\vec{o}, \vec{h}) \, G_2(\vec{o}, \vec{l}, \vec{h}) \, D(\vec{h})}{4 |\vec{n} \cdot \vec{o}| |\vec{n} \cdot \vec{l}|} \cdot L_i(p, \vec{l}) \cos \theta_l \]
Les paramètres du modèle sont la rugosité \( \alpha \) ainsi que l'indice de réfraction \( \eta \), ou \( F_0 \) (on peut calculer l'un en fonction de l'autre), il ne reste plus qu'à évaluer les termes :
quelques exemples de reflets pour Blinn - Phong et ce modèle à microfacettes :