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

HTML5 04 (tutorial WebGL, 3ème partie)

Dans le précédent billet nous avons pu visualiser les trois axes du référentiel du contexte WebGL. Nous avons même réussi à faire se déplacer le référentiel. Cela dit, à moins d'être très attentif et de ne pas quitter des yeux le référentiel, il est assez hasardeux de dire à un instant t, quel est l'axe X ou quel est l'axe Z. L'axe Y étant l'axe de rotation, il est plus facile à identifier.
Pour faciliter l'identification des axes, nous allons attribuer à chacun une couleur particulière.

Pour faire cela nous allons devoir modifier le code des DEUX shaders. Evidement cette modification devra s'accompagner une modification équivalente de leur initialisation.
Cela met en évidence le fait que le code des shaders ne peut pas être totalement quelconque par rapport à notre classe WebGL.
Pour gérer ce problème il y a deux approches :

  1. inclure le code des shaders dans la classe. C'est la solution adoptée dans le livre HTML5 Canvas Cookbook ;
  2. laisser chacun définir le code de ses shaders et fournir du coup une librairie contenant beaucoup moins de chose. C'est la solution adoptée dans le livre Professional WebGL Programming: Developing 3D Graphics for the Web.

La solution retenue pour ces billets est la première, tout en laissant la porte ouverte (ce que ne fait pas le code du livre) à des shaders "custom". La version finale de la classe WebGL que nous construisons peut d'ailleurs être prise pour une version "améliorée" de celle du livre. Améliorée, car elle offre de nouvelles possibilités, pour peu de l'on garde ce qui est présent dans la version du livre.
Dans cette optique, nous allons redécouper le code actuel de notre classe pour nous conformer au découpage de la classe WebGL de HTML5 Canvas Cookbook.
La nouvelle version du Javascript, WebGL_2.js, est la suivante :

/******************************************************************************
 * Classe permettant de faciliter la gestion de WebGL avec prise en compte
 * des l'animation.
 * Ce code est inspiré de celui du livre HTML5 Canvas Cookbook qui se trouve
 * à l'adresse suivante : http://www.html5canvastutorials.com/cookbook/
 ******************************************************************************/
var WebGL = function (canvasElement) {
  // Le canvas et le contexte WebGL (ou son équivalent IEWebGL).
  this.canvas = canvasElement;
  this.context = WebGLHelper.GetGLContext(canvasElement);
  this.stage = undefined;
 
  // Le nécessaire pour gérer l'animation.
  this.t = 0;
  this.timeInterval = 0;
  this.startTime = 0;
  this.lastTime = 0;
  this.frame = 0;
  this.animating = false;
 
  // Provided by Paul Irish.
  // Permet de faire fonctionner l'animation sur tous les butineurs.
  window.requestAnimFrame = (function (callback) {
    return window.requestAnimationFrame ||
           window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame ||
           window.oRequestAnimationFrame ||
           window.msRequestAnimationFrame ||
           function (callback) {
             window.setTimeout(callback, 1000 / 60);
           };
  })();
 
  // Encapsulation des matrices globales de glMatrix.
  this.mat3 = mat3;
  this.mat4 = mat4;
  this.vec3 = vec3;
 
  // Les constantes définissant les types de shader possible.
  this.FIXED_COLOR = "FIXED_COLOR";
  this.VARYING_COLOR = "VARYING_COLOR";
 
  // Le nécessaire pour WebGL.
  this.shaderProgram = null;
  this.mvMatrix = this.mat4.create();
 
  // La fonction qui permet à setCustomShaderProgram initialiser les shaders.
  this.initCustomShaders = undefined;
 
  // Doit-on afficher des alertes ?
  this.showAlerts = true;
 
  // Initialisation générale du contexte WebGL.
  this.context.viewportWidth = this.canvas.width;
  this.context.viewportHeight = this.canvas.height;
};
 
/******************************************************************************
 * Méthodes générales.
 ******************************************************************************/
WebGL.prototype.getContext = function () {
  return this.context;
};
 
WebGL.prototype.getCanvas = function () {
  return this.canvas;
};
 
WebGL.prototype.setClearColor = function (r, g, b, a) {
  this.context.clearColor(r, g, b, a);
};
 
WebGL.prototype.clear = function () {
  this.context.viewport(0, 0, this.context.viewportWidth, this.context.viewportHeight);
  this.context.clear(this.context.COLOR_BUFFER_BIT);
};
 
WebGL.prototype.setStage = function (func) {
  this.stage = func;
};
 
/******************************************************************************
 * Gestion de l'animation.
 ******************************************************************************/
WebGL.prototype.isAnimating = function () {
  return this.animating;
};
 
WebGL.prototype.getFrame = function () {
  return this.frame;
};
 
WebGL.prototype.startAnimation = function () {
  this.animating = true;
  var date = new Date();
  this.startTime = date.getTime();
  this.lastTime = this.startTime;
 
  if (this.stage !== undefined) {
    this.stage();
  }
 
  this.animationLoop();
};
 
WebGL.prototype.stopAnimation = function () {
  this.animating = false;
};
 
WebGL.prototype.getTimeInterval = function () {
  return this.timeInterval;
};
 
WebGL.prototype.getTime = function () {
  return this.t;
};
 
WebGL.prototype.getFps = function () {
  return this.timeInterval > 0 ? 1000 / this.timeInterval : 0;
};
 
WebGL.prototype.animationLoop = function () {
  var that = this;
 
  this.frame++;
  var date = new Date();
  var thisTime = date.getTime();
  this.timeInterval = thisTime - this.lastTime;
  this.t += this.timeInterval;
  this.lastTime = thisTime;
 
  if (this.stage !== undefined) {
    this.stage();
  }
 
  if (this.animating) {
    requestAnimFrame(function () {
      that.animationLoop();
    });
  }
};
 
/******************************************************************************
 * Gestion de WebGL.
 ******************************************************************************/
// Construction du shader program en utilisant les codes shader internes.
WebGL.prototype.setShaderProgram = function (shaderType) {
  // Récupération des codes sources.
  var fragmentGLSL = this.getFragmentShaderGLSL(shaderType);
  var vertexGLSL = this.getVertexShaderGLSL(shaderType);
 
  // Création du programme.
  this.buildShaderProgram(fragmentGLSL, vertexGLSL);
 
  // Initialisation des shaders.
  this.initShaders(shaderType);
};
 
// Construction du shader program en utilisant des codes shader externes.
// Il faut impérativement définir initCustomShaders à l'aide de
// setInitCustomShaders AVANT d'appeler setCustomShaderProgram.
WebGL.prototype.setInitCustomShaders = function (func) {
  this.initCustomShaders = func;
};
 
WebGL.prototype.setCustomShaderProgram = function (vertexId, fragmentId) {
  // Récupération des codes sources.
  var vertexCode = document.getElementById(vertexId).firstChild.nodeValue;
  var fragmentCode = document.getElementById(fragmentId).firstChild.nodeValue;
 
  // Création du programme.
  this.buildShaderProgram(fragmentCode, vertexCode);
 
  // Initialisation des shaders.
  if (this.initCustomShaders !== undefined) {
    this.initCustomShaders();
  }
  else {
    console.log("initCustomShaders is undefined");
    if (this.showAlerts) {
      alert("initCustomShaders is undefined");
    }
  }
};
 
// Méthodes de manipulation des matrices.
WebGL.prototype.identity = function () {
  this.mat4.identity(this.mvMatrix);
};
 
WebGL.prototype.translate = function (x, y, z) {
  this.mat4.translate(this.mvMatrix, [x, y, z]);
};
 
WebGL.prototype.rotate = function (angle, x, y, z) {
  this.mat4.rotate(this.mvMatrix, angle, [x, y, z]);
};
 
// Création/initialisation d'un tampon WebGL.
WebGL.prototype.createArrayBuffer = function (vertices) {
  var buffer = this.context.createBuffer();
  buffer.numElements = vertices.length;
  this.context.bindBuffer(this.context.ARRAY_BUFFER, buffer);
  this.context.bufferData(this.context.ARRAY_BUFFER, new Float32Array(vertices), this.context.STATIC_DRAW);
  return buffer;
};
 
// Méthodes associant les tampons WebGL avec les shaders internes.
WebGL.prototype.pushPositionBuffer = function (buffers) {
  this.context.bindBuffer(this.context.ARRAY_BUFFER, buffers.positionBuffer);
  this.context.vertexAttribPointer(this.shaderProgram.vertexPositionAttribute, 3, this.context.FLOAT, false, 0, 0);
};
 
WebGL.prototype.pushFixedColor = function (r, g, b, a) {
  this.context.uniform4fv(this.shaderProgram.uColor, [r, g, b, a]);
};
 
WebGL.prototype.pushVaryingColorBuffer = function (buffers) {
  this.context.bindBuffer(this.context.ARRAY_BUFFER, buffers.colorBuffer);
  this.context.vertexAttribPointer(this.shaderProgram.vertexColorAttribute, 4, this.context.FLOAT, false, 0, 0);
};
 
// Méthodes provoquant le rendu de la scène à partir de points.
WebGL.prototype.drawLineArrays = function (buffers) {
  this.setMatrixUniforms();
  // draw arrays
  this.context.drawArrays(this.context.LINES, 0, buffers.positionBuffer.numElements / 3);
};
 
/******************************************************************************
 * Méthodes de services WebGL internes à la classe.
 * Ne devraient pas être appelées de l'extérieur.
 ******************************************************************************/
// Les différents codes shader gérés par la classe.
WebGL.prototype.getFragmentShaderGLSL = function (shaderType) {
  switch (shaderType) {
    case this.FIXED_COLOR:
      return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "uniform vec4 uColor;\n" +
            "void main(void) {\n" +
            "gl_FragColor = uColor;\n" +
            "}";
    case this.VARYING_COLOR:
      return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "varying vec4 vColor;\n" +
            "void main(void) {\n" +
            "gl_FragColor = vColor;\n" +
            "}";
  }
};
 
WebGL.prototype.getVertexShaderGLSL = function (shaderType) {
  switch (shaderType) {
    case this.FIXED_COLOR:
      return "attribute vec3 aVertexPosition;\n" +
            "uniform mat4 uMVMatrix;\n" +
            "void main(void) {\n" +
            "gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0);\n" +
            "}";
    case this.VARYING_COLOR:
      return "attribute vec3 aVertexPosition;\n" +
            "attribute vec4 aVertexColor;\n" +
            "uniform mat4 uMVMatrix;\n" +
            "varying vec4 vColor;\n" +
            "void main(void) {\n" +
            "gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0);\n" +
            "vColor = aVertexColor;\n" +
            "}";
  }
};
 
// Initialisation des shaders gérés par notre .
WebGL.prototype.initShaders = function (shaderType) {
  this.initPositionShader();
 
  switch (shaderType) {
    case this.FIXED_COLOR:
      this.initFixedColorShader();
      break;
    case this.VARYING_COLOR:
      this.initVaryingColorShader();
      break;
  }
};
 
WebGL.prototype.initPositionShader = function () {
  this.shaderProgram.vertexPositionAttribute = this.context.getAttribLocation(this.shaderProgram, "aVertexPosition");
  this.context.enableVertexAttribArray(this.shaderProgram.vertexPositionAttribute);
  this.shaderProgram.mvMatrixUniform = this.context.getUniformLocation(this.shaderProgram, "uMVMatrix");
};
 
WebGL.prototype.initFixedColorShader = function () {
  this.shaderProgram.uColor = this.context.getUniformLocation(this.shaderProgram, "uColor");
};
 
WebGL.prototype.initVaryingColorShader = function () {
  this.shaderProgram.vertexColorAttribute = this.context.getAttribLocation(this.shaderProgram, "aVertexColor");
  this.context.enableVertexAttribArray(this.shaderProgram.vertexColorAttribute);
};
 
// Génération du programme shader.
WebGL.prototype.buildShaderProgram = function (fragmentGLSL, vertexGLSL) {
  // Compilation du fragment shader.
  var fragmentShader = this.context.createShader(this.context.FRAGMENT_SHADER);
  this.context.shaderSource(fragmentShader, fragmentGLSL);
  this.context.compileShader(fragmentShader);
 
  // Compilation du vertex shader.
  var vertexShader = this.context.createShader(this.context.VERTEX_SHADER);
  this.context.shaderSource(vertexShader, vertexGLSL);
  this.context.compileShader(vertexShader);
 
  // Création du programme.
  this.shaderProgram = this.context.createProgram();
  this.context.attachShader(this.shaderProgram, vertexShader);
  this.context.attachShader(this.shaderProgram, fragmentShader);
  this.context.linkProgram(this.shaderProgram);
 
  // Pour savoir s'il y a eu un problème dans les phases précédentes.
  if (!this.context.getShaderParameter(fragmentShader, this.context.COMPILE_STATUS)) {
    console.log(this.context.getShaderInfoLog(fragmentShader));
    if (this.showAlerts) {
      alert("Could not compile fragment shaders");
    }
  }
  if (!this.context.getShaderParameter(vertexShader, this.context.COMPILE_STATUS)) {
    console.log(this.context.getShaderInfoLog(vertexShader));
    if (this.showAlerts) {
      alert("Could not compile vertex shaders");
    }
  }
  if (!this.context.getProgramParameter(this.shaderProgram, this.context.LINK_STATUS)) {
    console.log(this.context.getProgramInfoLog(program));
    if (this.showAlerts) {
      alert("Could not initialize shaders");
    }
  }
 
  this.context.useProgram(this.shaderProgram);
};
 
// Mise en place des matrices de transformations.
WebGL.prototype.setMatrixUniforms = function () {
  this.context.uniformMatrix4fv(this.shaderProgram.mvMatrixUniform, false, this.mvMatrix);
};

Voyons maintenant comment utiliser cette nouvelle version de la classe WebGL.js ?

Commençons par une version "custom", c'est à dire une version où le code des shaders est placé dans des balises scripts dédiées.
Pour cela recopions le code de la page WebGL_02.htm pour créer la page WebGL_03.htm. Cela fait nous pouvons remodeler le code pour l'adapter à son nouveau contexte. Nous obtenons alors :



<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 id="vertex" type="x-shader">
      attribute vec3 aVertexPosition;
 
      uniform mat4 uMVMatrix;
 
      void main(void) {
        gl_Position = uMVMatrix * vec4(aVertexPosition, 1.0);
      }
    </script>
    <script id="fragment" type="x-shader">
      #ifdef GL_ES
        precision highp float;
      #endif
 
      uniform vec4 uColor;
 
      void main(void) {
        gl_FragColor = uColor;
      }
    </script>
    <script type="text/javascript">
      function stage(gl, vbuffer, angle) {
        // Effacement de l'image précédente.
        gl.clear();
 
        // Déplacement de l'objet.
        gl.identity();
        //gl.translate(0.5, 0.0, 0.0);
        // Rotation au tour de X.
        gl.rotate(Math.PI * 0.3, 1, 0, 0);
        // Rotation au tour de Y.
        gl.rotate(angle, 0, 1, 0);
        //gl.translate(0.5, 0.0, 0.0);
 
        // Dessin de l'image courante.
        gl.context.uniform4fv(gl.shaderProgram.uColor, [1.0, 0.0, 0.0, 1.0]);
        gl.context.vertexAttribPointer(gl.shaderProgram.aVertexPosition, 3, gl.context.FLOAT, false, 0, 0);
        gl.context.uniformMatrix4fv(gl.shaderProgram.uMVMatrix, false, gl.mvMatrix);
        gl.context.drawLineArrays(gl.context.LINES, 0, vbuffer.numElements / 3);
      }
 
      function initWebGL(canvasElement) {
        // Création de notre classe d'aide.
        var gl = new WebGL(canvasElement);
 
        // Initialisation des shaders.
        gl.setInitCustomShaders(function () {
          gl.shaderProgram.uColor = gl.context.getUniformLocation(gl.shaderProgram, "uColor");
          gl.shaderProgram.aVertexPosition = gl.context.getAttribLocation(gl.shaderProgram, "aVertexPosition");
          gl.context.enableVertexAttribArray(gl.shaderProgram.aVertexPosition);
          gl.shaderProgram.uMVMatrix = gl.context.getUniformLocation(gl.shaderProgram, "uMVMatrix");
        });
        gl.setCustomShaderProgram("vertex", "fragment");
 
        // Définition de la couleur de fond.
        gl.setClearColor(1, 0.98, 0.8, 1);
 
        // Définition des points de nos axes.
        var vertices = new Float32Array([
            -0.5,  0.0,  0.0, 0.5, 0.0, 0.0, // axe des X
             0.0, -0.5,  0.0, 0.0, 0.5, 0.0, // axe des Y
             0.0,  0.0, -0.5, 0.0, 0.0, 0.5  // axe des Z
          ]);
        var vbuffer = gl.createArrayBuffer(vertices);
 
        // L'angle de rotation au tour de Y.
        var angle = 0;
 
        // Mise en place de la scène.
        gl.setStage(function () {
          // Calcule du nouvel angle.
          var angularVelocity = Math.PI / 4; // radians / second
          var angleEachFrame = angularVelocity * gl.getTimeInterval() / 1000;
          angle += angleEachFrame;
 
          stage(gl, vbuffer, 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>

On peut voir que c'est la méthode setCustomShaderProgram qui est utilisée pour générer le programme shader. De ce fait, initCustomShaders doit être définie avant. Cela est réalisé avec la méthode setInitCustomShaders.
Pour dessiner la scène, on trouve à la fin de la méthode locale stage, le code positionnant les tampons et forçant le dessin.

Voyons maintenant la solution utilisant le code des shaders se trouvant dans le classe WebGL. Dans le rar disponible à la fin de ce billet il s'agit de la page WebGL_04.htm:



<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">
      function stage(gl, lineBuffers, angle) {
        // Effacement de l'image précédente.
        gl.clear();
 
        // Déplacement de l'objet.
        gl.identity();
        //gl.translate(0.5, 0.0, 0.0);
        // Rotation au tour de X.
        gl.rotate(Math.PI * 0.3, 1, 0, 0);
        // Rotation au tour de Y.
        gl.rotate(angle, 0, 1, 0);
        //gl.translate(0.5, 0.0, 0.0);
 
        // Dessin de l'image courante.
        gl.pushFixedColor(1.0, 0.0, 0.0, 1.0);
        gl.pushPositionBuffer(lineBuffers);
        gl.drawLines(lineBuffers);
      }
 
      function initWebGL(canvasElement) {
        // Création de notre classe d'aide.
        var gl = new WebGL(canvasElement);
 
        // Initialisation des shaders.
        gl.setShaderProgram("FIXED_COLOR");
 
        // Définition de la couleur de fond.
        gl.setClearColor(1, 0.98, 0.8, 1);
 
        // Définition des tampons nécessaires à la construction de la scène.
        var lineBuffers = {}
        // Les points de nos axes.
        lineBuffers.positionBuffer = gl.createArrayBuffer([
        // X
          -0.5,  0.0,  0.0,
           0.5,  0.0,  0.0,
        // Y
           0.0, -0.5,  0.0,
           0.0,  0.5,  0.0,
        // Z
           0.0,  0.0, -0.5,
           0.0,  0.0,  0.5
        ]);
        // La couleur étant fixe c'est fini.
 
        // L'angle de rotation au tour de Y.
        var angle = 0;
 
        // Mise en place de la scène.
        gl.setStage(function () {
          // Calcule du nouvel angle.
          var angularVelocity = Math.PI / 4; // radians / second
          var angleEachFrame = angularVelocity * gl.getTimeInterval() / 1000;
          angle += angleEachFrame;
 
          stage(gl, lineBuffers, 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>

Sans surprise cette version est plus compacte vue qu'elle ne comporte plus de code source shader et qu'elle délègue à WebGL la gestion fine des shaders. Notons qu'il faut tout de même ne pas oublier d'appeler les méthodes offertes par WebGL pour positionner les tampons et forcer le dessin.

Nous allons partir de cette dernière version pour créer une nouvelle page, WebGL_05.htm, dans laquelle chaque axe à sa propre couleur.
Si vous avez lu attentivement le code de la classe WebGL, vous avez dû voir qu'il y a déjà ce qu'il faut.
Les shaders correspondants sont obtenus à l'aide de l'appel setShaderProgram("VARYING_COLOR"); qui remplace setShaderProgram("FIXED_COLOR");.
Evidemment, il faut définir les couleurs associées à chaque élément dessiné. Nous n'allons pas faire compliqué. X en rouge, Y en bleu et Z en vert. Comme nous avons maintenant plusieurs tableaux à définir nous allons ajoutez une méthode locale dédiée, initBuffers. Son code est le suivant :

function initBuffers(gl) {
  var lineBuffers = {}
 
  // Les points de nos axes.
  lineBuffers.positionBuffer = gl.createArrayBuffer([
  // X
    -0.5,  0.0,  0.0,
     0.5,  0.0,  0.0,
  // Y
     0.0, -0.5,  0.0,
     0.0,  0.5,  0.0,
  // Z
     0.0,  0.0, -0.5,
     0.0,  0.0,  0.5
  ]);
 
  // Les couleurs de nos axes.
  var colors = [
    [1.0, 0.0, 0.0, 1.0], // X - Red
    [0.0, 1.0, 0.0, 1.0], // Y - Green
    [0.0, 0.0, 1.0, 1.0]  // Z - Blue
  ];
  var colorVertices = [];
  for (var n in colors) {
    var color = colors[n];
    for (var i = 0; i < 2; i++) {
      colorVertices = colorVertices.concat(color);
    }
  }
  lineBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
  return lineBuffers;
}

A part appeler la méthode que nous venons de définir, l'autre modification à faire consiste à remplacer gl.pushFixedColor(1.0, 0.0, 0.0, 1.0); par gl.pushVaryingColorBuffer(lineBuffers);. Cela fait vous devez obtenir le résultat suivant :
html5 04 01
Imaginons que nous voulions, en plus, distinguer la partie négative de la partie positive. Une première solution consiste à tracer des demi-axes de couleur différente. Cela s'obtient en modifiant initBuffersde la manière suivante :

function initBuffers(gl) {
  var lineBuffers = {}
 
  // Les points de nos axes.
  lineBuffers.positionBuffer = gl.createArrayBuffer([
  // X
    -0.5,  0.0,  0.0,
     0.0,  0.0,  0.0,
     0.0,  0.0,  0.0,
     0.5,  0.0,  0.0,
  // Y
     0.0, -0.5,  0.0,
     0.0,  0.0,  0.0,
     0.0,  0.0,  0.0,
     0.0,  0.5,  0.0,
  // Z
     0.0,  0.0, -0.5,
     0.0,  0.0,  0.0,
     0.0,  0.0,  0.0,
     0.0,  0.0,  0.5
  ]);
 
  // Les couleurs de nos 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];
    for (var i = 0; i < 2; i++) {
      colorVertices = colorVertices.concat(color);
    }
  }
  lineBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
  return lineBuffers;
}

Le résultat est alors :
html5 04 02
C'est bien, mais ce qui serait encore mieux, ce serait un dégradé qui passe progressivement d'une couleur à l'autre. Et bien la chose est encore plus simple à obtenir car WebGL interpole toujours pour obtenir les valeurs intermédiaires :

function initBuffers(gl) {
  var lineBuffers = {}
 
  // Les points de nos axes.
  lineBuffers.positionBuffer = gl.createArrayBuffer([
  // X
    -0.5,  0.0,  0.0,
     0.5,  0.0,  0.0,
  // Y
     0.0, -0.5,  0.0,
     0.0,  0.5,  0.0,
  // Z
     0.0,  0.0, -0.5,
     0.0,  0.0,  0.5
  ]);
 
  // Les couleurs de nos 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);
  }
  lineBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
  return lineBuffers;
}

Voici le résultat :
html5 04 03
Evidement le choix du jaune sur fond jaune pâle est (très) discutable. Cela dit c'est l'ensemble des couleurs qui est bof. Par contre au niveau du principe, cela ouvre la porte à plein de choses.

Pour finir ce billet nous allons nous pencher sur un problème qui vous a surement sauté aux yeux dans l'avant dernière version d'initBuffers. Cette dernière contient beaucoup, trop, de définition du même point. L'origine en l'occurrence.
Ce problème étant relativement courant, et pouvant parfois couter très cher en mémoire, il existe une solution. Définir les points une seule fois dans un tableau de point et fournir un tableau d'indices qui donne la vraie définition de la scène.
Nous allons ajouter à notre classe WebGL les méthodes requises. Cela fait, nous construirons une nouvelle version de la page, WebGL_06.htm, qui utilise cette méthode de construction de la scène.
Commençons par ajouter les méthodes nécessaires dans la classe WebGL:

// Création/initialisation d'un tampon d'entiers WebGL.
WebGL.prototype.createElementArrayBuffer = function (vertices) {
  var buffer = this.context.createBuffer();
  buffer.numElements = vertices.length;
  this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, buffer);
  this.context.bufferData(this.context.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertices), this.context.STATIC_DRAW);
  return buffer;
};
 
WebGL.prototype.pushIndexBuffer = function (buffers) {
  this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, buffers.indexBuffer);
};
 
// Méthodes provoquant le rendu de la scène à partir d'indexes.
WebGL.prototype.drawLineElements = function (buffers) {
  this.setMatrixUniforms();
  // draw elements
  this.context.drawElements(this.context.LINES, buffers.indexBuffer.numElements, this.context.UNSIGNED_SHORT, 0);
};

Cela fait nous pouvons modifier initBuffers:

function initBuffers(gl) {
  var lineBuffers = {}
 
  // Les points de nos axes.
  lineBuffers.positionBuffer = gl.createArrayBuffer([
    // L'origine
     0.0, 0.0, 0.0,
    // X
    -0.5,  0.0,  0.0,
     0.5,  0.0,  0.0,
    // Y
     0.0, -0.5,  0.0,
     0.0,  0.5,  0.0,
    // Z
     0.0,  0.0, -0.5,
     0.0,  0.0,  0.5
  ]);
 
  // Les couleurs de nos axes.
  var colors = [
    [0.0, 0.0, 0.0, 1.0], // Origine - Noir
    [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);
  }
  lineBuffers.colorBuffer = gl.createArrayBuffer(colorVertices);
 
  // Les indexes des points à utiliser pour former le référentiel.
  lineBuffers.indexBuffer = gl.createElementArrayBuffer([
    1, 0, 0, 2, // X
    3, 0, 0, 4, // Y
    5, 0, 0, 6  // Z
  ]);
 
  return lineBuffers;
}

Ce code n'est pas très compliqué à comprendre. Le point important réside dans le fait que les couleurs sont associées aux points, pas aux indexes.
En clair, compte tenu du fait que l'origine n'est définie qu'une seule fois dans le tableau des points, il aura toujours la même couleur. Le résultat n'est donc pas équivalent à ce que nous avions précédemment.
html5 04 04

Cela clôture ce billet. Dans le prochain billet nous nous occuperons de l'observateur, la camera donc.

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

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