WebGL leçon 16 : du rendu à la texture

| Giles Thomas | Alexandre Laurent |

Traducteur

Jérôme Marsaguet

Dans ce tutoriel, vous allez apprendre à dessiner une scène dans une texture vous permettant par la suite l'implémentation de nombreuses techniques avancées.

Commentez

- Titre : WebGL leçon 16 : du rendu à la texture

- Auteurs : Giles Thomas, Alexandre Laurent

- Traducteur : Jérôme Marsaguet

- Parution : 22 août 2013

- Licence : 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 ® 2013 Gilles Thomas. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Sommaire

- Navigation

- II. Introduction

- III. Leçon

- IV. Remerciements

- Navigation

Navigation

Tutoriel précédent : les textures spéculaires

Sommaire

II - Introduction

Bienvenue dans ma seizième leçon de la série WebGL ! Dans celle-ci, nous allons aborder une technique très utile : dessiner une scène 3D dans une texture, que nous pouvons par la suite utiliser comme entrée pour effectuer le rendu d'une autre scène. C'est une super astuce, car non seulement elle permet d'avoir des scènes dans des scènes, comme dans la page de démonstration de ce tutoriel, mais aussi parce qu'elle est le fondement nécessaire à l'ajout de picking (sélection des objets 3D avec la souris), d'ombres, de réflexions, et de nombreux autres effets 3D.

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

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/watch?v=avFT1om0mmU

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 apercevoir le modèle d'un ordinateur portable blanc, avec tous les effets lumineux abordés dans les leçons précédentes (y compris une lueur spéculaire sur son écran). Mais, plus intéressant, sur l'écran de l'ordinateur portable, vous verrez une autre scène 3D affichée - la lune et la caisse en orbite de la démonstration de la treizième leçon. Je pense qu'il est évident que dans cette page, nous effectuons le rendu de la scène de la treizième leçon dans une texture, puis utilisons cette texture sur l'écran de l'ordinateur portable.

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. La leçon est basée sur les treizième et quatorzième leçons, assurez-vous donc de les avoir comprises.

Il peut y avoir des bogues et problèmes de conception dans ce tutoriel. Cependant, grâce à l'aide généreuse de Marco Di Benedetto, le créateur de SpiderGL, et Paul Brunt du célèbre site GLGE et une bonne légion de testeurs, en particulier Stephen White, ce tutoriel est plus juste qu'il ne l'aurait été.

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

Une fois que vous avez une copie du code, ouvrez le fichier demo16.html dans un éditeur de texte et jetez-y un œil. Le fichier de ce tutoriel possède quelques changements par rapport aux leçons précédentes. Commençons donc par la fin et remontons. Tout d'abord, la fonction webGLStart, dans laquelle j'ai, comme d'habitude, mis en évidence les nouveautés :

function webGLStart() {
var canvas = document.getElementById("lesson16-canvas");
initGL(canvas);
initTextureFramebuffer(); // Nouveau
initShaders();
initBuffers();
initTextures();
loadLaptop();

gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);

tick();
}

Nous effectuons notre configuration habituelle : l'initialisation de WebGL, le chargement de nos shaders, la création des tampons de vertex à dessiner, le chargement des textures que nous allons utiliser (la lune et la caisse), et le lancement d'une requête de chargement du modèle JSON de l'ordinateur portable, comme nous l'avons fait pour charger le modèle de la théière dans la quatorzième leçon. Le nouveau morceau passionnant est la création d'un tampon d'image (« framebuffer ») pour la texture. Avant de vous montrer le code qui fait cela, regardons ce qu'est un tampon d'image.

Lorsque vous effectuez le rendu avec WebGL, vous avez évidemment besoin d'une sorte de zone mémoire dans la carte graphique capable de recevoir les résultats de rendu. Vous avez pour cela un contrôle précis sur le type de mémoire allouée à cet effet. Vous avez au minimum besoin de l'espace pour stocker les couleurs des différents pixels qui composent les résultats de votre rendu. C'est aussi très important (bien que pas toujours indispensable) d'avoir un tampon de profondeur, de sorte que votre rendu puisse prendre en compte la proximité des objets dans la scène masquant ceux plus éloignés (comme vu dans la huitième leçon), ce qui nécessite un peu de mémoire aussi. Il y a d'autres types de tampons qui peuvent également être utiles, comme un stencil buffer - que nous verrons dans une prochaine leçon.

Un tampon d'image est une chose dans laquelle vous pouvez dessiner une scène, il est constitué de plusieurs morceaux de mémoire. Il y a un tampon d'image « par défaut », qui est celui dans lequel nous avons toujours effectué le rendu dans le passé, et celui qui est affiché dans la page Web - mais vous pouvez créer vos propres tampons d'image et dessiner dedans à la place. Dans ce tutoriel, nous allons créer un tampon d'image et lui dire d'utiliser une texture en tant qu'espace mémoire où il devra stocker les couleurs quand il effectue son rendu, nous allons aussi devoir allouer un peu de mémoire pour ses calculs de profondeur.

Ainsi, après cette explication, nous allons jeter un œil au code qui fait tout cela. Il s'agit de la fonction initTextureFramebuffer, située à environ un tiers du fichier.

var rttFramebuffer;
var rttTexture;

function initTextureFramebuffer() {

Avant que la fonction ne débute, nous définissons quelques variables globales pour stocker le tampon d'image dans lequel nous allons effectuer le rendu qui ira sur l'écran de l'ordinateur portable, ainsi que stocker la texture qui contiendra le résultat du rendu de ce tampon d'image (auquel nous devrons accéder lorsque l'on dessinera le portable). Au sein de la fonction :

rttFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
rttFramebuffer.width = 512;
rttFramebuffer.height = 512;

Notre première étape consiste à créer le tampon d'image lui-même, et, suivant le schéma ordinaire (comme pour les textures, les tampons d'attributs de vertex, etc.) nous le sélectionnons comme courant - c'est-à-dire que les prochains appels de fonction s'appliqueront dessus. Nous conservons aussi la largeur et la hauteur de la scène dont nous allons effectuer le rendu ; ces attributs ne font normalement pas partie d'un tampon d'image, j'ai juste utilisé l'astuce habituelle de JavaScript pour associer de nouvelles propriétés, car nous en aurons besoin plus tard lorsque nous manipulerons le tampon d'image. J'ai pris une taille de 512 × 512 pixels - souvenez-vous que les textures ont des largeurs et hauteurs en puissances de 2, j'ai observé qu'une texture de taille 256 × 256 était trop pixelisée, et une texture de taille 1024 × 1024 n'améliorait pas le rendu.

Ensuite, nous créons un objet texture, et mettons en place les paramètres habituels :

rttTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
But there's one small difference; the call to gl.texImage2D has rather different parameters:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

Normalement, lorsque nous créons des textures pour montrer des images chargées avec JavaScript, nous appelons gl.texImage2D afin de les lier ensemble. Maintenant, bien sûr, il n'y a aucune image à charger, nous devons donc appeler une version différente de gl.texImage2D, lui disant que nous n'avons pas de données pour l'image, mais aimerions simplement allouer un espace mémoire sur la carte graphique pour notre texture. À proprement parler, le dernier paramètre de la fonction est un tableau qui doit être copié dans la mémoire fraîchement allouée comme point de départ. En passant null, nous disons qu'il n'y a rien à copier. (Dans les premières versions de Minefield, vous deviez passer un tableau vide de dimension appropriée, mais ceci semble avoir été corrigé maintenant.)

OK, nous avons donc maintenant une texture vide permettant de stocker les valeurs de couleur pour le rendu de notre scène. Nous créons ensuite un tampon de profondeur pour stocker les informations de profondeur :

var renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);

Jusqu'ici, nous avons créé un objet tampon de rendu (« renderbuffer »), un type générique d'objet qui stocke certains blocs de mémoire destinés à être associés à un tampon d'image. Nous le lions - tout comme les textures, tampons d'image, et tout le reste, WebGL possède un tampon de rendu courant - et appelons ensuite gl.renderbufferStorage pour indiquer à WebGL que le tampon de rendu courant a besoin de stocker des valeurs sur 16 bits dans un tampon, étant donné sa largeur et sa hauteur.

Ensuite :

gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

Nous attachons tout au tampon d'image courant (souvenez-vous, nous avons lié le nouveau pour en faire l'actuel après sa création au début de la fonction). Nous lui disons que l'espace du tampon d'image pour le rendu des couleurs (gl.COLOR_ATTACHMENT0) est notre texture, et que la mémoire qu'il doit utiliser pour les informations de profondeur (gl.DEPTH_ATTACHMENT) est le tampon de profondeur que nous venons de créer.

Maintenant que nous avons mis en place toute la mémoire pour notre tampon d'image, WebGL sait quel rendu effectuer lorsque nous l'utilisons. Alors maintenant, nous remettons la texture actuelle, le tampon de rendu et le tampon d'image à leur valeur par défaut :

gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

… et nous avons fini. Notre tampon d'image est correctement mis en place. Alors, maintenant que nous l'avons, comment l'utilisons-nous ? Commençons par regarder la fonction drawScene, près de la fin du fichier. Dès le début de la fonction, avant le code de configuration de la fenêtre et l'effacement du canvas, vous verrez quelque chose de nouveau :

var laptopAngle = 0;

function drawScene() {
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer); // Nouveau
drawSceneOnLaptopScreen(); // Nouveau

gl.bindFramebuffer(gl.FRAMEBUFFER, null); // Nouveau

gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

À la lumière de cette description, ce qui se passe ici devrait être assez évident. Nous basculons du tampon d'image par défaut, qui effectue le rendu du canvas dans la page HTML, vers le tampon d'image de rendu vers texture que nous avons créé dans initTextureFramebuffer, puis appelons une fonction nommée drawSceneOnLaptopScreen pour dessiner la scène à afficher sur l'écran de l'ordinateur portable (en effectuant implicitement le rendu dans notre tampon d'image). Une fois ceci fait, nous basculons vers le tampon d'image par défaut. Avant de regarder drawScene, jetons un œil à la fonction drawSceneOnLaptopScreen. Je ne la copierai pas ici, car elle est vraiment simple - c'est juste une version allégée de la fonction drawScene de la treizième leçon ! En effet, notre code de rendu jusqu'à présent ne faisait aucune hypothèse sur le lieu de rendu, il effectuait juste le rendu dans le tampon d'image courant. Les seules modifications apportées dans cette leçon sont les simplifications rendues possibles par la suppression de la source de lumière mobile et autres choses de la treizième leçon qui ne sont plus indispensables pour ce tutoriel.

Donc, ces trois premières lignes de la fonction drawScene exécutées, nous obtenons le cadre de la treizième leçon rendu dans une texture. Le reste du code dessine simplement l'ordinateur portable, et utilise cette texture pour son écran. Nous commençons par le code ordinaire servant à mettre en place la matrice modèle-vue et à faire tourner l'ordinateur portable selon l'angle laptopAngle (qui, comme dans les autres tutoriels, est mis à jour dans la fonction animate appelée à chaque fois que nous dessinons la scène pour faire tourner l'ordinateur portable en continu) :

mat4.identity(mvMatrix);

mvPushMatrix();

mat4.translate(mvMatrix, [0, -0.4, -2.2]);
mat4.rotate(mvMatrix, degToRad(laptopAngle), [0, 1, 0]);
mat4.rotate(mvMatrix, degToRad(-90), [1, 0, 0]);

Nous envoyons les valeurs déterminant les couleurs et les emplacements de nos sources de lumière à la carte graphique comme normales :

gl.uniform1i(shaderProgram.showSpecularHighlightsUniform, true);
gl.uniform3f(shaderProgram.pointLightingLocationUniform, -1, 2, -1);

gl.uniform3f(shaderProgram.ambientLightingColorUniform, 0.2, 0.2, 0.2);
gl.uniform3f(shaderProgram.pointLightingDiffuseColorUniform, 0.8, 0.8, 0.8);
gl.uniform3f(shaderProgram.pointLightingSpecularColorUniform, 0.8, 0.8, 0.8);

Ensuite, nous passons les informations des paramètres de l'éclairage de l'ordinateur portable à la carte graphique. Il y a quelque chose de nouveau qui n'est pas directement lié au rendu des textures. Vous vous souvenez peut-être dans la septième leçon lorsque j'ai décrit le modèle d'éclairage Phong, j'ai mentionné que les matériaux avaient des couleurs différentes pour chaque type de lumière : une couleur ambiante, une couleur diffuse, et une couleur spéculaire. Depuis cette leçon, et dans toutes les suivantes, nous avons fait l'hypothèse simplificatrice que ces couleurs étaient toujours blanches ou venaient de la texture, selon que les textures étaient désactivées ou non. Pour des raisons que nous verrons dans un instant - car cela ne suffit pas - nous aurons besoin de spécifier de façon plus détaillée les couleurs de l'écran du portable, et nous aurons à utiliser un nouveau type de couleur, la couleur émissive. Cependant, pour le corps de l'ordinateur portable, nous n'avons pas besoin de trop nous en soucier : les paramètres de couleurs des matériaux sont simples, l'ordinateur portable est juste blanc.

// Le corps de l'ordinateur portable est assez brillant et n'a pas de texture. Il reflète beaucoup de lumière spéculaire
gl.uniform3f(shaderProgram.materialAmbientColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 1.0, 1.0, 1.0);
gl.uniform3f(shaderProgram.materialSpecularColorUniform, 1.5, 1.5, 1.5);
gl.uniform1f(shaderProgram.materialShininessUniform, 5);
gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 0.0, 0.0, 0.0);
gl.uniform1i(shaderProgram.useTexturesUniform, false);

Si les coordonnées des vertex de l'ordinateur portable ont été chargées, la prochaine étape est de les dessiner. Ce code devrait vous être assez familier maintenant, surtout après la quatorzième leçon (à partir de laquelle il est largement copié) :

if (laptopVertexPositionBuffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopVertexNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, laptopVertexIndexBuffer);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, laptopVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0);
}

Une fois tout ceci fait, le corps de l'ordinateur portable a été dessiné. Nous devons ensuite dessiner l'écran. On commence par ses paramètres d'éclairage, cette fois nous fixons une couleur émissive :

gl.uniform3f(shaderProgram.materialAmbientColorUniform, 0.0, 0.0, 0.0);
gl.uniform3f(shaderProgram.materialDiffuseColorUniform, 0.0, 0.0, 0.0);
gl.uniform3f(shaderProgram.materialSpecularColorUniform, 0.5, 0.5, 0.5);
gl.uniform1f(shaderProgram.materialShininessUniform, 20);
gl.uniform3f(shaderProgram.materialEmissiveColorUniform, 1.5, 1.5, 1.5);
gl.uniform1i(shaderProgram.useTexturesUniform, true);

Alors, qu'est-ce qu'une couleur émissive ? Eh bien, les écrans comme ceux des ordinateurs portables ne reflètent pas la lumière - ils l'émettent. Nous voulons que la couleur de l'écran soit définie en priorité par la couleur de la texture que par les effets de la lumière. Nous pourrions le faire en changeant les variables uniformes qui régissent l'éclairage, éteindre l'éclairage ponctuel et monter l'éclairage ambiant jusqu'à 100 % avant de dessiner à l'écran, puis restaurer les anciennes valeurs. Mais cela ressemblerait un peu à un hack - après tout, cette émissivité de l'écran est la propriété de l'écran, et non celle de la lumière. Dans cet exemple précis, nous pourrions aussi le faire en utilisant simplement l'éclairage ambiant, parce qu'il est de couleur blanche, donc en fixant la couleur ambiante de l'écran à (1.5, 1.5, 1.5) nous obtiendrions le bon effet. Mais si quelqu'un venait à changer l'éclairage ambiant, la couleur de l'écran changerait, ce qui serait bizarre. Après tout, si vous mettez votre ordinateur portable dans une salle éclairée de rouge, l'écran ne devient pas rouge. Nous utilisons donc une nouvelle couleur émissive uniforme, qui est gérée par le shader en utilisant un code simple que nous verrons plus tard.

Il faut se rappeler que la couleur émissive d'un objet dans ce sens n'affecte pas les autres objets qui l'entourent - à savoir, il ne transforme pas l'objet en une source lumineuse pour éclairer les autres objets. C'est juste un moyen de donner à un objet une couleur indépendante de l'éclairage de la scène.

L'exigence pour avoir une couleur émissive explique aussi pourquoi nous avons besoin de séparer les autres paramètres de couleur des matériaux dans ce tutoriel. Notre écran d'ordinateur a une couleur émissive déterminée par sa texture, mais sa couleur spéculaire doit rester la même malgré cela - après tout, ce qui est affiché à l'écran de votre ordinateur portable ne change pas la couleur de la réflexion de la fenêtre derrière vous. Donc cette couleur reste blanche.

OK, continuons, nous lions les tampons désignant les attributs des vertex de l'écran de l'ordinateur portable :

gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexPositionBuffer);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, laptopScreenVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexNormalBuffer);
gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, laptopScreenVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);

gl.bindBuffer(gl.ARRAY_BUFFER, laptopScreenVertexTextureCoordBuffer);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, laptopScreenVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

Nous précisons ensuite que nous voulons utiliser la texture rendue précédemment :

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.uniform1i(shaderProgram.samplerUniform, 0);

Puis dessinons l'écran, et nous avons fini :

setMatrixUniforms();
gl.drawArrays(gl.TRIANGLE_STRIP, 0, laptopScreenVertexPositionBuffer.numItems);

mvPopMatrix();
}

Presque décevant n'est-ce pas. C'était tout le code nécessaire pour rendre une scène vers une texture, puis utiliser cette texture dans une autre scène.

C'est à peu près tout pour ce tutoriel, mais parcourons rapidement les autres modifications depuis les leçons précédentes. Il y a une paire de fonctions appelées loadLaptop et handleLoadedLaptop pour charger les données JSON qui servent au rendu de l'ordinateur portable, c'est fondamentalement le même code de chargement de la théière dans la quatorzième leçon. Il y a aussi un bout de code à la fin de la fonction initBuffers pour initialiser les tampons des vertex de l'écran du portable. Ce code un peu moche sera amélioré dans une version future de ce tutoriel (les valeurs devraient être chargées à partir de JSON comme les données de l'ordinateur portable, mais sont actuellement contenues là dans le code).

Enfin, il y a le nouveau fragment shader, qui doit gérer les couleurs matérielles de chaque type d'éclairage comme alternative à la couleur de la texture. Tout cela devrait être assez facile à comprendre à la lumière des précédents shaders. La seule chose vraiment nouvelle est l'éclairage émissif, et ce qui en découle c'est qu'il est ajouté à la couleur du fragment final tout à la fin. Voici le code :

precision mediump float;

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

uniform vec3 uMaterialAmbientColor;
uniform vec3 uMaterialDiffuseColor;
uniform vec3 uMaterialSpecularColor;
uniform float uMaterialShininess;
uniform vec3 uMaterialEmissiveColor;

uniform bool uShowSpecularHighlights;
uniform bool uUseTextures;

uniform vec3 uAmbientLightingColor;

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

uniform sampler2D uSampler;

void main(void) {
vec3 ambientLightWeighting = uAmbientLightingColor;

vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);

vec3 specularLightWeighting = vec3(0.0, 0.0, 0.0);
if (uShowSpecularHighlights) {
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);

float specularLightBrightness = pow(max(dot(reflectionDirection, eyeDirection), 0.0), uMaterialShininess);
specularLightWeighting = uPointLightingSpecularColor * specularLightBrightness;
}

float diffuseLightBrightness = max(dot(normal, lightDirection), 0.0);
vec3 diffuseLightWeighting = uPointLightingDiffuseColor * diffuseLightBrightness;

vec3 materialAmbientColor = uMaterialAmbientColor;
vec3 materialDiffuseColor = uMaterialDiffuseColor;
vec3 materialSpecularColor = uMaterialSpecularColor;
vec3 materialEmissiveColor = uMaterialEmissiveColor;
float alpha = 1.0;
if (uUseTextures) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
materialAmbientColor = materialAmbientColor * textureColor.rgb;
materialDiffuseColor = materialDiffuseColor * textureColor.rgb;
materialEmissiveColor = materialEmissiveColor * textureColor.rgb;
alpha = textureColor.a;
}
gl_FragColor = vec4(
materialAmbientColor * ambientLightWeighting
+ materialDiffuseColor * diffuseLightWeighting
+ materialSpecularColor * specularLightWeighting
+ materialEmissiveColor,
alpha
);
}

Et c'est vraiment fini ! Dans ce tutoriel, nous avons vu la façon de rendre une scène dans une texture et l'utiliser dans une autre scène, ainsi qu'abordé les couleurs matérielles et leur fonctionnement. Dans le prochain tutoriel, je vous montrerai comment en faire quelque chose de vraiment utile : le GPU picking, afin que vous puissiez écrire des scènes 3D permettant aux utilisateurs d'interagir avec les objets en cliquant dessus.

IV - Remerciements

J'ai eu besoin de beaucoup d'aide pour faire fonctionner ce code, en particulier parce que la première version avait des bogues qui n'étaient pas présents lorsque je l'avais lancée sur mon propre ordinateur portable. Je tiens particulièrement à remercier Marco Di Benedetto, le créateur de SpiderGL, et Paul Brunt, GLGE, pour m'avoir indiqué mes erreurs, et comment les corriger. Mais je dois beaucoup envers les personnes qui ont testé version après version la démonstration jusqu'à en obtenir finalement une qui fonctionnait à peu près n'importe où - Stephen White (qui m'a expliqué que le rendu vers texture était une nécessité pour le picking de précision, qui a fait le sujet de cette leçon), Denny (créateur de EnergizeGL), blinblin, nameless, Jacob Seidelin, Pyro Technick, ewgl, Peter, Springer, Christofer, Thormme et titan.

D'autres endroits dont je me suis tant inspiré sont le Guide de programmation OpenGL ES 2.0, la bibliothèque GLGE de Paul Brunt, et divers messages et requêtes sur le forum de développement d'iPhone : ici, ici et ici. Évidemment, la spécification WebGL m'a aussi aidé…

Le modèle 3D de l'ordinateur portable a été mis à disposition gratuitement par Xedium, et la texture de la Lune vient du Laboratoire Jet Propulsion.

Ouf. Cela commençait à ressembler à un discours d'acceptation d'Oscar…

Merci à LittleWhite pour sa relecture lors de la traduction et ClaudeLELOUP pour sa relecture orthographique.

Navigation

Tutoriel précédent : les textures spéculaires

Sommaire