Maison / Toit / éclairage ouvert. Cours et exemples de programmation Array(). Comment fonctionne l'éclairage

éclairage ouvert. Cours et exemples de programmation Array(). Comment fonctionne l'éclairage

Comme vous pouvez le voir, nous avons introduit une matrice supplémentaire normale , elle est nécessaire pour traduire les normales de l'objet du système de coordonnées local de l'objet au monde, cela est nécessaire pour calculer l'éclairage dans le système de coordonnées mondial.

Il convient de noter que lors de l'utilisation de FFP OpenGL, l'éclairage n'a pas été calculé dans le système de coordonnées mondial, mais dans la vue. À mon avis, ce n'est pas très pratique, parce que. le système de coordonnées de vue est connecté à la caméra, et il est pratique de définir les sources lumineuses dans le système de coordonnées mondial, et c'est là que l'ensemble du calcul est effectué.

Calcul d'éclairage

L'ombrage Phong est utilisé pour les calculs d'éclairage dans ce didacticiel. La signification principale du modèle est que l'illumination finale de l'objet se compose de trois composants :

  • Lumière de fond (ambiante)
  • Lumière diffusée (diffuse)
  • Lumière réfléchie (spéculaire)

En plus de ces paramètres, nous ajouterons notre propre lueur matérielle (émission), ce paramètre vous permet de mettre en évidence l'objet même s'il n'est éclairé par aucune source de lumière.

Ainsi, chacune des composantes est calculée en tenant compte des paramètres de la source lumineuse et du matériau de l'objet. Vous pouvez obtenir plus d'informations sur ce modèle d'éclairage dans cette note.

Le calcul de l'éclairage peut être soit un éclairage par sommet, soit un éclairage par pixel. Dans cette leçon, nous considérerons l'éclairage pixel par pixel, il vous permet de lisser le manque de détails dans les modèles polygonaux et de calculer plus précisément l'éclairage à chaque point de l'objet. Le calcul principal de l'éclairage par pixel a lieu dans le fragment shader.

Avant de procéder au calcul de l'éclairage, il est nécessaire de calculer et de passer certains paramètres de vertex du vertex shader au fragment shader :

  • Normal à la surface de l'objet au sommet (normal)
  • La direction de la lumière incidente, du sommet à la source lumineuse (direction de la lumière)
  • Direction de la vue, du sommet à l'observateur (direction de la vue)
  • Distance de la source lumineuse ponctuelle au sommet (distance)

La normale à la surface de l'objet et la direction de la lumière incidente sont utilisées pour calculer la lumière diffuse (diffuse) et réfléchie (spéculaire), cependant, pour calculer la lumière réfléchie, vous devez également connaître la direction du regard de l'observateur . La distance entre le sommet et la source lumineuse est nécessaire pour calculer le facteur d'atténuation global. Le vertex shader ressemblera à ceci :

# version 330 core #define VERT_POSITION 0 #define VERT_TEXCOORD 1 #define VERT_NORMAL 2 mise en page (emplacement = VERT_POSITION) en position vec3 ; mise en page (emplacement = VERT_TEXCOORD) dans vec2 texcoord ; mise en page (emplacement = VERT_NORMAL) dans vec3 normal ; // paramètres de transformation uniform struct Transform ( mat4 model; mat4 viewProjection; mat3 normal; vec3 viewPosition; ) transform; // paramètres de la source lumineuse ponctuelle uniform struct PointLight ( vec4 position ; vec4 ambient ; vec4 diffuse ; vec4 specular ; vec3 attenuation ; ) light ; // paramètres pour le fragment shader out Vertex ( vec2 texcoord; vec3 normal; vec3 lightDir; vec3 viewDir; float distance; ) Vert; void main(void ) ( // traduit les coordonnées du sommet dans le système de coordonnées mondial vec4 vertex = transform.model * vec4(position, 1 .0 ) ; // direction du sommet à la source de lumière dans le système de coordonnées mondial vec4 lightDir = lumière.position - sommet ; // passe certains paramètres au fragment shader // passe les coordonnées de texture vert.texcoord = texcoord; // passe la normale dans le système de coordonnées universel vert.normal = transform.normal * normal; // passe la direction à la source de lumière Vert.lightDir = vec3(lightDir) ; // passe la direction du sommet à l'observateur dans le système de coordonnées universel Vert.viewDir = transform.viewPosition - vec3(vertex) ; // passe la distance du sommet à la source de lumière Vert.distance = longueur(lightDir) ; // convertit les coordonnées des sommets en homogènes gl_Position = transform.viewProjection * sommet ; )

Sans source lumineuse, l'image n'est pas visible. Pour initialiser la source et activer le gestionnaire de calcul de l'effet de la source sur les objets, il suffit d'exécuter les commandes suivantes : glEnable(gl_lighting);//enable lighting analysis mode

GlEnable(gl_light0); // inclut une source spécifique (nulle) dans la scène, avec ses caractéristiques

La fonction Disable() est utilisée pour désactiver la source. La source de lumière par défaut est située dans l'espace avec les coordonnées (0,0,∞). Vous pouvez créer une source de lumière à n'importe quel endroit de l'espace image.

Quatre types de sources lumineuses sont pris en charge dans la bibliothèque OpenGl :

  • éclairage de fond (éclairage ambiant),
  • sources ponctuelles,
  • projecteurs (projecteurs),
  • sources lumineuses distantes (lumière distante).
Chaque source lumineuse a son propre ensemble de caractéristiques.
Les caractéristiques de la source lumineuse correspondent aux paramètres du modèle de Phong.
Pour définir les paramètres vectoriels, la fonction glLightfv() est utilisée, qui a le format suivant :

glLightfv(source, paramètre, pointer_to_array);

Il existe quatre paramètres vectoriels qui déterminent la position et la direction des rayons source et la composition de couleur de ses composants - fond, diffusion et spéculaire.
Pour définir des paramètres scalaires dans OpenGL, utilisez la fonction glLightf() :

glLightf(source, paramètre, valeur);

Supposons, par exemple, qu'il soit nécessaire d'inclure dans la scène la source GL_LIGHT0, qui doit être située au point (1.0, 2.0, 3.0). La position de la source est stockée dans le programme sous la forme d'un point en coordonnées homogènes :

GLfloat light0_pos=(1.0, 2.0, 3.0, 1.0);

Si la quatrième composante de ce point est égale à zéro, alors la source ponctuelle se transforme en une source distante, pour laquelle seule la direction des rayons est significative :

GLfloat light0_dir=(1.0, 2.0, 3.0, 0.0);

Ensuite, la composition de couleur des composants de fond, de diffusion et spéculaire de la source est déterminée. Si, dans cet exemple, la source a une composante spéculaire blanche et que les composantes de fond et de diffusion doivent être rouges, alors le fragment de programme qui forme la source ressemble à ceci :

GLfloat diffise0= (1.0, 0.0, 0.0, 1.0);

GLfloat ambient0=(1.0, 0.0, 0.0, 1.0);

GLfloat spéculaire0=(1.0, 1.0, 1.0, 1.0);

GlActive(GL_LIGHTING);

GlActiver(GL_LIGHT0);

GlLightfv(GL_LIGHT0, GL_POSITION, light0_pos);

GlLightfv(GL_LIGHT0, GL_AMBIENT, ambient0);

GlLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse0);

GlLightfv(GL_LIGHT0, GL_SPECULAR, specular0);

Vous pouvez également inclure un éclairage d'arrière-plan global dans la scène, qui n'est associé à aucune source lumineuse particulière. Si, par exemple, vous souhaitez mettre en surbrillance faiblement tous les objets de la scène avec du blanc, vous devez inclure l'extrait de code suivant dans votre programme :

GLfloat global_ambient=(0.1, 0.1, 0.1, 1.0);

GlLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

Dans le modèle d'éclairage, le terme qui prend en compte la distance à la source a la forme :

K= 1/(a+ b*d+ c*d^2)

Et composantes constantes, linéaires et quadratiques. Les coefficients correspondants pour chaque source sont définis individuellement à l'aide de la fonction de paramétrage scalaire, par exemple :

GlLumièref(GL_LUMIÈRE0, GL_ATTENTION_CONSTANTE, une);

Pour convertir une source ponctuelle en projecteur, vous devez spécifier la direction du faisceau du projecteur (GL_SPOT_DIRECTION), l'indice de la fonction de distribution d'intensité (GL_SPOT_EXPONENT) et l'angle de propagation du faisceau (GL_SPOT_CUTTOF). Ces paramètres sont définis à l'aide des fonctions glLightf() et glLightfv().

Les paramètres définis pour les sources lumineuses par défaut sont indiqués dans le tableau 3.

Paramètres par défaut pour les lumières

Tableau 3

Le nom du paramètre Valeur par défaut Contenu
GL_AMBIENT (0.0, 0.0, 0.0, 1.0) intensité ambiante RGBA de la lumière
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) intensité lumineuse RGBA diffuse
GL_SPECULAR (1.0, 1.0, 1.0, 1.0) intensité spéculaire RGBA de la lumière
GL_POSITION (0.0, 0.0, 1.0, 0.0) (x, y, z, w) position du feu
GL_SPOT_DIRECTION (0.0, 0.0, -1.0) (x, y, z) direction du projecteur
GL_SPOT_EXPONENT 0.0 exposant de projecteur
GL_SPOT_CUTOFF 180.0 angle de coupure du projecteur
GL_CONSTANT_ATTENUATION 1.0 facteur d'atténuation constant
GL_LINEAR_ATTENUATION 0.0 facteur d'atténuation linéaire
GL_QUADRATIC_ATTENUATION 0.0 facteur d'atténuation quadratique

L'éclairage dans OpenGL ES est une fonctionnalité intéressante qui peut donner une touche agréable aux jeux 3D. Pour utiliser cette fonctionnalité, nous devons d'abord comprendre le modèle d'éclairage OpenGL ES.

Comment fonctionne l'éclairage

Réfléchissons au fonctionnement de l'éclairage. Nous avons d'abord besoin d'une source de lumière qui émet de la lumière. Vous aurez également besoin d'un objet lumineux. Enfin, il faut aussi un capteur, comme des yeux ou une caméra, qui capte les photons émis par la source lumineuse et réfléchis par l'objet. L'éclairage modifie la couleur perçue d'un objet en fonction : du type de source lumineuse ; la couleur ou l'intensité de la source lumineuse ; position de la source lumineuse et sa direction par rapport à l'objet éclairé ; matière et texture de l'objet.

L'intensité avec laquelle la lumière est réfléchie par un objet dépend de nombreux facteurs. Le facteur le plus important auquel nous prêtons attention est l'angle auquel le faisceau lumineux atteint la surface. Plus cet angle est proche d'un angle droit, plus l'intensité avec laquelle la lumière sera réfléchie par l'objet est grande. Ceci est illustré dans la fig. 11.1.

Une fois qu'un faisceau lumineux atteint une surface, il rebondit dans deux directions différentes. La majeure partie de la lumière sera réfléchie de manière diffuse. Cela signifie que les rayons lumineux réfléchis sont dispersés de manière inégale et aléatoire sur la surface de l'objet. Certains rayons sont réfléchis de manière spéculaire. Cela signifie que les rayons lumineux rebondiront comme s'ils frappaient un miroir parfait. Sur la fig. La figure 11.2 montre la différence entre les réflexions diffuses et spéculaires.

Riz. 11.1. Plus l'angle est proche d'un angle droit, plus l'intensité de la lumière réfléchie est grande.

Riz. 11.2. Réflexions diffuses et spéculaires

La réflexion spéculaire apparaîtra sous forme de surbrillance sur les objets. Que la lumière rebondisse sur un objet de manière spéculaire dépend du matériau dont il est fait. Les objets avec des surfaces inégales ou rugueuses ressemblant à de la peau sont plus susceptibles de ne pas avoir de reflets spéculaires. Les objets qui ont une surface lisse comme le verre ou le marbre montreront ces artefacts légers. Bien sûr, le verre ou le marbre ne sont pas parfaitement lisses, mais comparés au bois ou à la peau humaine, ils le sont.

Lorsque la lumière frappe une surface, sa réflexion change également de couleur en fonction de la composition chimique de l'objet éclairé. Les objets qui nous apparaissent rouges ne reflètent que des portions rouges de lumière et absorbent toutes les autres fréquences. Un objet noir est un objet qui absorbe presque toute la lumière qui le frappe.

OpenGL ES vous permet de simuler un comportement réel en définissant des sources de lumière et des matériaux d'objet.

Sources lumineuses

Nous sommes entourés d'une grande variété de sources lumineuses. Le soleil envoie constamment ses photons. Les moniteurs émettent une lumière qui nous entoure d'une lueur agréable la nuit. Les ampoules et les phares nous aident à éviter les collisions avec divers objets dans l'obscurité. OpenGL ES vous permet de créer quatre types de sources lumineuses.

Rétroéclairage. Ce n'est pas une source de lumière en soi, mais le résultat de l'apparition de photons provenant d'autres sources lumineuses. Ensemble, ces photons aléatoires créent un niveau d'éclairage constant qui n'a pas de direction et illumine tous les objets de la même manière.

Sources lumineuses ponctuelles. Ils ont une position dans l'espace et émettent de la lumière dans toutes les directions. Par exemple, une ampoule est une source ponctuelle de lumière.

Sources lumineuses directionnelles. Exprimé sous forme de directions dans OpenGL ES. Ils sont supposés être infiniment éloignés. Idéalement, le Soleil peut être une telle source. Nous pouvons supposer que tous les rayons lumineux provenant du Soleil frappent la Terre sous le même angle en raison de la distance entre la Terre et le Soleil.

Ah les lampes. Ces feux sont similaires aux feux ponctuels en ce sens qu'ils ont une position fixe dans l'espace. De plus, ils ont une direction dans laquelle ils émettent des rayons lumineux. Ils créent un cône de lumière limité par un certain rayon. Un exemple d'une telle source lumineuse est un réverbère.

Nous ne considérerons que le rétroéclairage, ainsi que les sources lumineuses ponctuelles et directionnelles. Les lumières sont souvent difficiles à utiliser sur les GPU limités des appareils Android en raison de la façon dont l'éclairage est calculé dans OpenGL ES. Vous comprendrez bientôt pourquoi il en est ainsi.

En plus de la position et de la direction de la source lumineuse, OpenGL ES permet de définir la couleur ou l'intensité de la lumière. Ces caractéristiques sont exprimées en utilisant la couleur RGBA. Cependant, OpenGL ES nécessite que quatre couleurs différentes soient définies pour la même source au lieu d'une seule.

Surbrillance - L'intensité/la couleur qui contribue à l'ombrage de l'objet. L'objet sera éclairé de manière égale dans toutes les directions, quelle que soit sa position ou son orientation par rapport à la source lumineuse.

Diffuse - l'intensité/couleur de la lumière qui illuminera l'objet après avoir calculé la réflexion diffuse. Les bords de l'objet qui ne font pas face à la source lumineuse ne seront pas éclairés, comme dans la vraie vie.

Spéculaire - intensité/couleur similaire à la couleur diffuse. Cependant, cela n'affecte que les points de l'objet qui ont une certaine orientation par rapport à la source lumineuse et au capteur.

L'émissif est un calcul de couleur très complexe qui a une utilisation extrêmement limitée dans les applications physiques du monde réel, nous ne le couvrirons donc pas ici.

Le plus souvent, on utilisera les intensités diffuse et spéculaire de la source lumineuse, et on précisera les valeurs par défaut pour les deux autres. De plus, la plupart du temps, nous utiliserons la même couleur RGBA pour l'intensité diffuse et spéculaire.

matériaux

Tous les objets de notre monde sont faits d'un certain matériau. Chaque matériau détermine comment la lumière frappant un objet sera réfléchie et changera la couleur de la lumière réfléchie. OpenGL ES vous permet de définir les quatre mêmes couleurs RGBA pour un matériau comme pour une source lumineuse.

Surbrillance - Une couleur qui se fond avec la couleur d'arrière-plan de n'importe quelle source de lumière dans la scène.

Diffuse - Une couleur qui se combine avec la couleur diffuse de n'importe quelle source lumineuse.

Spéculaire - Une couleur qui se combine avec la couleur spéculaire de n'importe quelle source de lumière. Il est utilisé pour créer des reflets sur la surface d'un objet.

Émissif - nous continuons à ignorer ce type de couleur, car il n'est pratiquement pas utilisé dans notre contexte.

La figure 11.3 illustre les trois premiers types de propriétés de matériau/lumière : contre-jour, diffus et spéculaire.

Riz. 11.3. Différents types de matériaux/lumières : rétro-éclairage uniquement (à gauche), diffus uniquement (au milieu), rétro-éclairage et couleur diffuse avec reflets spéculaires (à droite)

Sur la fig. 11.3 montre l'effet de diverses propriétés des matériaux et des sources lumineuses sur la couleur. Le rétroéclairage éclaire la taille uniformément. La lumière diffusée sera réfléchie en fonction de l'angle sous lequel les rayons lumineux tombent sur l'objet ; les zones qui font directement face à la source lumineuse seront éclairées plus fort, les zones que la lumière ne peut pas atteindre seront sombres. Dans l'image de droite, vous pouvez voir une combinaison de rétroéclairage, de lumière ambiante et spéculaire. La lumière spéculaire se manifeste par des reflets blancs sur la sphère.

Comment OpenGL ES calcule l'éclairage : Vertex Normals

Vous savez que l'intensité de la lumière réfléchie par un objet dépend de son angle d'incidence sur l'objet. OpenGL ES utilise ce fait pour calculer l'éclairage. Il utilise pour cela les normales des sommets, qui doivent être définies dans le code de la même manière que les coordonnées de texture ou les couleurs des sommets. Sur la fig. La figure 11.4 montre une sphère et ses normales aux sommets.

Riz. 11.4. La sphère et ses normales aux sommets

Les normales sont des vecteurs unitaires qui indiquent la direction à laquelle la surface fait face. Dans notre cas, la surface est un triangle. Au lieu de définir une normale de surface, nous définissons une normale de sommet. La différence entre ces normales est que la normale au sommet peut ne pas pointer dans la même direction que la normale à la surface. Ceci est clairement visible sur la Fig. 11.4, où chaque normale au sommet est la normale moyenne de tous les triangles auxquels le sommet appartient. Cette moyenne est effectuée pour créer un ombrage lisse de l'objet.

Lors du rendu d'un objet en utilisant l'éclairage et les normales des sommets, OpenGL ES déterminera l'angle entre chaque sommet et la source de lumière. S'il connaît cet angle, il peut calculer la couleur du sommet en fonction des propriétés du matériau. Le résultat final est la couleur de chaque sommet, qui est ensuite appliquée à chaque triangle en combinaison avec les couleurs calculées des autres sommets. Cette couleur utilisée sera combinée avec toutes les transformations de texture que nous appliquons à l'objet.

Cela semble assez effrayant, mais ce n'est en fait pas si mal que ça. Nous devons activer l'utilisation de l'éclairage et définir les sources de lumière, le matériau de l'objet rendu et les normales des sommets (en plus des paramètres de sommet que nous définissons normalement, tels que la position ou les coordonnées de texture). Voyons comment cela peut être fait avec OpenGL ES.

En pratique

Complétons maintenant toutes les étapes nécessaires pour travailler avec l'éclairage en utilisant OpenGL ES. Créons quelques petites classes d'assistance qui faciliteront un peu le travail avec les lumières et mettons-les dans le package com.badlogi avec .androi dgames.framework.gl.

Autorisation et interdiction d'éclairage

Comme pour les autres états OpenGL ES, la fonctionnalité nommée doit d'abord être activée. Cela peut être fait comme ceci :

Après cela, l'éclairage sera appliqué à tous les objets rendus. Pour obtenir le résultat, vous devez définir les sources lumineuses et les matériaux, ainsi que les normales aux sommets. Dès que nous avons fini de dessiner tous les objets nécessaires, l'éclairage peut être éteint :

Détermination des sources lumineuses

OpenGL ES fournit 4 types de sources lumineuses : rétroéclairage, ponctuel, directionnel et luminaire. Voyons comment identifier les trois premiers. Pour que les luminaires soient efficaces et beaux, chaque modèle doit être composé d'un grand nombre de triangles. Pour de nombreux appareils mobiles d'aujourd'hui, ce n'est pas possible.

OpenGL ES vous permet de définir un maximum de 8 lumières en même temps, ainsi qu'une source lumineuse globale. Chacune des 8 lumières a un ID, de GL10.GL LIGHT0 à GL10.GL LIGHT7. Si vous souhaitez modifier les propriétés d'une de ces lumières, vous pouvez le faire en définissant l'ID correspondant.

Vous pouvez activer les sources d'éclairage à l'aide de la syntaxe suivante :

Ensuite, OpenGL ES obtiendra les propriétés de cette source de lumière et les appliquera à tous les objets rendus. Si nous devons désactiver la source lumineuse, nous pouvons le faire avec la déclaration suivante :

La surbrillance est un cas particulier car elle n'a pas d'identifiant. Il ne peut y avoir qu'un seul surlignage par scène OpenGL ES. Considérons cette source de lumière plus en détail.

Rétroéclairage

Le rétroéclairage est un type particulier d'éclairage. Il n'a ni position ni direction, seulement une couleur qui s'applique à tous les objets éclairés de la même manière. OpenGL ES vous permet de définir une surbrillance globale comme celle-ci :

Le ambientCol ou array contient les valeurs RGBA de la couleur de la lumière, représentées sous forme de nombres à virgule flottante allant de 0 à 1. La méthode gl LightModel fv prend comme premier paramètre une constante qui spécifie que l'on veut définir la couleur du fond source de lumière, un tableau de nombres à virgule flottante A qui contient la couleur de la source et le décalage du tableau de flottants à partir duquel la méthode commencera à lire les valeurs RGBA. Plaçons le code qui résout ce problème dans une petite classe. Son code est présenté dans le Listing 11.2.

Liste 11.2. Classe AmbientLight.java. abstraction de mise en évidence globale OdenGL ES simple

Tout ce que nous faisons est de stocker la couleur de surbrillance dans un tableau de flottants et de fournir deux méthodes, l'une utilisée pour définir la couleur et l'autre pour indiquer à OpenGL ES d'utiliser cette couleur particulière. La couleur par défaut est le gris.

Sources lumineuses ponctuelles

Les lumières ponctuelles ont une position et un arrière-plan, une couleur/intensité diffuse et spéculaire (nous ne considérons pas la couleur/intensité émissive). Vous pouvez définir différents types de couleurs comme suit :

Le premier paramètre est l'identifiant de la source lumineuse. Dans ce cas, nous utilisons la quatrième source. Le paramètre suivant spécifie l'attribut que nous voulons changer. Le troisième paramètre est un tableau de flottants contenant les valeurs RGBA, et le dernier est un décalage dans ce tableau. Déterminer la position de la source est tout aussi simple :

Nous définissons à nouveau l'attribut que nous voulons changer (dans ce cas la position), un tableau de quatre éléments contenant les coordonnées x, y et z de la source de lumière dans le monde en cours de création. Notez que le quatrième élément du tableau doit être égal à un si la source lumineuse a une position. Mettons cela dans une classe d'assistance. Son code se trouve dans l'extrait 11.3.

Liste 11.3. Classe Point.Light.java , une simple abstraction de lumière ponctuelle OpenGL ES

Notre classe d'assistance contient les composants de couleur d'arrière-plan, diffus et spéculaire de la lumière, ainsi que la position (le quatrième élément est un). De plus, on stocke le dernier identifiant utilisé pour une source donnée, il devient donc possible de créer une méthode disableO qui éteindra la lumière si nécessaire. Nous avons également la méthode enableO, qui prend une instance de la classe GL10 et un ID de lumière (par exemple, GL10.GL LIGHT6). Il permet l'utilisation de l'éclairage, définit ses attributs et stocke l'identifiant utilisé. La méthode disableO désactive simplement l'utilisation de l'éclairage à l'aide du membre de classe 1ast.Ligh.tId défini dans la méthode enable.

Nous utilisons des valeurs par défaut raisonnables pour les couleurs d'arrière-plan, diffuses et spéculaires lors de l'initialisation des tableaux de membres de classe. La lumière sera blanche et ne créera aucun éblouissement car sa composante spéculaire est noire.

Sources lumineuses directionnelles

Les sources lumineuses directionnelles sont presque identiques aux sources ponctuelles. La seule différence est qu'ils ont une direction au lieu d'une position. La manière dont la direction est exprimée est quelque peu déroutante. Au lieu d'utiliser un vecteur pour indiquer la direction, OpenGL ES s'attend à ce que nous définissions un seul point. La direction sera alors déterminée à l'aide d'un vecteur reliant ce point et l'origine. L'extrait suivant vous permet de créer une lumière directionnelle provenant du côté droit du monde :

On peut le convertir en vecteur :

Les autres attributs, tels que l'arrière-plan ou la couleur ambiante, sont identiques à ceux d'une lumière ponctuelle. Le Listing 11.4 montre le code d'une petite classe d'assistance utilisée pour créer des lumières directionnelles.

Liste 11.4. La classe Directi onLi ght.java, une simple abstraction des lumières directionnelles dans OpenGL ES

Cette classe d'assistance est presque identique à la classe PointLight. La seule différence est que dans le tableau de direction, le quatrième élément est un. De plus, à la place de la méthode setPosition, la méthode setDirecti on est apparue. Il permet de spécifier la direction, par exemple : (-1 ; 0 ; 0), dans ce cas, la lumière viendra du côté droit. Dans la méthode, tous les composants vectoriels changent de signe, nous convertissons donc la direction au format attendu par OpenGL ES.

Nous définissons les matériaux

Un matériau est défini par plusieurs attributs. Comme pour tout autre objet OpenGL ES, un matériau est un état qui sera actif jusqu'à ce que nous le modifions à nouveau ou jusqu'à ce que le contexte OpenGL ES soit perdu. Pour définir les attributs de matériau actuels, nous pouvons procéder comme suit :

Comme d'habitude, nous devons définir l'arrière-plan RGBA, les couleurs diffuses et spéculaires. Cela peut être fait de la même manière qu'avant - en utilisant des tableaux de nombres à virgule flottante, composés de quatre éléments.

Combiner ces actions en une seule classe d'assistance est très simple. Vous pouvez voir le résultat dans le Listing 11.5.

Liste 11.5. Classe Java Material , une simple abstraction des matériaux OpenGL ES

Il n'y a là non plus rien d'étonnant. Nous gardons juste les trois composants qui décrivent le matériel, et fournissons des fonctions pour définir leurs valeurs et une méthode d'activation qui les transmet à OpenGL ES.

OpenGL ES a un autre tour dans sa manche en matière de matériaux. Il utilise généralement quelque chose appelé la couleur du matériau au lieu de la méthode glMaterialfvO. Cela signifie qu'au lieu des couleurs d'arrière-plan et diffuses définies par la méthode glMaterial al fv, OpenGL ES prendra la couleur des sommets de nos modèles comme couleurs d'arrière-plan et diffuses du matériau. Pour activer cette fonctionnalité, il vous suffit de l'appeler :

C'est généralement ce que je fais, car le fond et les couleurs diffuses sont souvent les mêmes. Comme je n'utilise pas de reflets spéculaires dans la plupart de mes jeux et démos, je peux facilement utiliser cette méthode et ne pas appeler glMaterial fv. La méthode que vous utilisez dépend de vous.

Définition des normales

Pour que l'éclairage fonctionne dans OpenGL ES, vous devez définir des normales de sommet pour chaque sommet du modèle. La normale au sommet doit être un vecteur unitaire pointant (généralement) dans la direction à laquelle la surface à laquelle appartient le sommet fait face. Sur la fig. La figure 11.5 illustre les normales aux sommets de notre cube.

Riz. 11.5. Normales aux sommets pour chaque sommet de notre cube

Une normale de sommet est un autre attribut d'un sommet, tout comme la position ou la couleur. Pour profiter des normales des sommets, nous devons modifier à nouveau la classe Vertices3. Afin d'indiquer à OpenGL ES où il peut trouver les normales pour chaque sommet, nous utiliserons la méthode gl Normal PointerQ, tout comme nous avons utilisé les méthodes gl VertexPointer ou gl Col ou Pointer plus tôt. Le Listing 11-6 montre la version finale de la classe Vertices3.

Liste 11.6. Classe Vertices3.Java, version finale prenant en charge les normales

La classe a un nouveau membre hasNormal.s qui garde une trace de si les sommets ont des normales.

Le constructeur accepte désormais également un paramètre hasNormals. Nous devons encore modifier le calcul du membre vertexSize en ajoutant si possible trois flottants par sommet.

Comme vous pouvez le voir, les méthodes setVertices et setIndices restent inchangées.

Dans la méthode bind que nous venons de démontrer, nous utilisons les mêmes astuces avec le tampon ByteBuffer qu'avant, mais cette fois nous ajoutons des normales en utilisant la méthode gl Normal Pointer. Pour calculer le décalage du pointeur normal, il est nécessaire de prendre en compte si les coordonnées de texture et les couleurs sont spécifiées.

Comme vous pouvez le voir, la méthode de dessin n'a pas changé non plus ; toute l'action se produit dans la méthode de liaison O.

Enfin, nous modifions un peu la méthode unbindQ. Nous désactivons l'utilisation des pointeurs normaux, le cas échéant, en effaçant l'état OpenGL ES en conséquence.

L'application de la classe Verti ces3 modifiée est aussi simple qu'avant. Prenons un petit exemple :

Nous créons un tableau de flottants pour contenir trois sommets, chacun avec une position (les trois premiers nombres sur chaque ligne) et une normale (les trois derniers nombres sur chaque ligne). Dans ce cas, nous définissons un triangle dans le plan xy, avec ses normales pointant dans la direction de l'axe z positif.

Tout ce que nous avons à faire est de créer une instance de la classe Vertices3 et de définir les valeurs des sommets. Plutôt facile, non ?

Tout le travail de liaison, de dessin et de déliaison est effectué exactement de la même manière que dans la version précédente de la classe. Comme précédemment, nous pouvons ajouter des couleurs de sommet et des coordonnées de texture.

Mettre tous ensemble

Mettons tout cela ensemble. Nous devons dessiner une scène avec un éclairage global, des sources lumineuses ponctuelles et directionnelles. Ils illumineront le cube situé à l'origine. Nous devons également appeler la méthode gl uLookAt. pour positionner la caméra. Sur la fig. 11.6 montre l'apparence de notre monde.

Comme pour tous les autres exemples, nous allons créer une classe qui s'appellera LightTest, comme d'habitude en étendant la classe GLGame. Il renverra une instance de la classe LightScreen en utilisant la méthode getStartScreenQ. La classe LightScreen hérite de la classe GLScreen (Listing 11-7).

Riz. 11.6. Notre première scène illuminée

Liste 11.7. Fragments de la classe LightTest.java. création d'éclairage avec OpenGL ES

Commençons par décrire quelques membres de la classe. Le membre angle stocke des informations sur la rotation actuelle du cube autour de l'axe y. Le membre Vertices3 stocke les sommets du modèle de cube, que nous définirons bientôt. De plus, nous avons des instances des classes AmbientLight, PointLight et Directional Light, ainsi qu'une instance de la classe Material.

Vient ensuite le constructeur. C'est là que les sommets du modèle de cube sont créés et que la texture de la boîte est également chargée. Nous initialisons également les lumières et les matériaux. La couleur d'éclairage est vert clair. La source directionnelle est rouge et se situe au point (3 ; 3 ; 0) de notre monde. La source lumineuse directionnelle a une couleur diffuse bleue, la lumière tombe de la gauche. Pour le matériau, utilisez les valeurs par défaut (quelque peu de fond, blanc pour la composante diffuse et noir pour la composante spéculaire).

Dans la méthode resume, nous nous assurons que notre texture est (re)chargée si le contexte est perdu.

La méthode createCube n'a pas beaucoup changé depuis les exemples précédents. Cependant, cette fois, nous ajoutons des normales pour chaque sommet, comme le montre la Fig. 11.5. A part ça, tout reste pareil.

Dans la méthode de mise à jour, nous augmentons simplement l'angle de rotation du cube.

C'est plus intéressant ici. Les premières lignes sont passe-partout pour effacer le tampon de couleur et de profondeur, activer les tests de profondeur et définir la portée.

Ensuite, nous définissons la matrice de projection égale à la matrice de projection en perspective à l'aide de la méthode gl uPerspective, et nous utilisons également la méthode gl uLookAt sur la matrice modèle-vue, de sorte que la caméra fonctionne de la même manière que sur la Fig. 11.6.

Ensuite, nous autorisons l'utilisation de l'éclairage. À ce stade, aucune lumière n'a encore été définie, nous les définissons donc dans les prochaines lignes avec un appel de méthode pour les lumières et les matériaux.

Comme d'habitude, nous activons également la texturation et lions la texture de la boîte. Enfin, nous appelons la méthode gl RotatefC) pour faire pivoter le cube puis dessiner ses sommets avec des appels d'instance bien placés à la classe Vertices3.

À la fin de la méthode, nous éteignons les lumières ponctuelles et directionnelles (rappelez-vous, la surbrillance est un état global), ainsi que les tests de texturation et de profondeur. C'est tout pour l'éclairage dans OpenGL ES.

Le reste de la classe est vide ; nous n'avons rien à faire en cas de pause. Sur la fig. 11.7 montre le résultat du programme.

Riz. 11.7. La scène représentée sur la Fig. 11.6 rendu avec OpenGL ES

Quelques notes sur l'éclairage dans OpenGL ES

Bien que l'utilisation de l'éclairage puisse ajouter du style à votre jeu, elle a ses limites et ses pièges. Il y a certaines choses dont vous devez être conscient.

L'utilisation de l'éclairage consomme trop de ressources, particulièrement visible sur les appareils lents. Appliquez l'éclairage avec soin. Plus vous décrivez de lumières, plus il faudra de calculs pour rendre la scène.

La position/direction des sources lumineuses ponctuelles/directionnelles doit être déterminée après le chargement des matrices de caméra et avant que la matrice modèle-vue ne soit multipliée par toute autre matrice pour déplacer et faire pivoter les objets. C'est essentiel. Le non-respect de ces directives peut entraîner des artefacts lumineux inexpliqués.

Lorsque vous utilisez la méthode gl Seal ef pour redimensionner un modèle, ses normales seront également mises à l'échelle. C'est mauvais car OpenGL ES s'attend à ce que les normales soient dans les unités données. Pour contourner ce problème, vous pouvez utiliser la commande glEnable(GL10.GL NORMALIZE) ou, dans certaines circonstances, gl Enable(GL10 .GL RESCALE N0RMAL). Je pense que la première commande doit être utilisée car la seconde a des limites et des pièges. Le problème est que la normalisation ou la remise à l'échelle des normales nécessite une grande puissance de traitement. La meilleure solution en termes de performances est de ne pas mettre à l'échelle les objets éclairés.

Dans cette leçon, nous allons apprendre à éclaircir et ombrager nos modèles 3D. Voici une liste de ce que nous allons apprendre :

  • Comment rendre un objet plus lumineux lorsqu'il est proche d'une source de lumière.
  • Comment faire des réflexions lorsque nous voyons de la lumière réfléchie sur un objet (éclairage spéculaire)
  • Comment faire apparaître un objet un peu sombre lorsque la lumière n'est pas directement sur l'objet (éclairage diffus)
  • Éclairage de scène (éclairage ambiant)
  • Ombres. Ce sujet mérite une leçon séparée (ou des leçons, sinon même des livres).
  • Miroir réflexion (ex. eau)
  • sous la surfacediffusion (par exemple, comme la cire)
  • Anisotrope matériaux (peint métal, par exemple)
  • Ombrage basé sur des processus physiques pour imiter encore mieux la réalité.
  • Blocage de la lumière (Occlusion ambiante si quelque chose bloque la lumière, elle devient plus sombre)
  • Réflexion de couleur (un tapis rouge fera apparaître un plafond blanc légèrement rougeâtre)
  • Transparence
  • Illumination globale (en principe, tout ce que nous avons indiqué ci-dessus peut être appelé ce terme)

En d'autres termes, l'éclairage et l'ombrage les plus simples.

Normales

Dans la dernière leçon, nous avons travaillé avec des normales, mais sans trop comprendre pourquoi elles sont nécessaires.

Normales du triangle

La normale à un plan est un vecteur unitaire perpendiculaire à ce plan.

La normale à un triangle est un vecteur unitaire dirigé perpendiculairement au triangle. La normale est très simplement calculée à partir du produit croisé des deux côtés du triangle (si vous vous en souvenez, le produit croisé de deux vecteurs nous donne un vecteur perpendiculaire aux deux) et normalisée : sa longueur est fixée à un.

Voici le pseudocode de calcul normal :

triangle(v1, v2, v3)
côté1=v2-v1
côté2=v3-v1
triangle.normal = vectProduct(côté1, côté2).normalize()

Normale au sommet

C'est la normale introduite pour la commodité des calculs. Il s'agit de la normale combinée des normales des triangles entourant le sommet donné. C'est très pratique, car dans les vertex shaders, nous avons affaire à des sommets, pas à des triangles. En tout cas, dans En OpenGL, nous n'avons presque jamais affaire à des triangles.

haut v1, v2, v3, ....
triangle tr1, tr2, tr3 // ils utilisent tous le sommet v1
v1.normal = normaliser(tr1.normal + tr2.normal + tr3.normal)

Utilisation des normales aux sommets dans OpenGL

L'utilisation des normales dans OpenGL est très simple. Une normale n'est qu'un attribut de sommet, tout comme la position, la couleur ou les coordonnées UV... Il n'y a donc rien de nouveau à apprendre... même notre simple fonction loadOBJ charge déjà les normales.

GLint tampon normal ;
glGenBuffers(1, &normalbuffer);

glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals, GL_STATIC_DRAW);

// Troisième tampon d'attribut : normales
glEnableVertexAttribArray(2);

glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // attribut
3, // taille
GL_FLOAT, //taper
GL_FALSE, // normalisé ?
0, // étape
(vide*)0 // décalage du tampon
);

Et c'est suffisant pour commencer :


éclairage diffus

L'importance de la normale de surface

Lorsqu'un faisceau lumineux frappe une surface, la plus grande partie est réfléchie dans toutes les directions. C'est ce qu'on appelle la "composante diffuse". Nous verrons le reste des composants un peu plus tard.

Après la chute du faisceau, la surface réfléchit la lumière de différentes manières, en fonction de l'angle sous lequel ce faisceau tombe sur la surface. Si le faisceau tombe perpendiculairement à la surface, alors il est concentré sur une petite surface, s'il est tangentiel, alors il est dispersé sur une surface beaucoup plus grande :


Du point de vue de l'infographie, la couleur d'un pixel est très dépendante de la différence des angles de la direction de la lumière et de la normale à la surface.


//
//
float cosTheta = point(n,l);

Dans ce code, "n" est la normale et "l" est le vecteur unitaire qui va de la surface à la lumière (et non l'inverse, bien que cela puisse sembler déroutant)

Attention au signe

Parfois, notre formule ne fonctionnera pas. Par exemple, lorsque la lumière est derrière le triangle, n et l seront opposés, donc n.l sera négatif. Et à la fin, nous aurons une sorte de couleur négative, et par conséquent, une sorte de non-sens. Par conséquent, nous convertirons tous les nombres négatifs en 0 en utilisant la fonction de serrage.

// Cosinus de l'angle entre la direction normale et la direction de la lumière
// 1 - si la lumière est perpendiculaire au triangle
// 0 - si la lumière est parallèle au triangle
// 0 - si la lumière est derrière le triangle
float cosTheta = clamp(dot(n, l), 0.1);
couleur = LightColor * cosTheta ;

Couleur du matériau

Bien sûr, la couleur de l'objet doit dépendre beaucoup de la couleur du matériau. La lumière blanche a trois composants - rouge, bleu et vert. Lorsque la lumière frappe une surface rouge, les composantes verte et bleue sont absorbées et le rouge est réfléchi.



Nous pouvons modéliser cela avec une simple multiplication :

couleur = MaterialDiffuseColor * LightColor * cosTheta ;

modelage léger

Supposons que nous ayons une source lumineuse ponctuelle qui émet de la lumière dans toutes les directions, comme une bougie.

Avec une telle source lumineuse, le niveau d'éclairement de la surface dépendra de la distance à la source lumineuse : plus elle est éloignée, plus elle est sombre. Cette dépendance se calcule ainsi :

couleur = MaterialDiffuseColor * LightColor * cosTheta / (distance*distance);

Bientôt, nous aurons besoin d'un paramètre supplémentaire pour contrôler le niveau d'intensité lumineuse - la couleur de la lumière, mais pour l'instant, supposons que nous ayons une ampoule blanche d'une certaine puissance (par exemple, 60 watts).

couleur = MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance);

Mettre tous ensemble

Pour que ce code fonctionne, nous avons besoin d'un certain ensemble de paramètres (couleurs et puissance) et d'un code supplémentaire.

MaterialDiffuseColor - nous pouvons le prendre directement à partir de la texture.

LightColor et LightPower devront être définis dans le shader à l'aide d'un uniforme GLSL.

CosTheta dépendra des vecteurs n et l. Il peut être calculé pour n'importe lequel des espaces, l'angle sera le même. Nous utiliserons l'espace de la caméra, car il est très facile de calculer la position de la source lumineuse ici :

// Fragment normal dans l'espace caméra
vec3 n = normaliser(Normal_cameraspace);
// Direction de la lumière (du fragment à la source lumineuse
vec3 l = normaliser(LightDirection_cameraspace);

Normal _cameraspace et LightDirection _cameraspace sont calculés dans le vertex shader et transmis au fragment shader pour un traitement ultérieur :

// Position du vertex dans l'espace caméra : MVP * position
gl_Position= MVP * vec4(vertexPosition_modelspace,1);
// Position du sommet dans l'espace univers : M * position
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz ;
// Vecteur qui vient du hautcaméra dans l'espace caméra
// Dans l'espace caméra, la caméra est à la position (0,0,0)
vec 3 vertexPosition _ espace caméra = ( V * M * vec 4( vertexPosition _ espace modèle ,1)). xyz ;
EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace ;
// Un vecteur qui va du sommet à la source de lumière dans l'espace de la caméra.
//Matrice M omis, puisqu'il est singulier dans cet espace.
vec3 LightPosition_cameraspace = (V * vec4(LightPosition_worldspace,1)).xyz ;
LightDirection_cameraspace = LightPosition_cameraspace +
EyeDirection_cameraspace ;
// Normal pics V espace appareils photo
Normal_cameraspace = (V * M * vec4(vertexNormal_modelspace,0)).xyz ; // Sera travail seul V volume cas , Quand matrice des modèles Pas changements son taille .

À première vue, le code peut sembler plutôt complexe et déroutant, mais en fait, il n'y a rien de nouveau ici qui n'était pas dans la leçon 3 : Matrices. J'ai essayé de donner des noms significatifs à chaque variable afin qu'il vous soit facile de comprendre ce qui se passe et comment.

Assurez-vous d'essayer !!!

M et V sont les matrices Model et View qui sont transmises au shader, tout comme notre bon vieux MVP.

Temps de test

Je vous ai dit tout ce qu'il faut savoir pour faire un éclairage diffus. Allez-y, essayez-le.

Résultat

Avec un seul composant diffus, nous obtenons une telle image ici (pardonnez-moi pour les textures laides).



Cela semble être mieux qu'avant, mais il manque encore beaucoup de choses. Le problème est particulièrement visible avec les pièces non éclairées. L'arrière de la tête de notre cher singe Suzanne est complètement noir (nous avons utilisé clamp() après tout).

Éclairage ambiant

L'éclairage ambiant est une pure tricherie.

L'arrière de la tête de Suzanne ne doit pas être complètement noir, car dans la vraie vie, la lumière de la lampe doit tomber sur le mur, le sol, le plafond, en refléter partiellement et éclairer la partie ombrée de l'objet.

Cependant, cela est trop coûteux en temps de calcul pour le faire en temps réel. Et c'est pourquoi nous allons ajouter une composante constante. C'est comme si l'objet lui-même émettait de la lumière pour ne pas être complètement noir.

vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor ;
couleur=
// Ambiant éclairage : simuler indirect éclairage
MatériauAmbientColor +
// diffuser : " couleur " l'objet lui-même
MaterialDiffuseColor * LightColor * LightPower * cosTheta /
(distance*distance);

Résultat

C'est là que ça s'améliore un peu. Vous pouvez jouer avec les coefficients (0,1, 0,1, 0,1) pour essayer d'obtenir le meilleur résultat.



Lumière réfléchie

La partie de la lumière qui est réfléchie est principalement réfléchie vers le faisceau réfléchi vers la surface.



Comme on peut le voir sur la figure, la lumière réfléchie forme une tache lumineuse. Dans certains cas, lorsque la composante diffuse est nulle, cette tache lumineuse est très très étroite (toute la lumière est complètement réfléchie dans une direction) et on obtient un miroir.

(cependant, bien que vous puissiez modifier les paramètres pour obtenir un miroir, dans notre cas, il ne prendra en compte que la réflexion de notre source de lumière. Il se révélera donc être un miroir étrange)


// vecteur de regard (vers la caméra)
vec3 E = normaliser(EyeDirection_cameraspace);
//Direction dans laquelle le triangle réfléchit la lumière
vec 3 R = refléter (- je , n );
// Cosinus de l'angle entre le vecteur de vue et le vecteur de réflexion ajusté à zéro si nécessaire
// - Regardez directement le reflet -> 1
// - Nous regardons quelque part dans l'autre sens -\u003e< 1
float cosAlpha = clamp(dot(E,R), 0.1);
couleur=
// Éclairage ambiant : simulez l'éclairage indirect
MatériauAmbientColor +
// diffuser : " couleur " l'objet lui-même
MaterialDiffuseColor * LightColor * LightPower * cosTheta /
(distance*distance) ;
// Reflected : reflets réfléchis comme un miroir
MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) /

Dans la prochaine leçon, nous analyserons comment nous pouvons accélérer le rendu de notre VBO.