Navigation

Tutoriel précédent : surbrillance spéculaire et chargement de modèle JSON   Sommaire   Tutoriel suivant : du rendu à la texture

II. Introduction

Bienvenue dans ma quinzième leçon de la série WebGL ! Dans celle-ci, nous allons aborder les textures spéculaires, qui donnent un effet plus réaliste aux scènes en facilitant la définition de la réflexion d'un objet en chacun de ses points, tout comme les textures normales vous permettent de spécifier leur nuance de couleur. Du point de vue du code, c'est en fait une extension assez simple sur ce que nous avons déjà couvert, mais conceptuellement, c'est un pas en avant.

Voici à quoi ressemble la leçon lorsqu'elle est exécutée sur un navigateur qui prend en charge WebGL :



Cliquez ici pour voir la version live WebGL si vous avez un navigateur qui la prend en charge ; voici comment en obtenir un si vous n'en avez pas. Vous allez voir la Terre en rotation, avec un reflet spéculaire très lumineux - celui du soleil sur sa surface. Plus important encore, si vous regardez attentivement, vous verrez que le reflet spéculaire apparaît uniquement sur les océans. La terre, comme vous vous en doutez, ne reflète pas la lumière de façon spéculaire.

Essayez de désactiver la case « Utiliser la texture spéculaire » sous le canvas. Vous verrez que maintenant la lumière spéculaire apparaît aussi sur la terre, vous aurez (comme moi) aussi l'impression que ceci gâche l'illusion et donne au reflet spéculaire un aspect de spot très brillant sur la Terre. Les textures spéculaires peuvent grandement améliorer le réalisme en vous donnant un contrôle précis sur la façon dont les objets de votre modèle interagissent avec l'éclairage.

Réactivez ensuite la case « Utiliser la texture spéculaire », réduisez l'intensité de la lumière diffuse jusqu'à, disons (0.5, 0.5, 0.5), puis désactivez la case « Utiliser la texture de couleurs ». Vous verrez que la texture de couleurs de la Terre n'est plus appliquée, contrairement à la lumière spéculaire toujours présente sur les parties terrestre et maritime.

Alors, comment ceci marche ? Lisez la suite pour le découvrir.

Ces leçons sont destinées aux personnes ayant de bonnes connaissances en programmation, mais sans réelle expérience dans la 3D, le but est de vous donner les moyens de démarrer, avec une bonne compréhension de ce qui se passe dans le code, afin que vous puissiez commencer à produire vos propres pages Web 3D aussi rapidement que possible. Si vous n'avez pas lu les tutoriels précédents, vous devriez le faire avant de lire celui-ci. Je n'expliquerai ici que les nouveaux éléments. Cette leçon est basée sur la quatorzième, assurez-vous donc de l'avoir bien comprise.

Il y a deux façons d'obtenir le code de cet exemple, il suffit d'« Afficher la source » pendant que vous visionnez la version live, ou si vous utilisez GitHub, vous pouvez le cloner (ainsi que les autres leçons) à partir du dépôt.

III. Leçon

Avant de regarder le code, il est bon d'expliquer le contexte. Jusqu'ici, nous avons considéré les textures comme un bon moyen, simple, « d'habiller » les objets 3D avec des images. Nous spécifions une image et notre objet possède des coordonnées par vertex désignant où doit se trouver chaque partie de l'image. Donc quand nous dessinons chaque pixel de l'objet dans notre fragment shader, nous pouvons déterminer que telle partie de l'image correspond à telle autre de l'objet, de prendre sa couleur depuis l'échantillonneur (« sampler ») qui représente la texture dans le shader, et l'utiliser en tant que couleur de l'objet en ce point.

Les textures spéculaires poussent cette logique un pas plus loin. La couleur d'un point dans la texture est, bien sûr, spécifiée par les composantes rouge, vert, bleu et alpha. Dans le shader, chacune d'elles est un nombre en virgule flottante. Mais il n'y a pas de raison particulière empêchant de les utiliser comme valeurs de couleur. Vous vous souvenez de la dernière leçon où la brillance d'un matériau est déterminée par un seul nombre à virgule flottante. Il n'y a ainsi aucune raison empêchant d'utiliser des textures comme un moyen de faire passer une « texture de brillance » (« shininess map ») au fragment shader, tout comme nous les utilisons pour faire passer une texture de couleurs.

C'est donc l'astuce que nous utilisons dans cet exemple. Deux textures distinctes sont passées au fragment shader pour les appliquer sur la Terre ; une texture de couleurs qui ressemble à ceci :

Image non disponible

… est une texture spéculaire basse résolution comme ceci :

Image non disponible

C'est un fichier GIF ordinaire que j'ai créé avec Paint.NET en utilisant la texture de couleurs comme point de départ. J'ai décidé de mettre les composantes rouge, vert et bleu de couleur de chaque point à la même valeur : la brillance du point. J'ai eu également besoin de choisir une valeur signifiant « cet endroit de la Terre n'est pas brillant ». Parce que la majorité de l'image était assez sombre (une brillance de 32 correspond à la couleur RGB 32, 32, 32, qui est un gris très foncé), j'ai décidé d'utiliser du blanc pur pour cela.

Donc, toutes explications faites, passons au code. Les différences entre le code de cette leçon et la quatorzième sont en fait assez minimes et assez claires. Comme on pouvait s'y attendre, les changements vraiment importants sont dans le fragment shader, nous allons donc y jeter un œil (avec les changements mis en évidence, comme d'habitude).

Comme premier changement, nous avons un ensemble de nouvelles variables uniformes spécifiant si nous souhaitons utiliser ou non les textures de couleurs et spéculaire :

 
Sélectionnez
  precision mediump float;

  varying vec2 vTextureCoord;
  varying vec3 vTransformedNormal;
  varying vec4 vPosition;

  uniform bool uUseColorMap;    // Nouveau
  uniform bool uUseSpecularMap; // Nouveau
  uniform bool uUseLighting;

Ensuite, nous avons des variables uniformes de type sampler pour les deux textures. Nous avons renommé l'ancienne uSampler qui était utilisée pour la texture de couleurs en uColorMapSampler, et en avons ajouté une nouvelle pour l'échantillonneur qui représente la texture spéculaire.

 
Sélectionnez
  uniform vec3 uAmbientColor;

  uniform vec3 uPointLightingLocation;
  uniform vec3 uPointLightingSpecularColor;
  uniform vec3 uPointLightingDiffuseColor;

  uniform sampler2D uColorMapSampler;    // Nouveau
  uniform sampler2D uSpecularMapSampler; // Nouveau

Vient ensuite notre code passe-partout habituel pour traiter le cas où l'utilisateur souhaite désactiver les effets d'éclairage, et le calcul de la normale et de la direction de la lumière dans le cas contraire :

 
Sélectionnez
  void main(void) {
    vec3 lightWeighting;
    if (!uUseLighting) {
      lightWeighting = vec3(1.0, 1.0, 1.0);
    } else {
      vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
      vec3 normal = normalize(vTransformedNormal);

Nous passons enfin au code qui gère la texture spéculaire. Tout d'abord, nous définissons une variable qui contiendra la quantité d'éclairage spéculaire à appliquer, qui sera bien sûr à zéro si le morceau que nous considérons s'avère n'avoir aucun éclairage spéculaire après avoir fait tous les calculs.

 
Sélectionnez
      float specularLightWeighting = 0.0;

Ensuite, nous voulons déterminer la brillance de ce morceau du matériau. Si l'utilisateur a spécifié qu'il ne fallait pas utiliser la texture spéculaire, ce code fait le choix (essentiellement arbitraire) d'utiliser la valeur 32.0. Sinon, on récupère la valeur à partir de la texture spéculaire pour les coordonnées de texture du fragment, tout comme nous avons précédemment trouvé la valeur de la texture de couleurs. Maintenant, pour chaque image, notre texture spéculaire a la brillance stockée dans les trois composantes de couleur - rouge, vert et bleu de même valeur, raison pour laquelle elles apparaissent en niveaux de gris lorsqu'elles sont affichées en tant qu'image. Nous utilisons le rouge dans ce code, mais on aurait pu utiliser n'importe laquelle des deux autres si nous avions voulu :

 
Sélectionnez
      float shininess = 32.0;  // Nouveau
      if (uUseSpecularMap) {   // Nouveau
        shininess = texture2D(uSpecularMapSampler,   vec2(vTextureCoord.s, vTextureCoord.t)).r * 255.0;  // Nouveau
      }  // Nouveau

Maintenant, vous vous souvenez que nous avions besoin de pouvoir dire « cet endroit du matériau n'est pas brillant » dans la texture spéculaire, et que j'ai choisi d'utiliser le blanc qui contrastait bien avec les zones gris foncé pour les parties les plus brillantes de la Terre. Nous n'effectuons donc aucun calcul pour l'éclairage spéculaire si la valeur de la brillance extraite de la texture est supérieure ou égale à 255.

 
Sélectionnez
      if (shininess < 255.0) { // Nouveau

Le morceau de code suivant contient le même calcul de lumière spéculaire que celui de la dernière leçon, sauf que nous utilisons la constante de brillance prélevée dans la texture :

 
Sélectionnez
        vec3 eyeDirection = normalize(-vPosition.xyz);
        vec3 reflectionDirection = reflect(-lightDirection, normal);

        specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);   // Nouveau
      }  // Nouveau

Finalement, nous ajoutons les différentes contributions de l'éclairage ensemble et les utilisons pour pondérer la couleur du fragment, qu'elles viennent de la texture de couleurs ou, si uUseColorMap est à false, juste de la constante blanche.

 
Sélectionnez
      float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
      lightWeighting = uAmbientColor
        + uPointLightingSpecularColor * specularLightWeighting
        + uPointLightingDiffuseColor * diffuseLightWeighting;
    }

    vec4 fragmentColor;
    if (uUseColorMap) {   // Nouveau
      fragmentColor = texture2D(uColorMapSampler, vec2(vTextureCoord.s, vTextureCoord.t));
    } else {
      fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
    }
    gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
  }

Si vous êtes arrivé jusqu'ici, vous comprenez tout ce qu'il y a à savoir sur le code de cette leçon. Il y a d'autres changements, mais aucun qui ne vaille la peine d'être parcouru de façon détaillée. initShaders contient du code nouveau pour gérer les nouvelles variables uniformes ; initTextures a besoin de charger les deux nouvelles textures, le code qui a servi à charger la théière est remplacé par une fonction initBuffers comme dans la onzième leçon ; drawScene dessine une sphère à la place de la théière et récupère les valeurs dans les champs d'entrée d'utilisateur en dessous du canvas pour les mettre dans les variables uniformes appropriées ; animate a été mis à jour pour faire tourner la Terre.

Et après cela, c'est fini ! Vous savez maintenant comment utiliser une texture pour fournir des informations sur la brillance spéculaire d'un objet. Comme vous êtes sans doute passé par cette étape, rien ne vous empêche d'utiliser des textures pour passer d'autres informations précises à vos shaders - des techniques similaires à celle-ci permettent de transmettre des textures contenant les variations des normales de la surface, ce qui vous permet d'avoir des surfaces avec un fin relief sans avoir à créer de nombreux vertex. Nous regarderons cela de plus près dans une prochaine leçon.

IV. Remerciements

La texture de la Terre provient de l'agence spatiale européenne Envisat. Merci également à Paul Brunt pour sa suggestion d'augmenter les niveaux de lumière spéculaire pour obtenir la page de démonstration plus claire.

Merci à LittleWhite pour sa relecture attentive et ClaudeLELOUP pour sa relecture orthographique.

Navigation

Tutoriel précédent : surbrillance spéculaire et chargement de modèle JSON   Sommaire   Tutoriel suivant : du rendu à la texture