WebGL

Introduction

Généralités

La programmation graphique fait appel à de nombreuses techniques
dont la finalité est de permettre de visualiser des scènes 3D ou 2D.

Elle réunit plusieurs phases aussi fastidieuses qu'essentielles :

  • Algèbre linéaire (vecteurs & matrices)
  • Rasterization (primitives → pixels)
  • Shaders (textures, éclairage, etc.).

Historique

OpenGL vs. DirectX

Les deux principales bibliothèques ont longtemps été :

  • OpenGL est une interface de programmation
    développée, à l’origine, par Silicon Graphics (1992).
  • Direct3D, sous-ensemble de DirectX,
    est l’interface développée par Microsoft (1996).

Généalogie

WebGL 2.0 est issu d'OpenGL ES 3.0 (embedded systems = systèmes embarqués),
lui-même dérivé d'OpenGL 3.3 et 4.2.

Dédié à HTML5, il fait office d'interface entre JavaScript et les drivers OpenGL ES.

(Windows + ANGLE : la couche OpenGL ES est émulée via Direct3D)

Pipeline

Contexte

  • Indispensable pour exécuter du code WebGL
  • Supporté par les versions récentes de tous les navigateurs
  • Disponible à travers l'objet <canvas> de HTML5
var canvas = document.getElementById (id);
var gl = canvas.getContext ('webgl2');
if (gl) gl.clear (gl.COLOR_BUFFER_BIT);
else alert ('Navigateur incompatible !');

Hello, world!


          

three.js

Standard de facto du web 3D.

  • Bilbiothèque haut niveau : caméras, matériaux, etc.
  • Graphe de scène : animation hiérarchique, articulations
  • Chargement asynchrone de ressources
  • WebGL, SVG, CSS3D

Hello, three.js!


          

Babylon.js

Hello, Babylon.js!


          

Système de vision

Analogies avec la photographie

Photographe Paramètres d'une caméra
1 Arranger les éléments de la scène à capturer Composer une scène virtuelle Transformation de modélisation
2 Positionner l'appareil photo Positionner la caméra virtuelle Transformation de vision
3 Régler la focale de l'appareil photo Configurer une projection Transformation de projection
4 Choisir la taille des tirages photographiques Choisir les dimensions de l’image video Transformation de cadrage

Transformation de modélisation

Règle de la main droite

Coordonnées homogènes

  • Vecteurs codés sur quatre composantes
  • Transformations codées par des matrices 4×4
\[ \mathbf{p} = \begin{pmatrix}x \\ y \\ z \\ 1\end{pmatrix} \quad \mathbf{v} = \begin{pmatrix}x \\ y \\ z \\ 0\end{pmatrix} \quad \mathbf{Id} = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} \]

Translation

\[\begin{pmatrix} 1 & 0 & 0 & x \\ 0 & 1 & 0 & y \\ 0 & 0 & 1 & z \\ 0 & 0 & 0 & 1 \end{pmatrix}\]

THREE.Object3D.position

Rotation

\[\begin{pmatrix} x^2(1-c)+c & x y(1-c)-zs & x z(1-c)+ys & 0 \\ x y(1-c)+zs & y^2(1-c)+c & y z(1-c)-xs & 0 \\ x z(1-c)-ys & y z(1-c)+xs & z^2(1-c)+c & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}\] \[c = \cos\theta \qquad s = \sin\theta \qquad \sqrt{x^2+y^2+z^2} = 1\]

THREE.Object3D.rotation

Échelle

\[\begin{pmatrix} x & 0 & 0 & 0 \\ 0 & y & 0 & 0 \\ 0 & 0 & z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix}\]

THREE.Object3D.scale

Non-commutativité

\(\mathbf{T} \times \mathbf{R} \times \:\) \(\qquad \neq \qquad\) \(\mathbf{R} \times \mathbf{T} \times \:\)

Transformation de vision

  • Position relative de la caméra par rapport aux objets :
    translater la caméra de \(\mathbf{t}\) = translater tous les objets de \(-\mathbf{t}\).
  • Souvent, la matrice de la caméra est calculée à partir de trois paramètres :
    sa position, sa cible et un vecteur définissant la verticalité de la caméra.
var camera = …;
camera.position.set (px, py, pz);
var cible = new THREE.Vector3 (cx, cy, cz);
camera.lookAt (cible);
  • new THREE.TrackballControls (camera);
  • new THREE.FirstPersonControls (camera);

Transformation de projection1/2

volume observé par la caméra :
forme propre au type de projection

volume de vision normalisé :
\([-1,+1]\times[-1,+1]\times[-1,+1]\)

Transformation de projection2/2

Deux types de projection pour modéliser l'optique des caméras :

projection orthogonale
2D, visualisation scientifique, architecturale, etc.
projection perspective
perception de l'éloignement (jeux vidéos, cinéma, etc.)

Démo

Projection orthogonale

\[\begin{pmatrix} \frac{2}{x_r-x_l} & 0 & 0 & -\frac{x_r+x_l}{x_r-x_l} \\ 0 & \frac{2}{y_t-y_b} & 0 & -\frac{y_t+y_b}{y_t-y_b} \\ 0 & 0 & \frac{-2}{z_f-z_n} & -\frac{z_f+z_n}{z_f-z_n} \\ 0 & 0 & 0 & 1 \end{pmatrix}\]

new THREE.OrthographicCamera (xl, xr, yt, yb, zn, zf);

Projection perspective

\[\begin{pmatrix} \frac{f}{aspect} & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & \frac{z_n+z_f}{z_n-z_f} & \frac{2 z_n z_f}{z_n-z_f} \\ 0 & 0 & -1 & 0 \end{pmatrix}\]

où \(f = \cot\frac{fov_y}{2}\).

new THREE.PerspectiveCamera (fov, aspect, zn, zf);

Transformation de cadrage

gl.viewport (x, y, w, h);

  • Surface utilisée lors du dessin
  • Coordonnées dans le repère du <canvas>

gl.scissor (x, y, w, h);

  • Restriction lors du traitement des fragments
  • Zone rectangulaire appelée scissor box

matrix.js


          

Tableaux
de sommets

Vertex buffer objects1/2

  • Tableaux d'octets dans la mémoire graphique
  • Sources potentielles d'attributs de sommets
  • Structures et types connus du seul développeur

Vertex buffer objects2/2

// Création du VBO.
var vbo = gl.createBuffer ();

// Activation du VBO.
gl.bindBuffer (gl.ARRAY_BUFFER, vbo);

// Initialisation des données (avec copie).
var VERTICES = new Float32Array ([…]);
gl.bufferData (gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW);

// Désactivation du VBO.
gl.bindBuffer (gl.ARRAY_BUFFER, null);

Tableaux typés

Attributs de sommets1/2

gl.enableVertexAttribArray (index);

Activation d'un attribut identifié par son indice

gl.vertexAttribPointer (index, size, type, normalized, stride, offset);

Attributs de sommets2/2

Structure des données
// Activation du VBO.
gl.bindBuffer (gl.ARRAY_BUFFER, vbo);

// Activation de l'attribut nº 0.
gl.enableVertexAttribArray (0);
// Déclaration de la structure du tableau.
gl.vertexAttribPointer (0, 3, gl.FLOAT, false, 16, 0);

// Activation de l'attribut nº 1.
gl.enableVertexAttribArray (1);
// Déclaration de la structure du tableau.
gl.vertexAttribPointer (1, 3, gl.UNSIGNED_BYTE, true, 16, 12);

Primitives géométriques1/3

gl.POINTS

(cliquez pour redémarrer l'animation)

Primitives géométriques2/3

gl.LINES / gl.LINE_STRIP / gl.LINE_LOOP

(cliquez pour redémarrer l'animation)

Primitives géométriques3/3

gl.TRIANGLES / gl.TRIANGLE_STRIP / gl.TRIANGLE_FAN

(clic : redémarrer ; double clic : redistribuer les points)

Mode direct

gl.drawArrays (mode, first, count);

Indices de sommets 1/3

  • gl.TRIANGLE_STRIP et gl.TRIANGLE_FAN pas toujours adaptés
  • Nombreux sommets identiques dupliqués au sein du VBO
  • Deux sommets sont identiques si tous leurs attributs sont identiques
  • Ajout d'un second buffer pour accéder aux sommets de façon indirecte

Indices de sommets 2/3

Indices de sommets 3/3

// Création du VBO.
var indices = gl.createBuffer ();

// Activation du VBO.
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, indices);

// Initialisation des données (avec copie).
var INDICES = new Uint16Array ([…]);
gl.bufferData (gl.ELEMENT_ARRAY_BUFFER, INDICES, gl.STATIC_DRAW);

// Désactivation du VBO.
gl.bindBuffer (gl.ELEMENT_ARRAY_BUFFER, null);

Mode indirect

gl.drawElements (mode, count, type, offset);

vbo.js


          

Vertex array objects

  • Mémorisation de la structure du VBO !
  • Réduction du nombre de commandes GPU
  • Modes direct et indirect

vao.js


          

quad.js


          

Instanciation géométrique

  • Geometry instancing
  • Plusieurs instances d'un même objet
  • Attributs d'instances via un VBO supplémentaire

Maillages

new THREE.Mesh (geometry, material);

// Création d'un cube.
var geometry = new THREE.BoxGeometry (1, 1, 1);

// Création d'un matériau rouge uni.
var material = new THREE.MeshBasicMaterial ({color: 'red'});

// Création d'un maillage associant géométrie et matériau.
var mesh = new THREE.Mesh (geometry, material);

// Ajout du maillage à la scène.
scene.add (mesh);

Géométries1/2

// Création d'une géométrie vide.
var geometry = new THREE.Geometry ();

// Création des quatre sommets.
geometry.vertices.push (
  new THREE.Vector3 (-0.5, -0.5, 0),
  new THREE.Vector3 (+0.5, -0.5, 0),
  new THREE.Vector3 (-0.5, +0.5, 0),
  new THREE.Vector3 (+0.5, +0.5, 0)
);

// Création des deux faces.
geometry.faces.push (
  new THREE.Face3 (0, 1, 2),
  new THREE.Face3 (2, 1, 3)
);

Géométries2/2

Buffer geometries

var geometry = new THREE.BufferGeometry ();

geometry.addAttribute ('position',
  new THREE.BufferAttribute (
    new Float32Array ([
      -0.5, -0.5, 0,
      +0.5, -0.5, 0,
      -0.5, +0.5, 0,
      +0.5, +0.5, 0
    ]), 3));

geometry.setIndex (
  new THREE.BufferAttribute (
    new Uint16Array ([
      0, 1, 2,
      2, 1, 3
    ]), 1));

Buffer geometry loader

Chargement asynchrone d'un maillage JSON

// Gestionnaire de chargement.
var loader = new THREE.BufferGeometryLoader ();

// Déclenchement d'un chargement.
loader.load ('00/teapot.json',
  function (geometry) {
    var m = new THREE.MeshBasicMaterial ();
    scene.add (new THREE.Mesh (geometry, m));
  });

Élimination
des faces cachées

Ordre d'affichage

Si tous les triangles sont affichés dans n’importe quel ordre,
le dernier affiché recouvrira tous les autres, qu’ils soient devant ou derrière lui.

Algorithme du peintre 1/2

L’algorithme du peintre propose de dessiner les polygones
du plus lointain au plus proche.
Algorithme du peintre
fr.wikipedia.org

Algorithme du peintre 2/2

  • Cet algorithme est coûteux car il faut trier tous les polygones.
    Pour trier \(n\) primitives, il faut au minimum \(n\log(n)\) opérations.
  • Comment trier des polygones dont les intervalles de profondeur se chevauchent ?
    Ambiguité 1 Ambiguité 2

Partition binaire de l'espace (Wikipédia)

Z-buffer1/2

  • L’algorithme du Z-buffer permet de résoudre le problème des faces cachées à coût constant.
  • L’utilisation d’un 2e buffer, de dimensions égales à celles du framebuffer, permet de conserver la profondeur associée à chaque pixel.
  • Pour chaque pixel, on peut ainsi déterminer si celui-ci doit être conservé ou écrasé par un autre simplement en comparant les deux profondeurs.
// Activation du test de profondeur.
gl.enable (gl.DEPTH_TEST);
// Mode de filtrage : fragments les plus proches.
gl.depthFunc (gl.LESS);
// Effacement simultané des deux buffers.
gl.clear (gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);

Z-buffer2/2

Visibilité 1/2

  • L’étape de rasterization est la plus coûteuse.
    Certes, le Z-buffer permet de générer correctement les images
    mais il n’évite pas les calculs que nécessitent les faces cachées.
  • Besoin de tests simples, qui éliminent un grand nombre de primitives !

Visibilité 2/2

Culling

Back-face culling

  • Produit scalaire entre \(\mathbf{v}\) et \(\mathbf{n}\).
  • Élimine en moyenne 50 % des facettes.
// Activation du culling.
gl.enable (gl.CULL_FACE);
// Élimination des faces arrières :
// gl.FRONT, gl.BACK ou gl.FRONT_AND_BACK.
gl.cullFace (gl.BACK);

Alpha blending1/2

  • Effets de transparence
  • Opérateur OVER de Porter & Duff (Lucasfilm)
  • Combinaison de deux fragments :
    • celui que l'on s'apprête à écrire (source)
    • celui déjà présent dans le framebuffer (destination)
// Activation du blending.
gl.enable (gl.BLEND);
// Définition des facteurs de blending.
gl.blendFunc (src, dst);

T. Porter & T. Duff. Compositing Digital Images. Computer Graphics 18(3):253-259, July 1984.

Alpha blending2/2

src dst R G B A

Shaders

Programmation GPU

  • Principe introduit dans RenderMan de Pixar
  • Programmabilité de certaines étapes clés
  • Code exécuté par le processeur graphique
  • Massivement parallèle

OpenGL ES Shading Language

  • ESSL 3.0, ou GLSL ES 3.0, dérivé de GLSL 3.3
  • Compatibilité OpenGL, OpenGL ES et WebGL
  • Types natifs adaptés à l'algèbre linéaire
  • Syntaxe proche du C

Rasterization

Pipeline

Types natifs

Scalaires floatintuintbool

Vecteurs vec2ivec2uvec2bvec2
vec3ivec3uvec3bvec3
vec4ivec4uvec4bvec4

Matrices mat2mat3x2mat4x2
mat2x3mat3mat4x3
mat2x4mat3x4mat4

Qualificatifs de type1/3

const
Constante substituée directement à la compilation
uniform
Quasi-constante accessible par tous,
modifiable uniquement hors rendu (lecture seule)

Qualificatifs de type2/3

#version 100

attribute
Variable d'entrée d'un vertex shader,
issue directement du VBO actif (lecture seule)
varying
Variable de sortie d'un vertex shader
Variable d'entrée d'un fragment shader,
interpolée à partir des valeurs définies aux sommets

Qualificatifs de type3/3

#version 300 es

in
Attribut d'entrée d'un vertex shader
Variable d'entrée d'un fragment shader (interpolée)
out
Variable de sortie d'un vertex shader (interpolable)
Résultat d'un fragment shader (ex : couleur)

Swizzling1/2

  • Accès aux composantes d'un vecteur
  • Création de nouveaux vecteurs par ré-arrangement
  • Différents jeux de noms selon le type d'utilisation

x y z w / r g b a / s t p q

Swizzling2/2

vec4 v1;
float f = v1.x + v1.y;

vec2 v2 = vec2 (1, 2);
vec4 v3 = v2.xyxx;           // 1 2 1 1
vec3 v4 = v3.zyy;            // 1 2 2

vec4 v5;
v5.wzyx = vec4 (1, 2, 3, 4); // 4 3 2 1
v5.zx   = vec2 (5, 6);       // 6 3 5 1

Vertex shaders1/2

  • Exécutés une fois pour chaque sommet transmis au GPU
  • Accès en lecture seule à tous les attributs de sommets
  • Peuvent déclarer des variables de type varying :
    position, normale, couleur, coordonnées de textures, etc.
  • Doivent définir la position 2D du sommet : gl_Position

Vertex shaders2/2

#version 300 es
precision mediump float;
in vec2 position;
void main ()
{
  gl_Position = vec4 (position, 0.0, 1.0);
}

Fragment shaders1/2

  • Exécutés une fois pour chaque fragment issu de la rasterization
  • Paramètres de type varying (interpolation bilinéaire)
  • Doivent définir la couleur du fragment : gl_FragColor

Fragment shaders2/2

#version 100
precision mediump float;
uniform vec4 color;
// out vec4 gl_FragColor
void main ()
{
  gl_FragColor = color;
}
#version 300 es
precision mediump float;
uniform vec4 color;
out vec4 fragment;
void main ()
{
  fragment = color;
}

Programmes de rendu1/2

  • Paire vertex shader + fragment shader
  • Correspondance numéros ↔ noms d'attributs
  • Compilation des shaders
  • Édition des liens du programme
  • Indispensable pour effectuer un rendu

Programmes de rendu2/2

« Hello, world! »

program.js


          

coloring-book.js


          

Pour les curieux…

Shadertoy

Firefox Shader Editor

Textures

Intérêt

Exemples

Cycle de vie

// Crée un objet texture.
var texture = gl.createTexture ();

// Associe la texture à l'unité 2D.
gl.bindTexture (gl.TEXTURE_2D, texture);

// Transfère les pixels dans la texture 2D actuelle.
var pixels = new Uint8Array ([…]);
gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGB,
               w, h, 0,
               gl.RGB, gl.UNSIGNED_BYTE, pixels);

// Libère l'objet texture.
gl.deleteTexture (texture);

DOM

// Élément <img>
var image = document.getElementById ('huey');
gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA,
               gl.RGBA, gl.UNSIGNED_BYTE, image);

// Élément <canvas>
var canvas = document.getElementById ('dewey');
gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA,
               gl.RGBA, gl.UNSIGNED_BYTE, canvas);

// Élément <video>
var video = document.getElementById ('louie');
gl.texImage2D (gl.TEXTURE_2D, 0, gl.RGBA,
               gl.RGBA, gl.UNSIGNED_BYTE, video);

Coordonnées de texture

Gestion des bords 1/2

gl.texParameteri (gl.TEXTURE_2D, param, value);

Deux paramètres à configurer pour le filtrage :

gl.TEXTURE_WRAP_Sabscisses dans l'espace texture
gl.TEXTURE_WRAP_Tordonnées dans l'espace texture

Trois modes possibles :

gl.CLAMP_TO_EDGEcoordonnée bornée entre 0 et 1
gl.REPEATrépétition de la texture
gl.MIRRORED_REPEATrépétition avec réflexion

Gestion des bords 2/2

Filtrage 1/2

gl.texParameteri (gl.TEXTURE_2D, param, value);

Deux paramètres à configurer pour le filtrage :

gl.TEXTURE_MIN_FILTERla texture est réduite à l'écran
gl.TEXTURE_MAG_FILTERla texture est agrandie à l'écran

Deux modes possibles (pour l’instant) :

gl.NEARESTcouleur du pixel le plus proche
gl.LINEARcouleur interpolée linéairement

Filtrage 2/2

Mipmaps1/4

Mipmaps2/4

« Multum in parvo »

gl.generateMipmap (gl.TEXTURE_2D);

Mipmaps3/4

gl.TEXTURE_MIN_FILTER

Quatre modes supplémentaires :
gl.NEAREST_MIPMAP_NEAREST mipmap le plus proche
gl.LINEAR_MIPMAP_NEAREST
gl.NEAREST_MIPMAP_LINEAR interpolation mipmaps
gl.LINEAR_MIPMAP_LINEAR

Mipmaps4/4

// EXT_texture_filter_anisotropic.
if (e = gl.getExtension ('EXT_texture_filter_anisotropic'))
{
  // Valeur maximale supportée par l'implémentation.
  var m = gl.getParameter (e.MAX_TEXTURE_MAX_ANISOTROPY_EXT);

  // Application à la texture courante. 
  gl.texParameterf (gl.TEXTURE_2D, e.TEXTURE_MAX_ANISOTROPY_EXT, m);
}

1 | 2

Non-puissances de deux

OpenGL s'est longtemps limité aux textures
dont chaque dimension était une puissance de deux.

Cette contrainte affecte toujours OpenGL ES 2.0
et l'usage de textures « NPOT » reste limité :

  • Pas de filtrage !
  • Pas de répétition !
  • Pas de mipmaps !

Cube mapping1/2

Y+
X- Z+ X+ Z-
Y-

Cube mapping2/2

Éclairage,
ombrage

Modèles d’éclairement

Déterminent la couleur en chaque point de la surface
en fonction de propriétés optiques ou empiriques.

  • Matériau
  • Lumière
  • Type d'éclairage
  • Type d'ombrage

Matériau

Comportement vis-à-vis de la lumière qui l'atteint.

Ensemble de caractéristiques propre à chaque objet :

  • couleur
  • opacité
  • rugosité
  • réflexion
  • réfraction
  • etc.

Lumière

Source ambiante
Couleur
Source ponctuelle
Couleur + position + direction (une ampoule)
Source directionnelle
Couleur + direction (le soleil)
Spot
Source ponctuelle avec angle d'ouverture

Éclairage ambiant

  • Interréflexion de la lumière :
    • la lumière rebondit sur les objets
    • perte d'énergie à chaque rebond
  • Simplification radicale :
    • contribution identique partout,
      quelles que soient position et orientation
    • couleur uniforme sur tous les objets

Éclairage de Lambert 1/2

Lambert
  • Modèle physique basé sur la loi de Lambert
  • Surfaces idéalement diffuses (= lambertiennes)
\[C_d = K_d L_d \cos\theta = K_d L_d (\mathbf{N} \cdot \mathbf{L})\]

Éclairage de Lambert 2/2

Éclairage de Phong 1/3

Phong
  • Développé par Bùi Tường Phong en 1973
  • Perception des tâches de réflexion spéculaire
\[C_s = K_s L_s \cos^n\phi = K_s L_s (\mathbf{V} \cdot \mathbf{R})^n\]

Bùi Tường Phong. Illumination for Computer Generated Pictures. Communications of the ACM 18(6):311-317, June 1975

Éclairage de Phong 2/3

Ambiant + Lambert + Phong
fr.wikipedia.org

Éclairage de Phong 3/3

Éclairage Blinn-Phong 1/2

Phong
  • Proposé par Jim Blinn en 1977
  • Version simplifiée/accélérée de l'éclairage de Phong
  • Évaluation de \(\mathbf{R}\) relativement coûteuse
\[C_s = K_s L_s (\mathbf{N} \cdot \mathbf{H})^n\]

James F. Blinn. Models of Light Reflection for Computer Synthesized Pictures. SIGGRAPH Comput. Graph. 11(2):192-198, July 1977

Éclairage Blinn-Phong 2/2

Phong vs. Blinn-Phong
fr.wikipedia.org

Modèles d’ombrage

Ombrage plat Ombrage de Gouraud Ombrage de Phong
1 normale pour 1 facette 1 normale pour 1 sommet 1 normale pour 1 fragment
couleur uniforme interpolation de la couleur interpolation de la normale

Matériaux three.js

Lumières three.js

Démo

Fin

  1. RefCard 1.0
  2. RefCard 2.0