IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre OpenGL moderne

Quatrième partie : OpenGL avancé


précédentsommairesuivant

VI. Texture cubique (cubemap)

Nous utilisons les textures 2D depuis quelque temps déjà, mais il existe d’autres types de texture que nous n’avons pas encore vu. Ce tutoriel explore un de ces autres types, qui est en réalité une combinaison de plusieurs textures assemblées en une : une texture cubique (cubemap).

Une texture cubique est une texture qui contient un ensemble de six textures 2D pouvant être appliqué sur chaque face du cube : un cube texturé. Vous pouvez vous demander à quoi cela sert : en effet, pourquoi s’ennuyer à assembler six textures en une seule entité alors qu’il est possible d’utiliser six textures séparément ? Eh bien, les textures de cube ont la particularité d’être indexables/échantillonnables grâce à un vecteur direction. Imaginez que nous avons une texture cubique de 1x1x1 avec comme origine un vecteur direction en son centre. L’échantillonnage d’une valeur de la texture cubique avec le vecteur orange donnera ceci :

Échantillon d'une carte de cube en OpenGL

La magnitude du vecteur direction n’est pas important. Tant que la direction est fournie, OpenGL récupère le texel que le vecteur direction atteint (in fine) et retourne la valeur de la texture appropriée.

Si nous imaginons un cube et que nous y attachons une texture cubique, le vecteur direction qui échantillonnera la texture sera similaire à une position (interpolée) sur le cube. De cette façon, nous pouvons échantillonner la texture cubique à l’aide de la position dans le cube tant que celui-ci est centré en son origine. Nous pouvons obtenir les coordonnées de texture de tous les sommets juste en utilisant la position du sommet du cube. Le résultat est une coordonnée de texture qui accède à la face correspondante dans la texture cubique.

VI-A. Créer une texture cubique

Une texture cubique est une texture comme toute autre, qu’il suffit de générer et de lier à la cible correspondante avant de faire plus d’opérations. Cette fois, nous lions au type GL_TEXTURE_CUBE_MAP :

 
Sélectionnez
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

Comme la texture cubique est composée de six textures, une par face, nous devons appeler six fois la fonction glTexImage2D() avec des paramètres similaires à ceux vus dans les précédents tutoriels. Cette fois, nous devons définir le paramètre de cible de la texture pour définir la face de la texture cubique. Cela indique à OpenGL sur quelle face nous souhaitons la texture que nous créons. Cela signifie que nous devons appeler glTexImage2D() pour chaque face de la texture cubique.

Comme nous avons six faces, OpenGL nous fournit six cibles :

Cible de texture

Orientation

GL_TEXTURE_CUBE_MAP_POSITIVE_X

Droite

GL_TEXTURE_CUBE_MAP_NEGATIVE_X

Gauche

GL_TEXTURE_CUBE_MAP_POSITIVE_Y

Haut

GL_TEXTURE_CUBE_MAP_NEGATIVE_Y

Bas

GL_TEXTURE_CUBE_MAP_POSITIVE_Z

Arrière

GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

Avant

Comme pour la majorité des énumérations OpenGL, c’est un int dont toutes les valeurs se suivent. Si nous devions avoir un tableau pour contenir les emplacements des textures, nous pourrions le parcourir en commençant par GL_TEXTURE_CUBE_MAP_POSITIVE_X et incrémenter l’index de 1 afin de boucler sur toutes les cibles de textures.

 
Sélectionnez
int width, height, nrChannels;
unsigned char *data;  
for(GLuint i = 0; i < textures_faces.size(); i++)
{
    data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}

Ici, nous avons un std::vector appelé textures_faces qui contient les emplacements de toutes les textures nécessaires pour la texture cubique. Cela génère une texture pour chaque face de la texture cubique liée.

Comme la texture cubique est une texture comme toute autre, nous devons aussi spécifier ses méthodes de filtrage :

 
Sélectionnez
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

Ne soyez pas impressionnés par le GL_TEXTURE_WRAP_R. Il définit simplement la méthode de dépassement pour la coordonnée R qui correspond à la troisième dimension de la texture (comme le z pour la position). Nous définissons la méthode de dépassement à GL_CLAMP_TO_EDGE comme les coordonnées de texture entre deux faces ne vont pas toujours atteindre une même face (à cause des limitations matérielles). Donc, en utilisant GL_CLAMP_TO_EDGE, OpenGL retournera toujours les valeurs de bordure lorsque nous échantillonnerons entre deux faces.

Avant de dessiner des objets utilisant notre texture cubique, nous devons activer l’unité de texture correspondante et lier la texture cubique. En réalité, c’est comme pour les textures 2D.

Dans le fragment shader, nous devons aussi utiliser un nouveau type d’échantillonneur samplerCube que nous utilisons conjointement avec la fonction texture(). Évidemment, les coordonnées seront dans un vecteur de type vec3 au lieu de vec2. Voici un exemple utilisant une texture cubique :

 
Sélectionnez
in vec3 textureDir; // vecteur direction représentant une coordonnée de texture 3D
uniform samplerCube cubemap; // échantillonneur pour une texture cubique

void main()
{             
    FragColor = texture(cubemap, textureDir);
}

C’est bien, mais pourquoi s’en soucier ? Eh bien, il existe quelques techniques intéressantes qui sont plus faciles à implémenter avec un texture cubique. Une de ces techniques est la création d’une skybox.

VI-B. Skybox

Une skybox est un (grand) cube qui englobe l’intégralité de la scène et contient six images de l’environnement l’englobant. Ainsi, le joueur a l’illusion d’être dans un environnement bien plus grand que ce qu’il est vraiment. Certains exemples de skybox de jeux vidéos possèdent des montagnes, des nuages ou un ciel étoilé. Dans la capture suivante du troisième opus d’Elder Scrolls, vous pouvez y voir une skybox de ciel étoilé :

Image de Morrowind avec une skybox

Vous le savez sûrement déjà, les textures cubiques sont la solution parfaite pour les skybox : nous avons un cube avec six faces qui a besoin d’une texture par face. Dans l’image précédente, les développeurs ont utilisé plusieurs images de ciel étoilé de nuit pour donner au joueur l’impression qu’il y a un univers bien plus grand que cette simple petite boite.

Il est facile de trouver des skybox en ligne. Ce site, par exemple, a un grand ensemble de skybox. Les images de skybox sont généralement conçues ainsi :

Image d'une skybox pour texture cubique dans OpenGL

Si vous pliez ces six carrés, vous obtenez un cube complètement texturé simulant un grand paysage. Certains sites fournissent les skybox dans une image comme celle-ci et vous devez extraire manuellement les six faces, mais dans la majorité des cas, vous aurez six images.

Cette skybox (de haute qualité) est celle que nous allons utiliser dans notre scène et peut être téléchargée ici.

VI-B-1. Chargement d’une skybox

Comme la skybox n’est qu’une texture cubique, le chargement ne diffère pas de ce que nous avons vu précédemment. Pour charger la skybox, nous allons utiliser la fonction suivante qui accepte un std::vector des six emplacements de texture :

 
Sélectionnez
unsigned int loadCubemap(vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

    int width, height, nrChannels;
    for (unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                         0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
            );
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}

La fonction ne doit pas être très surprenante. C’est grossièrement le code de chargement d’une texture cubique que nous avons vu dans la section précédente, mais intégré dans une unique fonction.

Avant d’appeler cette fonction, nous devons charger les chemins des textures dans le std::vector dans l’ordre de l’énumération OpenGL :

 
Sélectionnez
vector<std::string> faces;
{
    "right.jpg",
    "left.jpg",
    "top.jpg",
    "bottom.jpg",
    "front.jpg",
    "back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);

Nous allons maintenant charger la skybox dans une texture cubique avec la fonction cubemapTexture(). Nous pouvons lier la texture à un cube et enfin remplacer la couleur de fond que nous utilisions depuis tout ce temps.

VI-B-2. Afficher une skybox

Comme la skybox est affichée sur un cube, nous devons créer d’autres VAO et VBO, ainsi qu’un nouvel ensemble de sommets. Vous pouvez obtenir les données des sommets ici.

Utiliser une texture cubique pour texturer un cube 3D peut s’effectuer en utilisant la position du cube comme coordonnées de texture. Lorsque le cube est centré en (0, 0, 0), chaque point est aussi une direction depuis son origine. La direction est exactement ce dont nous avons besoin pour obtenir les valeurs correspondantes de la texture en cette position du cube. Pour cette raison, nous ne fournissons que les positions et nous ne fournissons pas de coordonnées de texture.

Pour afficher la skybox, nous allons avoir besoin d’un nouvel ensemble de shaders qui ne sont pas très compliqués. Comme nous n’avons que la position des sommets comme attribut, c’est plutôt simple :

 
Sélectionnez
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}

La partie intéressante du shader est que les positions en entrée sont copiées en sortie pour devenir des coordonnées de texture pour le fragment shader. Ce dernier les utilise pour échantillonner un samplerCube:

 
Sélectionnez
#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{    
    FragColor = texture(skybox, TexCoords);
}

Le fragment shader est évident. Nous prenons la position des sommets comme vecteur de direction pour la texture et les utilisons pour échantillonner les valeurs de la texture cubique.

Le rendu de la skybox est simple maintenant que nous avons la texture cubique. Nous lions simplement la texture et l’échantillonneur skybox est automatiquement connecté avec la texture cubique. Pour dessiner la skybox, nous allons la dessiner en premier et en désactivant les tests de profondeur. De cette façon, nous sommes certains que la skybox sera toujours au fond, derrière tous les autres objets.

 
Sélectionnez
glDepthMask(GL_FALSE);
skyboxShader.use();
// ... définir la matrice de vue et projection
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
// ... dessiner le reste de la scène

Si vous exécutez ce code en l’état, vous allez avoir des problèmes. Nous souhaitons avoir une skybox centrée autour du joueur, peu importe ses déplacements. La skybox ne s’approchera pas du joueur, ce qui donnera l’impression d’un environnement immense. La matrice de vue actuelle transforme les sommets de la skybox en les tournant, en les redimensionnant et en les déplaçant. Par conséquent, si le joueur se déplace, la skybox se déplace aussi ! Nous souhaitons supprimer le déplacement de la matrice de vue afin que le mouvement n’affecte pas la position de la skybox

Souvenez-vous du tutoriel d’éclairage basique, où nous apprenions que l’on peut supprimer la translation en ne gardant que la partie 3x3 supérieure gauche d’une matrice 4x4. Nous pouvons faire cela en convertissant la matrice de vue en une matrice 3x3 (supprimant ainsi la translation) et en la reconvertissant en matrice 4x4 :

 
Sélectionnez
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));

Cela enlève toute translation, tout en gardant les autres transformations, ce qui permet ainsi au joueur de regarder autour de lui.

Le résultat rend la scène immense grâce à la skybox. Si vous naviguez autour du conteneur, vous avez une notion de sa taille, ce qui augmente le réalisme. Le résultat doit être celui-ci :

Image de la skybox dans une scène OpenGL

Essayez d’expérimenter avec différentes skybox et de voir leur impact sur l’aspect de la scène.

VI-B-3. Optimisation

Pour le moment, nous avons dessiné la skybox avant tous les autres objets de la scène. Cela fonctionne très bien, mais ce n’est pas efficace. Si nous dessinons la skybox en premier, nous exécutons le fragment shader pour chaque pixel de l’écran, même si certaines parties de la skybox seront cachées. Les fragments qui auraient pu être ignorés pourraient nous aider à gagner en bande passante.

Pour obtenir un léger gain de performance, nous allons donc dessiner la skybox en dernier. De cette façon, le tampon de profondeur est complètement repli avec les valeurs de profondeur de chaque objet. Ainsi, nous ne dessinerons que les pixels de la skybox qui seront réellement visibles, c’est-à-dire, ceux qui passent le test de profondeur. Le problème est que la skybox risque de ne pas s’afficher, car ce n’est qu’un cube 1x1x1 qui échouera à tous les tests de profondeur. Son affichage sans test de profondeur ne résout pas le problème, car la skybox recouvrera tous les objets de la scène. Nous devons tromper le tampon de profondeur afin de faire croire que la skybox a la profondeur maximale (soit 1,0), de telle sorte qu’elle échouera à tous les tests où la profondeur est différente, c’est-à-dire lorsqu’il y a un autre objet devant.

Dans le tutoriel sur les systèmes de coordonnées, nous avons expliqué que la division de perspective est exécutée après l’exécution du vertex shader. Elle divise les coordonnées xyz de gl_Position par la composante w. Nous savons aussi, grâce au tutoriel sur le test de profondeur, que la composante z résultant de la division est égale à la valeur de la profondeur du sommet. Grâce à cette information, nous pouvons définir la composante z de la position de sortie égale à la composante w. Ainsi, la division de w/z donne toujours 1,0, car la division devient w/w = 1,0.

 
Sélectionnez
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
}

Le résultat obtenu en coordonnées normalisées sera toujours une valeur z égale à 1, soit la valeur de profondeur maximale. La skybox ne sera dessinée qu’aux emplacements où il n’y a pas d’objet visible (seuls ceux qui passent le test de profondeur, le reste du monde est devant la skybox).

Nous devons changer la fonction du test de profondeur pour le définir à GL_LEQUAL au lieu de GL_LESS. Le tampon de profondeur sera toujours rempli avec la valeur 1,0 pour la skybox, nous devons donc nous assurer que la skybox réussisse le test de profondeur pour les valeurs plus petites ou égales à celle dans le tampon de profondeur.

Vous pouvez trouver cette version optimisée ici.

VI-C. Application de l’environnement

Nous avons maintenant un environnement englobant grâce à l’application d’une simple texture. Nous pouvons aussi utiliser les textures cubiques pour d’autres effets. En particulier, elles nous permettent d’appliquer des propriétés de réflexion et de réfraction aux objets. Ces techniques qui utilisent une texture cubique d’environnement s’appellent « application de l’environnement » (environment mapping) et les deux plus courantes sont la réflexion et la réfraction.

VI-C-1. Réflexion

La propriété de réflexion fait qu’un objet (ou une partie d’un objet) reflète son environnement, c’est-à-dire, les couleurs de l’objet sont plus ou moins basées sur l’environnement l’englobant et l’angle du spectateur. Un miroir est un exemple d’objet reflétant : il reflète son environnement suivant la position du spectateur.

La réflexion de base n’est pas difficile. L’image suivante montre comment calculer un vecteur de réflexion et utilise ce vecteur pour échantillonner la texture cubique :

Image montrant comment calculer le reflet

Nous calculons un vecteur réfléchissant kitxmlcodeinlinelatexdvp\color{green}{\bar{R}}finkitxmlcodeinlinelatexdvp grâce à la normale de l’objet kitxmlcodeinlinelatexdvp\color{red}{\bar{N}}finkitxmlcodeinlinelatexdvp et la direction du spectateur kitxmlcodeinlinelatexdvp\color{gray}{\bar{I}}finkitxmlcodeinlinelatexdvp. Nous calculons le vecteur de réflexion grâce à la fonction GLSL reflect(). Le vecteur kitxmlcodeinlinelatexdvp\color{green}{\bar{R}}finkitxmlcodeinlinelatexdvp résultant est utilisé comme direction pour indexer/échantillonner la texture cubique et ainsi obtenir la couleur de l’environnement. L’effet final est que l’objet semble réfléchir la skybox.

Comme nous avons déjà configuré la skybox pour notre scène, la création des reflets n’est pas très compliquée. Nous changeons le fragment shader utilisé par le conteneur pour lui donner des propriétés réfléchissantes :

 
Sélectionnez
#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 Position;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main()
{             
    vec3 I = normalize(Position - cameraPos);
    vec3 R = reflect(I, normalize(Normal));
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

D’abord, nous calculons le vecteur direction de la caméra I. Celui-ci permet d’obtenir le vecteur de réflexion R, qui est ensuite utilisé pour échantillonner la texture cubique de la skybox. Notez que les variables interpolées Normal et Position du fragment shader doivent être modifiées.

 
Sélectionnez
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 Position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    Position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

Nous utilisons les normales, nous devons donc les transformer avec la matrice des normales. Le vecteur de sortie Position est un vecteur dans l’espace monde. Cette sortie est utilisée pour calculer la direction du spectateur dans le fragment shader.

Comme nous utilisons les normales, nous souhaitons mettre à jour les données des sommets et les pointeurs d’attributs. Assurez-vous de définir la variable uniforme cameraPos.

Ensuite, nous souhaitons lier la texture cubique avant d’afficher le conteneur :

 
Sélectionnez
glBindVertexArray(cubeVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);          
glDrawArrays(GL_TRIANGLES, 0, 36);

Compilez et exécutez votre code afin d’obtenir un conteneur réfléchissant comme un miroir. La skybox est parfaitement reflétée sur le conteneur :

Image d'un cube reflétant la skybox grâce à la texture cubique et l'application de l'environnement

Vous pouvez trouver le code source ici.

Lorsque la réflexion est appliquée à l’intégralité d’un objet (comme le conteneur), l’objet semble être un matériau hautement réfléchissant comme de l’acier ou du chrome. Si nous chargeons le modèle nanosuit utilisé dans le tutoriel sur le chargement d’objet 3D, nous aurions un effet faisant croire que la combinaison est en chrome :

Image de la nanosuit de Crysis reflétant la skybox grace à la texture cubique et l'application de l'environnement

Cela est génial, mais, en réalité, la plupart des modèles ne sont pas complètement réfléchissants. Nous pouvons, par exemple, utiliser des textures de réflexion offrant des détails supplémentaires au modèle. Tout comme pour la texture diffuse et spéculaire, la texture de réflexion est une image qui est échantillonnée pour déterminer la propriété réfléchissante du fragment. Ainsi, nous pouvons déterminer quelle partie du modèle doit réfléchir l’environnement et avec quelle intensité. Dans les exercices de ce tutoriel, c’est à vous d’ajouter une texture de réflexion pour améliorer les détails de ce modèle

VI-C-2. Réfraction

Une autre technique d’application de l’environnement s’appelle la réfraction et s’apparente à la réflexion. La réfraction change la direction de la lumière, à cause du changement de matière traversée par celle-ci. La réfraction se voit généralement avec l’eau, où la lumière ne traverse pas le milieu aqueux en ligne droite. C’est comme regarder son bras lorsqu’il est à moitié dans l’eau.

La réfraction est décrite par la loi de Snell, qui ressemble à cela avec texture d’environnement :

Image expliquant la réfraction de la lumière pour les textures cubiques

Encore une fois, nous avons le vecteur du spectateur kitxmlcodeinlinelatexdvp\color{gray}{\bar{I}}finkitxmlcodeinlinelatexdvp, et une normale kitxmlcodeinlinelatexdvp\color{red}{\bar{N}}finkitxmlcodeinlinelatexdvp qui permettent d’obtenir un vecteur de réfraction kitxmlcodeinlinelatexdvp\color{green}{\bar{R}}finkitxmlcodeinlinelatexdvp. Comme vous pouvez le voir, la direction du vecteur du spectateur est légèrement déviée. Le vecteur dévié kitxmlcodeinlinelatexdvp\color{green}{\bar{R}}finkitxmlcodeinlinelatexdvp est utilisé pour échantillonner la texture cubique.

La réfraction peut être facilement implémentée grâce à la fonction GLSL refract(), qui prend une normale, la direction du spectateur et un ratio entre les deux indices de réfraction des matériaux.

L’indice de réfraction est la quantité de lumière déviée par le matériau. Chaque matériau a son propre indice de réfraction. Voici une liste des indices les plus communs :

Matériau

Indice de réfraction

Air

1,00

Eau

1,33

Glace

1,309

Verre

1,52

Diamant

2,42

Nous utilisons ces indices pour calculer le ratio de lumière passant entre deux matériaux. Dans notre cas, la lumière/rayon du spectateur passe de l’air au verre (si nous prenons un conteneur en verre). Le ratio est donc :

kitxmlcodelatexdvp\frac{1.00}{1.52} = 0.658finkitxmlcodelatexdvp

Nous avons déjà la texture cubique liée, les données des sommets avec leurs normales et la définition de la caméra dans une variable uniforme. Il ne reste plus qu’à changer le fragment shader :

 
Sélectionnez
void main()
{             
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

En changeant l’indice de réfraction, vous pouvez créer des résultats très différents. La compilation et l’exécution de l’application n’a que peu d’intérêt, car nous n’utilisons qu’un simple conteneur. Il n’expose pas assez bien la réfraction qui n’agit que comme loupe. Pas contre, en utilisant les mêmes shaders et le modèle de nanosuit, nous obtenons bien un objet semblant être en verre.

Image d'application de l'environnement et de la réfraction en OpenGL

Vous pouvez imaginer qu’avec la bonne combinaison de lumière, réflexion, réfraction ainsi qu’en déplaçant les sommets, vous pouvez créer un effet d’eau vraiment joli. Notez que, pour obtenir des résultats physiquement réalistes, nous devrions réfracter la lumière lorsqu’elle sort de l’objet. Pour le moment, nous utilisons juste une réfraction dans un seul sens, ce qui est suffisant pour la plupart des cas.

VI-C-3. Application de l’environnement dynamique

Jusqu’à présent, nous avons utilisé un ensemble d’images statiques pour la skybox. C’est bien, mais cela n’inclut pas les autres objets de la scène, qui peuvent se déplacer. Nous ne l’avons pas remarqué, car nous n’utilisons qu’un seul objet. Si nous avions plusieurs objets réfléchissant d’autres objets aux alentours, seule la skybox serait visible dans le miroir, comme si c’était l’unique objet de la scène.

Grâce au tampon d’image, il est possible de créer une texture de la scène avec les six différents angles aperçus de l’objet et de stocker cela dans une texture cubique à chaque itération. Nous pouvons ensuite l’utiliser (générée dynamiquement) pour créer des surfaces réfléchissantes et réfringentes qui incluent tous les objets. Cela s’appelle une texture d’environnement dynamique, car nous créons la texture cubique dynamiquement et nous l’utilisons comme texture d’environnement.

Même si c’est beau, cela a un gros désavantage : nous devons afficher la scène six fois par objet qui utilise une texture d’environnement. Les applications modernes essaient d’utiliser la skybox autant que possible et des textures cubiques pré-générées pour obtenir un effet similaire. Bien que les textures d’environnement dynamiques soient une bonne technique, cela nécessite beaucoup d’astuces et de hacks pour réussir à l’appliquer sans avoir de gros défaut de performances.

VI-D. Exercices

  • Essayez d’ajouter une texture de réflexion dans le chargement du modèle que nous avons créé au cours du tutoriel sur le chargement. Vous pouvez trouver un modèle de nanosuit à jour, avec texture de réflexion, ici. Il y a quelques points à noter :

    • Assimp n’aime pas les textures de réflexion dans la plupart des formats de modèle 3D. Nous avons donc triché en l’implémentant comme une texture ambiante. Vous pouvez charger la texture de réflexion en spécifiant aiTextureType_AMBIENT comme type de texture lors du chargement des matériaux ;
    • je me suis dépêché de créer les textures de réflexion à partir des textures spéculaire. Du coup, la texture de réflexion ne correspondra pas parfaitement au modèle :) ;
    • comme le chargement du modèle prend déjà trois unités de texture dans le shader, vous devez charger la skybox dans une quatrième unité, car vous devez échantillonner la skybox dans un même shader que les autres textures.
  • Si vous avez réussi, cela devrait vous donner ceci.

VI-E. Remerciements

Ce tutoriel est une traduction réalisée par Alexandre Laurent dont l’original a été écrit par Joey de Vries et qui est disponible sur le site Learn OpenGL.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Joey de Vries. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.