Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
Fanfois et ses papillons
23 mai 2012

HTML5 07 (tutorial WebGL 6ème partie)

Dans le précédent billet, nous avons permis à l'utilisateur de manipuler la caméra et donc de visualiser la scène comme il le souhaite. Par contre la scène que nous lui proposons est nécessairement simple. En effet nous sommes actuellement obligés de la dessiner en une seule fois. Ce fonctionnement ne nous permettra pas de réaliser des scènes plus complexes. Nous devons remédier à ce problème.

Pour expliciter la chose, nous allons considérer que nous voulons peupler notre scène avec plusieurs spirales (chaque spirale viendra avec ses axes).
Nous cherchons donc à dupliquer ce que nous savons faire actuellement en plusieurs endroits de la scène.
C'est la base de la construction d'une scène 3D. On modélise un objet en 3D en le centrant sur l'origine. Lors du rendu de la scène, on dessine cet objet et on le déplace pour le positionner correctement dans la scène. Si l'objet doit être représenté plusieurs fois on recommence le processus autant de fois que nécessaire. Généralement plusieurs types d'objets doivent être rendus, la même approche est utilisée pour chaque type d'objet.
Pour faire la chose de manière efficace, il est important de limiter les opérations sur les matrices qui peuvent, surtout si elles sont nombreuses, plomber sérieusement les performances.
Il se trouve que lors de la construction de la scène, certaines positions vont être partagées par plusieurs objets.
L'exemple le plus évident étant les déplacements induits par la caméra. Chaque dessin d'un objet de la scène implique de recalculer la matrice mvMatrix induite par la caméra. En fait nous avons besoin de la matrice, pas forcément de la recalculer à chaque fois. Nous pouvons très bien la calculer une fois, la sauver quelque part et la récupérer à chaque fois que nous en aurons besoin.
Pour cela tout le monde utilise une pile de matrices.
Avant de dessiner un élément particulier de la scène, on sauve la matrice courante dans la pile. Après avoir dessiné l'objet on restaure la matrice en la récupérant dans la pile. Avec cette méthode, le dessin d'un objet ne provoque pas de modification de la matrice courante (sauf pendant le dessin lui-même). On peut donc utiliser la matrice courante pour dessiner l'objet suivant, il n'est pas nécessaire de reconstruire cette matrice.

La première chose à faire consiste donc à ajouter une pile de matrice dans notre classe WebGL et de la doter des méthodes permettant d'empiler et de dépiler. Nous ajoutons donc (seul le code ajouté est présenté) :

var WebGL = function (canvasElement) {
  ...
 
  this.mvMatrixStack = [];
 
  ...
}
 
WebGL.prototype.save = function () {
  var copy = this.mat4.create();
  this.mat4.set(this.mvMatrix, copy);
  this.mvMatrixStack.push(copy);
};
 
WebGL.prototype.restore = function () {
  if (this.mvMatrixStack.length == 0) {
    throw "Invalid popMatrix!";
  }
  this.mvMatrix = this.mvMatrixStack.pop();
};

Voyons comment utiliser ces deux méthodes.
Pour commencer nous créons une nouvelle page HTML, strictement identique à WebGL_10.htm, que nous appelons WebGL_11.htm.
Il nous faut simplement travailler en quatre étapes :

  • pour commencer nous avons besoin de définir les points et les couleurs d'une spirale. Nous avons déjà une méthode pour cela, elle s'appelle initBuffers. Dans un contexte où il est possible d'avoir plusieurs types d'objets, ce nom n'est pas très précis. Nous renommons cette méthode initSpiralBuffers;
  • il nous faut maintenant savoir combien il y aura de spirales et où il faudra les placer. Pour cela nous ajoutons une nouvelle méthode initSpiralBuffers. Son code est le suivant :
    function initSpiralPositions() {
      var spiralPositions = [];
      // L'espace disponible est de -50 à +50, -20 à +20 verticalement.
      // Sachant qu'une spirale s'inscrit dans un volume de 20 unités (-10 à +10),
      // on défini les limites possibles.
      var hLimit = 40;
      var vLimit = 10;
      // Génération du nombre de spirales (entre 1 et 10).
      var spiralCount = Math.floor((Math.random() * 10) + 1);
      // Génération des positions.
      for (var n = 0; n < spiralCount; n++) {
        var spiralPos = {};
        spiralPos.x = (Math.random() * hLimit * 2) - hLimit;
        spiralPos.y = (Math.random() * vLimit * 2) - vLimit;
        spiralPos.z = (Math.random() * hLimit * 2) - hLimit;
        spiralPos.rotationX = Math.random() * Math.PI * 2;
        spiralPos.rotationY = Math.random() * Math.PI * 2;
        spiralPos.rotationZ = Math.random() * Math.PI * 2;
        spiralPositions.push(spiralPos);
      }
      return spiralPositions;
    }
    
    Le code proposé initialise tous les éléments permettant de positionner un objet. C'est pour la démonstration. A l'arrivée il y a de grandes chances que ce soit un peu brouillon. Les laisser toutes verticales peut ordonner un peu la scène ;
  • il faut savoir dessiner les spirales. C'est le travail de la méthode drawSpirals:
    function drawSpirals(gl, spiralPositions, spiralBuffers, angle) {
      for (var n = 0; n < spiralPositions.length; n++) {
        // Sauvegarde de la matrice courante.
        gl.save();
        // Application de la position particulière.
        var spiralPos = spiralPositions[n];
        gl.translate(spiralPos.x, spiralPos.y, spiralPos.z);
        gl.rotate(spiralPos.rotationX, 1, 0, 0);
        gl.rotate(spiralPos.rotationY, 0, 1, 0);
        gl.rotate(spiralPos.rotationZ, 0, 0, 1);
        // Application de l'animation.
        gl.rotate(angle, 0, 1, 0);
        // Dessin de la spirale courante.
        gl.pushVaryingColorBuffer(spiralBuffers);
        gl.pushPositionBuffer(spiralBuffers);
        gl.drawLineArrays(spiralBuffers);
        // Restauration de la matrice initiale.
        gl.restore();
      }
    }
    
  • dans la phase d'initialisation, il convient maintenant d'appeler initSpiralBuffers et initSpiralPositions. Lors du dessin de la scène c'est à drawSpirals qu'il faut faire appel, après avoir mis en place la dite scène.

Si l'on veut ajouter un autre type d'objet, il suffit de définir les trois méthodes correspondantes au nouveau type, et le tour est joué.

Justement, ajoutons un nouveau type d'objet. Nous allons ajouter une nouvelle sorte de spirales, les spirales représentées par des points.

Notre classe WebGL ne sait pas afficher des points. Conséquence, nous devons d'abord lui apprendre à le faire.

Pour cela nous définissons une nouvelle méthode drawPointArrays. Son code n'est pas des plus compliqué :

WebGL.prototype.drawPointArrays = function (buffers) {
  this.setMatrixUniforms();
  // draw arrays
  this.context.drawArrays(this.context.POINTS, 0, buffers.positionBuffer.numElements / 3);
};

L'intérêt de définir une méthode drawPointElements semble très limité. Il n'y a pas grand intérêt à utiliser les indexes pour dessiner des points.

Maintenant que nous disposons de la fonction, utilisons là.
Nous n'allons pas faire très compliqué. Nous allons pratiquement reprendre le code de la spirale mais au lieu des traits, nous placerons simplement des points. Attention, nous allons mélanger les points et les traits car nous tracerons toujours les axes.
Pour dessiner une spirale à l'aide de traits, nous avons dessiné deux choses. Les axes et la spirale. Comme les deux choses étaient de la même nature nous les avons placées dans une unique fonction.
Pour dessiner une spirale en point nous devons dessiner deux choses, les axes et les points formant la spirale. Les deux choses étant de nature différente nous allons les séparer.
Nous allons donc avoir :

  1. une méthode pour définir ce qu'est un objet référentiel ;
  2. une méthode pour définir ce qu'est un objet spirale en traits ;
  3. une méthode pour définir ce qu'est un objet spirale en points ;
  4. une méthode pour définir où est quoi. Pour construire quelque chose de regardable cette méthode ne fera pas appel au hasard;
  5. une méthode assemble les axes et les spirales, soit en traits, soit en point et les dessine au bon endroit. Il aurait été possible de découper cette méthode en trois méthodes mais cela n'a pas été fait.

Cela nous donne, dans une nouvelle page WebGL_12.htm, le code suivant :

<!DOCTYPE HTML>
<html>
  <head>
    <!-- required to use WebGLHelper object -->
    <!-- <script type="text/javascript" src="http://iewebgl.com/scripts/webglhelper.js"></script> -->
    <script type="text/javascript" src="script/webglhelper.js"></script>
    <!-- La librairie de manipulation de matrices de Brandon Jones -->
    <script type="text/javascript" src="script/gl-matrix-min.js"></script>
    <!-- Notre librairie d'animation et d'aide WebGL -->
    <script type="text/javascript" src="script/WebGL_2.js"></script>
    <script type="text/javascript">
      // Définition d'un objet référentiel.
      function initRefBuffers(gl) {
        var refBuffers = {};
 
        // Les points des axes.
        refBuffers.positionBuffer = gl.createArrayBuffer([
          -10, 0, 0, 10, 0, 0, // X
          0, -10, 0, 0, 10, 0, // Y
          0, 0, -10, 0, 0, 10  // Z
        ]);
 
        // Les couleurs des extrémités des axes.
        var colors = [
          [1.0, 0.0, 1.0, 1.0], // X - Rose
          [1.0, 0.0, 0.0, 1.0], // X - Red
          [1.0, 1.0, 0.0, 1.0], // Y - Jaune
          [0.0, 1.0, 0.0, 1.0], // Y - Green
          [0.0, 1.0, 1.0, 1.0], // Z - Turquoise
          [0.0, 0.0, 1.0, 1.0]  // Z - Blue
        ];
        var colorVertices = [];
        for (var n in colors) {
          var color = colors[n];
          colorVertices = colorVertices.concat(color);
        }
        refBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
        return refBuffers;
      }
 
      // Définition d'un objet spirale en traits.
      function initLineSpiralBuffers(gl) {
        var lineSpiralBuffers = {}
 
        // Les points de la spirale.
        var points = [];
        var x = 10.0;
        var y = -10.0
        var z = 0.0;
        var teta = 0.0;
        points = points.concat(x);
        points = points.concat(y);
        points = points.concat(z);
        while (true) {
          teta += Math.PI / 16;
          x = 10 * Math.cos(teta);
          y += 0.25;
          z = 10 * Math.sin(teta);
 
          points = points.concat(x);
          points = points.concat(y);
          points = points.concat(z);
 
          if (y >= 10.0)
            break;
 
          points = points.concat(x);
          points = points.concat(y);
          points = points.concat(z);
        }
 
        lineSpiralBuffers.positionBuffer = gl.createArrayBuffer(points);
 
        // Les couleurs des points de la spirale.
        var colorVertices = [];
        for (var i = 0; i < lineSpiralBuffers.positionBuffer.numElements; i++) {
          colorVertices = colorVertices.concat([1.0, 1.0, 1.0, 1.0]);
        }
        lineSpiralBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
        return lineSpiralBuffers;
      }
 
      // Définition d'un objet spirale en points.
      function initDotSpiralBuffers(gl) {
        var dotSpiralBuffers = {}
 
        // Les points de la spirale.
        var points = [];
        var x = -10.0;
        var y = -10.0
        var z = 0.0;
        var teta = 0.0;
        while (y <= 10.0) {
          points = points.concat(x);
          points = points.concat(y);
          points = points.concat(z);
 
          teta += Math.PI / 32;
          x = -10 * Math.cos(teta);
          y += 0.125;
          z = 10 * Math.sin(teta);
        }
 
        dotSpiralBuffers.positionBuffer = gl.createArrayBuffer(points);
 
        // Les couleurs des points de la spirale.
        var colorVertices = [];
        for (var i = 0; i < dotSpiralBuffers.positionBuffer.numElements; i++) {
          colorVertices = colorVertices.concat([1.0, 1.0, 1.0, 1.0]);
        }
        dotSpiralBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
        return dotSpiralBuffers;
      }
 
      // Position et nature des spirales.
      function initSpiralPositions() {
        var spiralPositions = [];
        // La première.
        var spiralPos1 = {};
        spiralPos1.isDot = false;
        spiralPos1.x = -10;
        spiralPos1.y = 0;
        spiralPos1.z = 0;
        spiralPos1.rotationX = 0.2;
        spiralPos1.rotationY = 0;
        spiralPos1.rotationZ = 0;
        spiralPositions.push(spiralPos1);
        // La seconde.
        var spiralPos2 = {};
        spiralPos2.isDot = true;
        spiralPos2.x = 10;
        spiralPos2.y = 0;
        spiralPos2.z = 0;
        spiralPos2.rotationX = 0.2;
        spiralPos2.rotationY = 0;
        spiralPos2.rotationZ = 0;
        spiralPositions.push(spiralPos2);
 
        return spiralPositions;
      }
 
      function drawSpirals(gl, spiralPositions, refBuffers, lineSpiralBuffers, dotSpiralBuffers, angle) {
        for (var n = 0; n < spiralPositions.length; n++) {
          // Sauvegarde de la matrice courante.
          gl.save();
          // Application de la position particulière.
          var spiralPos = spiralPositions[n];
          gl.translate(spiralPos.x, spiralPos.y, spiralPos.z);
          gl.rotate(spiralPos.rotationX, 1, 0, 0);
          gl.rotate(spiralPos.rotationY, 0, 1, 0);
          gl.rotate(spiralPos.rotationZ, 0, 0, 1);
          // Application de l'animation.
          if (spiralPos.isDot) {
            gl.rotate(-angle, 0, 1, 0);
          } else {
            gl.rotate(angle, 0, 1, 0);
          }
          // Dessin du référentiel.
          gl.pushVaryingColorBuffer(refBuffers);
          gl.pushPositionBuffer(refBuffers);
          gl.drawLineArrays(refBuffers);
          // Dessin de la spirale.
          if (spiralPos.isDot) {
            gl.pushVaryingColorBuffer(dotSpiralBuffers);
            gl.pushPositionBuffer(dotSpiralBuffers);
            gl.drawPointArrays(dotSpiralBuffers);
          } else {
            gl.pushVaryingColorBuffer(lineSpiralBuffers);
            gl.pushPositionBuffer(lineSpiralBuffers);
            gl.drawLineArrays(lineSpiralBuffers);
          }
          // Restauration de la matrice initiale.
          gl.restore();
        }
      }
 
      function stage(gl, spiralPositions, refBuffers, lineSpiralBuffers, dotSpiralBuffers, angle) {
        // Effacement de l'image précédente.
        gl.clear();
 
        // Initialisation des matrices (caméra appliquée).
        gl.initializeMatrix();
 
        // Définition de la perspective.
        // "Ouverture" de l'objectif, 45°.
        // Zone visible, 0,1 à 100.
        gl.perspective(45, 0.1, 100.0);
 
        // Dessin des spirales.
        drawSpirals(gl, spiralPositions, refBuffers, lineSpiralBuffers, dotSpiralBuffers, angle);
      }
 
      function initWebGL(canvasElement) {
        // Création de notre classe d'aide.
        var gl = new WebGL(canvasElement);
 
        // Initialisation des shaders.
        gl.setShaderProgram("VARYING_COLOR");
 
        // Définition de la couleur de fond.
        gl.setClearColor(0.0, 0.0, 0.0, 1);
 
        // Définition des points et des couleurs d'un référentiel.
        var refBuffers = initRefBuffers(gl);
        // Définition des points et des couleurs d'une spirale en traits.
        var lineSpiralBuffers = initLineSpiralBuffers(gl);
        // Définition des points et des couleurs d'une spirale en points.
        var dotSpiralBuffers = initDotSpiralBuffers(gl);
        // Définition des positions et des orientations des n spirales.
        var spiralPositions = initSpiralPositions();
 
        // Positionnement initial de la caméra.
        gl.setInitialCameraPosition(0, 0, 50, 0, 0, 0);
        gl.resetCameraPosition();
        // Définition des vitesses de déplacement de la caméra.
        //gl.setHorizontalSpeed(20);
        //gl.setVerticalSpeed(10);
 
        // Activation de la caméra.
        gl.activateCameraTracking();
 
        // L'angle de rotation au tour de Y.
        var angle = 0;
 
        // Mise en place de la scène.
        gl.setStage(function () {
          // Calcul du nouvel angle.
          var angularVelocity = Math.PI / 4; // radians / second
          var angleEachFrame = angularVelocity * gl.getTimeInterval() / 1000;
          angle += angleEachFrame;
 
          stage(gl, spiralPositions, refBuffers, lineSpiralBuffers, dotSpiralBuffers, angle);
        });
 
        // Lancement de l'animation.
        gl.startAnimation();
      }
 
      function OnGLCanvasCreated(canvasElement, elementId) {
        window.setTimeout(function () { initWebGL(canvasElement); }, 500);
      }
 
      function OnGLCanvasFailed(canvasElement, elementId) {
        alert("Votre butineur ne supporte pas WebGL !");
      }
    </script>
  </head>
  <body>
    <script id="WebGLCanvasCreationScript" type="text/javascript" width="800" height="500" style="border:1px solid black;">
      WebGLHelper.CreateGLCanvasInline('glCanvas', OnGLCanvasCreated, OnGLCanvasFailed)
    </script>
  </body>
</html>

A l'écran, le résultat devrait être :
html5 07 01

Pour clore ce billet, nous allons ajouter à notre classe WebGL les méthodes permettant de dessiner une scène 3D classique.
En particulier nous demanderons à WegGL de gérer la profondeur lors du rendu de la scène. Nous aurions déjà dû le faire, mais avec nos traits d'un pixel de large la chose n'est pas évidente à voir. Actuellement, le dernier objet dessiné détermine ce que l'on voit. Normalement ce n'est pas l'ordre du rendu des objets qui devrait déterminer ce que l'on voit, c'est la profondeur par rapport à l'observateur. Ce qui est derrière, même dessiné en dernier, ne doit pas être visible mais masqué par ce qui est devant.
Pour valider que tout est bon, nous reconstruirons le dernier exemple du livre HTML5 Canvas Cookbook en l'adaptant à notre version de la classe WebGL.

Pour ce qui est du code de la classe WebGL, le voici (seul le code ajouté ou modifié est présenté) :

var WebGL = function (canvasElement) {
  ...
  // Les constantes définissant les types de shader possible.
  this.TEXTURE = "TEXTURE";
  this.TEXTURE_DIRECTIONAL_LIGHTING = "TEXTURE_DIRECTIONAL_LIGHTING";
 
  ...
 
  // Initialisation générale du contexte WebGL.
  this.context.enable(this.context.DEPTH_TEST);
};
 
WebGL.prototype.clear = function () {
  this.context.viewport(0, 0, this.context.viewportWidth, this.context.viewportHeight);
  this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);
};
 
WebGL.prototype.pushTextureBuffer = function (buffers, texture) {
  this.context.bindBuffer(this.context.ARRAY_BUFFER, buffers.textureBuffer);
  this.context.vertexAttribPointer(this.shaderProgram.textureCoordAttribute, 2, this.context.FLOAT, false, 0, 0);
  this.context.activeTexture(this.context.TEXTURE0);
  this.context.bindTexture(this.context.TEXTURE_2D, texture);
  this.context.uniform1i(this.shaderProgram.samplerUniform, 0);
};
 
WebGL.prototype.pushNormalBuffer = function (buffers) {
  this.context.bindBuffer(this.context.ARRAY_BUFFER, buffers.normalBuffer);
  this.context.vertexAttribPointer(this.shaderProgram.vertexNormalAttribute, 3, this.context.FLOAT, false, 0, 0);
};
 
WebGL.prototype.drawTriangleArrays = function (buffers) {
  this.setMatrixUniforms();
 
  // draw arrays
  this.context.drawArrays(this.context.TRIANGLES, 0, buffers.positionBuffer.numElements / 3);
};
 
WebGL.prototype.drawTriangleElements = function (buffers) {
  this.setMatrixUniforms();
 
  // draw elements
  this.context.drawElements(this.context.TRIANGLES, buffers.indexBuffer.numElements, this.context.UNSIGNED_SHORT, 0);
};
 
WebGL.prototype.enableLighting = function () {
  this.context.uniform1i(this.shaderProgram.useLightingUniform, true);
};
 
WebGL.prototype.setAmbientLighting = function (red, green, blue) {
  this.context.uniform3f(this.shaderProgram.ambientColorUniform, parseFloat(red), parseFloat(green), parseFloat(blue));
};
 
WebGL.prototype.setDirectionalLighting = function (x, y, z, red, green, blue) {
  // directional lighting
  var lightingDirection = [x, y, z];
  var adjustedLD = this.vec3.create();
  this.vec3.normalize(lightingDirection, adjustedLD);
  this.vec3.scale(adjustedLD, -1);
  this.context.uniform3fv(this.shaderProgram.lightingDirectionUniform, adjustedLD);
 
  // directional color
  this.context.uniform3f(this.shaderProgram.directionalColorUniform, parseFloat(red), parseFloat(green), parseFloat(blue));
};
 
WebGL.prototype.getFragmentShaderGLSL = function (shaderType) {
  switch (shaderType) {
    ...
    case this.TEXTURE:
      return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "varying vec2 vTextureCoord;\n" +
            "uniform sampler2D uSampler;\n" +
            "void main(void) {\n" +
            "gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n" +
            "}";
    case this.TEXTURE_DIRECTIONAL_LIGHTING:
      return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "varying vec2 vTextureCoord;\n" +
            "varying vec3 vLightWeighting;\n" +
            "uniform sampler2D uSampler;\n" +
            "void main(void) {\n" +
            "vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n" +
            "gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a);\n" +
            "}";
  }
};
 
WebGL.prototype.getVertexShaderGLSL = function (shaderType) {
  switch (shaderType) {
    ...
    case this.TEXTURE:
      return "attribute vec3 aVertexPosition;\n" +
            "attribute vec2 aTextureCoord;\n" +
            "uniform mat4 uMVMatrix;\n" +
            "uniform mat4 uPMatrix;\n" +
            "varying vec2 vTextureCoord;\n" +
            "void main(void) {\n" +
            "gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n" +
            "vTextureCoord = aTextureCoord;\n" +
            "}";
    case this.TEXTURE_DIRECTIONAL_LIGHTING:
      return "attribute vec3 aVertexPosition;\n" +
            "attribute vec3 aVertexNormal;\n" +
            "attribute vec2 aTextureCoord;\n" +
            "uniform mat4 uMVMatrix;\n" +
            "uniform mat4 uPMatrix;\n" +
            "uniform mat3 uNMatrix;\n" +
            "uniform vec3 uAmbientColor;\n" +
            "uniform vec3 uLightingDirection;\n" +
            "uniform vec3 uDirectionalColor;\n" +
            "uniform bool uUseLighting;\n" +
            "varying vec2 vTextureCoord;\n" +
            "varying vec3 vLightWeighting;\n" +
            "void main(void) {\n" +
            "gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n" +
            "vTextureCoord = aTextureCoord;\n" +
            "if (!uUseLighting) {\n" +
            "vLightWeighting = vec3(1.0, 1.0, 1.0);\n" +
            "} else {\n" +
            "vec3 transformedNormal = uNMatrix * aVertexNormal;\n" +
            "float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0);\n" +
            "vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting;\n" +
            "}\n" +
            "}";
  }
};
 
// Initialisation des shaders gérés par notre .
WebGL.prototype.initShaders = function (shaderType) {
  ...
 
  switch (shaderType) {
    ...
    case this.TEXTURE:
      this.initTextureShader();
      break;
    case this.TEXTURE_DIRECTIONAL_LIGHTING:
      this.initTextureShader();
      this.initNormalShader();
      this.initLightingShader();
      break;
  }
};
 
WebGL.prototype.initTextureShader = function () {
  this.shaderProgram.textureCoordAttribute = this.context.getAttribLocation(this.shaderProgram, "aTextureCoord");
  this.context.enableVertexAttribArray(this.shaderProgram.textureCoordAttribute);
  this.shaderProgram.samplerUniform = this.context.getUniformLocation(this.shaderProgram, "uSampler");
};
 
WebGL.prototype.initNormalShader = function () {
  this.shaderProgram.vertexNormalAttribute = this.context.getAttribLocation(this.shaderProgram, "aVertexNormal");
  this.context.enableVertexAttribArray(this.shaderProgram.vertexNormalAttribute);
  this.shaderProgram.nMatrixUniform = this.context.getUniformLocation(this.shaderProgram, "uNMatrix");
};
 
WebGL.prototype.initLightingShader = function () {
  this.shaderProgram.useLightingUniform = this.context.getUniformLocation(this.shaderProgram, "uUseLighting");
  this.shaderProgram.ambientColorUniform = this.context.getUniformLocation(this.shaderProgram, "uAmbientColor");
  this.shaderProgram.lightingDirectionUniform = this.context.getUniformLocation(this.shaderProgram, "uLightingDirection");
  this.shaderProgram.directionalColorUniform = this.context.getUniformLocation(this.shaderProgram, "uDirectionalColor");
};
 
WebGL.prototype.initTexture = function (texture) {
  this.context.pixelStorei(this.context.UNPACK_FLIP_Y_WEBGL, true);
  this.context.bindTexture(this.context.TEXTURE_2D, texture);
  this.context.texImage2D(this.context.TEXTURE_2D, 0, this.context.RGBA, this.context.RGBA, this.context.UNSIGNED_BYTE, texture.image);
  this.context.texParameteri(this.context.TEXTURE_2D, this.context.TEXTURE_MAG_FILTER, this.context.NEAREST);
  this.context.texParameteri(this.context.TEXTURE_2D, this.context.TEXTURE_MIN_FILTER, this.context.LINEAR_MIPMAP_NEAREST);
  this.context.generateMipmap(this.context.TEXTURE_2D);
  this.context.bindTexture(this.context.TEXTURE_2D, null);
};
 
// Mise en place des matrices de transformations.
WebGL.prototype.setMatrixUniforms = function () {
  this.context.uniformMatrix4fv(this.shaderProgram.pMatrixUniform, false, this.pMatrix);
  this.context.uniformMatrix4fv(this.shaderProgram.mvMatrixUniform, false, this.mvMatrix);
 
  var normalMatrix = this.mat3.create();
  this.mat4.toInverseMat3(this.mvMatrix, normalMatrix);
  this.mat3.transpose(normalMatrix);
  this.context.uniformMatrix3fv(this.shaderProgram.nMatrixUniform, false, normalMatrix);
};

Le code de la page se "limite" alors à ceci :

<!DOCTYPE HTML>
<html>
  <head>
    <title>3D World (HTML5 Canvas Cookbook)</title>
    <!-- required to use WebGLHelper object -->
    <!-- <script type="text/javascript" src="http://iewebgl.com/scripts/webglhelper.js"></script> -->
    <script type="text/javascript" src="script/webglhelper.js"></script>
    <!-- La librairie de manipulation de matrices de Brandon Jones -->
    <script type="text/javascript" src="script/gl-matrix-min.js"></script>
    <!-- Notre librairie d'animation et d'aide WebGL -->
    <script type="text/javascript" src="script/WebGL_2.js"></script>
    <script type="text/javascript">
      /*************************************
      * Controller
      */
      function Controller(canvasElement) {
        this.view = new View(this, canvasElement);
        this.gl = new WebGL(canvasElement);
        this.gl.setShaderProgram("TEXTURE_DIRECTIONAL_LIGHTING");
        this.model = new Model(this);
 
        // Positionnement initial de la caméra.
        this.gl.setInitialCameraPosition(0, 1.5, 5, 0, 0, 0);
        this.gl.resetCameraPosition();
        // Définition des vitesses de déplacement de la caméra.
        this.gl.setHorizontalSpeed(8);
        this.gl.setVerticalSpeed(1);
 
        // Activation de la caméra.
        this.gl.activateCameraTracking();
 
        var sources = {
          crate: "image/crate.jpg",
          metalFloor: "image/metalFloor.jpg",
          metalWall: "image/metalWall.jpg",
          ceiling: "image/ceiling.jpg"
        };
 
        var that = this;
        this.loadTextures(sources, function () {
          that.gl.setStage(function () {
            that.view.stage();
          });
          that.gl.startAnimation();
        });
      }
 
      Controller.prototype.loadTextures = function(sources, callback){
        var gl = this.gl;
        var context = gl.getContext();
        var textures = this.model.textures;
        var loadedImages = 0;
        var numImages = 0;
        for (var src in sources) {
          // anonymous function to induce scope
          (function () {
            var key = src;
            numImages++;
            textures[key] = context.createTexture();
            textures[key].image = new Image();
            textures[key].image.onload = function () {
              gl.initTexture(textures[key]);
              if (++loadedImages >= numImages) {
                callback();
              }
            };
 
            textures[key].image.src = sources[key];
          })();
        }
      };
 
      /*************************************
      * Model
      */
      function Model(controller) {
        this.controller = controller;
        this.cubeBuffers = {};
        this.floorBuffers = {};
        this.wallBuffers = {};
        this.textures = {};
        this.cratePositions = [];
 
        this.initBuffers();
        this.initCratePositions();
      }
 
      Model.prototype.initCratePositions = function () {
        var crateRange = 45;
        // randomize 20 floor crates
        for (var n = 0; n < 20; n++) {
          var cratePos = {};
          cratePos.x = (Math.random() * crateRange * 2) - crateRange;
          cratePos.y = 0;
          cratePos.z = (Math.random() * crateRange * 2) - crateRange;
          cratePos.rotationY = Math.random() * Math.PI * 2;
          this.cratePositions.push(cratePos);
 
          if (Math.round(Math.random() * 3) == 3) {
            var stackedCratePosition = {};
            stackedCratePosition.x = cratePos.x;
            stackedCratePosition.y = 2.01;
            stackedCratePosition.z = cratePos.z;
            stackedCratePosition.rotationY = cratePos.rotationY + ((Math.random() * Math.PI / 8) - Math.PI / 16);
            this.cratePositions.push(stackedCratePosition);
          }
        }
      };
 
      Model.prototype.initCubeBuffers = function(){
        var gl = this.controller.gl;
        this.cubeBuffers.positionBuffer = gl.createArrayBuffer([
          -1, -1,  1,  1, -1,  1,  1,  1,  1, -1,  1,  1, // Front face
          -1, -1, -1, -1,  1, -1,  1,  1, -1,  1, -1, -1, // Back face
          -1,  1, -1, -1,  1,  1,  1,  1,  1,  1,  1, -1, // Top face
          -1, -1, -1,  1, -1, -1,  1, -1,  1, -1, -1,  1, // Bottom face
           1, -1, -1,  1,  1, -1,  1,  1,  1,  1, -1,  1, // Right face
          -1, -1, -1, -1, -1,  1, -1,  1,  1, -1,  1, -1  // Left face
        ]);
 
        this.cubeBuffers.normalBuffer = gl.createArrayBuffer([
           0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1, // Front face
           0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1, // Back face
           0,  1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0, // Top face
           0, -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0, // Bottom face
           1,  0,  0,  1,  0,  0,  1,  0,  0,  1,  0,  0, // Right face
          -1,  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0  // Left face
        ]);
 
        this.cubeBuffers.textureBuffer = gl.createArrayBuffer([
          0, 0, 1, 0, 1, 1, 0, 1, // Front face
          1, 0, 1, 1, 0, 1, 0, 0, // Back face
          0, 1, 0, 0, 1, 0, 1, 1, // Top face
          1, 1, 0, 1, 0, 0, 1, 0, // Bottom face
          1, 0, 1, 1, 0, 1, 0, 0, // Right face
          0, 0, 1, 0, 1, 1, 0, 1  // Left face
        ]);
 
        this.cubeBuffers.indexBuffer = gl.createElementArrayBuffer([
           0,  1,  2,  0,  2,  3, // Front face
           4,  5,  6,  4,  6,  7, // Back face
           8,  9, 10,  8, 10, 11, // Top face
          12, 13, 14, 12, 14, 15, // Bottom face
          16, 17, 18, 16, 18, 19, // Right face
          20, 21, 22, 20, 22, 23  // Left face
        ]);
      };
 
      Model.prototype.initFloorBuffers = function () {
        var gl = this.controller.gl;
        this.floorBuffers.positionBuffer = gl.createArrayBuffer([
          -50, 0, -50, -50, 0, 50, 50, 0, 50, 50, 0, -50
        ]);
 
        this.floorBuffers.textureBuffer = gl.createArrayBuffer([
          0, 25, 0, 0, 25, 0, 25, 25
        ]);
 
        this.floorBuffers.indexBuffer = gl.createElementArrayBuffer([
          0, 1, 2, 0, 2, 3
        ]);
 
        // floor normal points upwards
        this.floorBuffers.normalBuffer = gl.createArrayBuffer([
          0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0
        ]);
      };
 
      Model.prototype.initWallBuffers = function(){
        var gl = this.controller.gl;
        this.wallBuffers.positionBuffer = gl.createArrayBuffer([
          -50, 5, 0, 50, 5, 0, 50, -5, 0, -50, -5, 0
        ]);
 
        this.wallBuffers.textureBuffer = gl.createArrayBuffer([
          0, 0, 25, 0, 25, 1.5, 0, 1.5
        ]);
 
        this.wallBuffers.indexBuffer = gl.createElementArrayBuffer([
          0, 1, 2, 0, 2, 3
        ]);
 
        // floor normal points upwards
        this.wallBuffers.normalBuffer = gl.createArrayBuffer([
          0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1
        ]);
      };
 
      Model.prototype.initBuffers = function () {
        this.initCubeBuffers();
        this.initFloorBuffers();
        this.initWallBuffers();
      };
 
      /*************************************
      * View
      */
      function View(controller, canvasElement) {
        this.controller = controller;
        this.canvas = canvasElement;
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
      }
 
      View.prototype.drawFloor = function () {
        var controller = this.controller;
        var gl = controller.gl;
        var model = controller.model;
        var floorBuffers = model.floorBuffers;
 
        gl.save();
        gl.translate(0, -1.1, 0);
        gl.pushPositionBuffer(floorBuffers);
        gl.pushNormalBuffer(floorBuffers);
        gl.pushTextureBuffer(floorBuffers, model.textures.metalFloor);
        gl.pushIndexBuffer(floorBuffers);
        gl.drawTriangleElements(floorBuffers);
        gl.restore();
      };
 
      View.prototype.drawCeiling = function(){
        var controller = this.controller;
        var gl = controller.gl;
        var model = controller.model;
        var floorBuffers = model.floorBuffers;
 
        gl.save();
        gl.translate(0, 8.9, 0);
        // use floor buffers with ceiling texture
        gl.pushPositionBuffer(floorBuffers);
        gl.pushNormalBuffer(floorBuffers);
        gl.pushTextureBuffer(floorBuffers, model.textures.ceiling);
        gl.pushIndexBuffer(floorBuffers);
        gl.drawTriangleElements(floorBuffers);
        gl.restore();
      };
 
      View.prototype.drawCrates = function () {
        var controller = this.controller;
        var gl = controller.gl;
        var model = controller.model;
        var cubeBuffers = model.cubeBuffers;
 
        for (var n = 0; n < model.cratePositions.length; n++) {
          gl.save();
          var cratePos = model.cratePositions[n];
          gl.translate(cratePos.x, cratePos.y, cratePos.z);
          gl.rotate(cratePos.rotationY, 0, 1, 0);
          gl.pushPositionBuffer(cubeBuffers);
          gl.pushNormalBuffer(cubeBuffers);
          gl.pushTextureBuffer(cubeBuffers, model.textures.crate);
          gl.pushIndexBuffer(cubeBuffers);
          gl.drawTriangleElements(cubeBuffers);
          gl.restore();
        }
      };
 
      View.prototype.drawWalls = function(){
        var controller = this.controller;
        var gl = controller.gl;
        var model = controller.model;
        var wallBuffers = model.wallBuffers;
        var metalWallTexture = model.textures.metalWall;
 
        gl.save();
        gl.translate(0, 3.9, -50);
        gl.pushPositionBuffer(wallBuffers);
        gl.pushNormalBuffer(wallBuffers);
        gl.pushTextureBuffer(wallBuffers, metalWallTexture);
        gl.pushIndexBuffer(wallBuffers);
        gl.drawTriangleElements(wallBuffers);
        gl.restore();
 
        gl.save();
        gl.translate(0, 3.9, 50);
        gl.rotate(Math.PI, 0, 1, 0);
        gl.pushPositionBuffer(wallBuffers);
        gl.pushNormalBuffer(wallBuffers);
        gl.pushTextureBuffer(wallBuffers, metalWallTexture);
        gl.pushIndexBuffer(wallBuffers);
        gl.drawTriangleElements(wallBuffers);
        gl.restore();
 
        gl.save();
        gl.translate(50, 3.9, 0);
        gl.rotate(Math.PI * 1.5, 0, 1, 0);
        gl.pushPositionBuffer(wallBuffers);
        gl.pushNormalBuffer(wallBuffers);
        gl.pushTextureBuffer(wallBuffers, metalWallTexture);
        gl.pushIndexBuffer(wallBuffers);
        gl.drawTriangleElements(wallBuffers);
        gl.restore();
 
        gl.save();
        gl.translate(-50, 3.9, 0);
        gl.rotate(Math.PI / 2, 0, 1, 0);
        gl.pushPositionBuffer(wallBuffers);
        gl.pushNormalBuffer(wallBuffers);
        gl.pushTextureBuffer(wallBuffers, metalWallTexture);
        gl.pushIndexBuffer(wallBuffers);
        gl.drawTriangleElements(wallBuffers);
        gl.restore();
      };
 
      View.prototype.stage = function(){
        var controller = this.controller;
        var gl = controller.gl;
        var model = controller.model;
        var view = controller.view;
 
        gl.clear();
        gl.initializeMatrix();
        gl.perspective(45, 0.1, 150.0);
 
        // enable lighting
        gl.enableLighting();
        gl.setAmbientLighting(0.5, 0.5, 0.5);
        gl.setDirectionalLighting(-0.25, -0.25, -1, 0.8, 0.8, 0.8);
 
        view.drawFloor();
        view.drawWalls();
        view.drawCeiling();
        view.drawCrates();
      }
 
      function OnGLCanvasCreated(canvasElement, elementId) {
        window.setTimeout(function () {
          new Controller(canvasElement);
        }, 500);
      }
 
      function OnGLCanvasFailed(canvasElement, elementId) {
        alert("WebGL n'est pas supporté par votre butineur !");
      }
    </script>
  </head>
  <body>
    <script id="WebGLCanvasCreationScript" type="text/javascript">
      WebGLHelper.CreateGLCanvasInline('glCanvas', OnGLCanvasCreated, OnGLCanvasFailed)
    </script>
  </body>
</html>

Ce code et plus concis car toutes la gestion de l'interaction avec l'utilisateur se trouve maintenant dans la classe WebGL. Avec cette version, il est même possible de se déplacer verticalement vu que la classe WebGL sait le faire.

Nous voici arrivé à la fin de ce billet. Nous sommes maintenant capables de rendre tout type de scène, même relativement complexe.
Evidement l'introduction de la partie 3D classique a été assez violente. Encore une fois, comme cela a déjà été écrit dans le second billet de cette série, Internet regorge de bons tutoriaux sur la question. En faire un nouveau, très probablement moins bon, ne présente aucun intérêt.

Si vous souhaitez récupérer le code à jour, c'est ici

A bientôt pour la suite de nos aventures.

Publicité
Publicité
Commentaires
Fanfois et ses papillons
Publicité
Archives
Publicité