Pipeline Graphique en WebGL

Que se passe-t-il sur la carte graphique ?

  • Three.js effectue le rendu d'une scène
  • Comment cela marche en pratique ?
  • Que se passe-t-il sur la carte graphique ?

Le pipeline graphique

  • Une liste de sommets
  • Une liste d'attributs (couleurs, normales...)
  • Des relations d'adjacence
  • + autres... (caméras, lumières, textures...)
Pipeline

Une grille 2D de pixels

Le pipeline graphique simplifié

Pipeline

Le pipeline graphique

Opérations par sommet

Pipeline
  • En parallèle pour chaque sommet
  • Programmable : Vertex Program
  • Transformations géométriques et projection en 2D
  • Déplacement des sommets (déformation de la géométrie)
  • Au minimum : Position de chaque sommet (gl_Position)

Le pipeline graphique

Assemblage des primitives

Pipeline
  • Programmable : Geometry Program
  • Information d'adjacence (Identique pour tous les sommets)
  • Assemble les sommets 2D transformé
  • Produit une primitive dans le plan 2D

Le pipeline graphique

Rasterisation

Pipeline
  • Discretisation des polygones, tracés de ligne, remplissage des polygones
  • Génère un ensemble de fragments
  • Fragments de pixel
    • Correspond à une partie d'un pixel dans l'espace final
    • A une position fixe (le pixel)
  • Interpolation des attributs

Le pipeline graphique

Rasterisation

Pipeline
  • En parallèle pour chaque fragment
  • Programmable : Fragment Program
  • Calcul d'éclairage (eq. du rendu)
    • Changement d'apparence
    • La plupart des effets spéciaux
  • Au minimum : Couleur de chaque fragment

Le pipeline graphique

Combinaison des fragments

Pipeline
  • En parallèle pour chaque fragments
  • Elimination des parties cachées
  • Opérations de transparence
  • Mélange des fragments avec le frame buffer
  • Remplissage du frame buffer

Pour résumer

  • Un sommet de la géometrie : Un triplet XYZ
  • Opération par sommet : Vertex program
    • Transformations géometriques
    • Projections
  • Assemblage et Rasterisation
  • Fragment : Partie potentielle de pixel
  • Opération par fragment : Fragment program
    • Illumination
    • Apparence
  • Combinaison et remplissage du Frame Buffer

Les shaders

Le principe est de créer un materiau et d'ecrire les programmes correspondant pour changer l'execution du pipeline

Un style de materiau : MeshShaderMaterial


         var shaderMaterial = new THREE.MeshShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById( 'vertexShader' ).textContent,
        fragmentShader: document.getElementById( 'fragmentShader' ).textContent
        

En WebGL, il est obligatoire de définir un vertex et un fragment program (Three.js le fait pour les matériaux utilisés jusqu'a présent)

Un shader, qu'est ce que c'est ?

  • Un algorithme (ici en GLSL)
  • S'applique a tous les éléments d'une étape
  • S'exécute sur la carte graphique
  • Ne peux pas accéder directement à la mémoire de l'application
  • Doit être compilé au préalable
  • Algorithme instancié et executé une fois pour chaque élément
  • Programmation parallèle
    • Indépendance des données
    • Séparation des opérations

GLSL : OpenGL Shading Langage

  • Programmation du pipeline graphique
  • Langage de haut niveau basé sur du C
  • Des évolutions conjointes à OpenGL
  • En WebGL, shaders limmités à openGL ES 2.0 (env. OpengL 3.2)

GLSL : OpenGL Shading Langage

  • float : variable réelle
  • vec* avec * dans {2,3,4} : vecteur de 2,3 ou 4 éléments réels
  • ivec* avec * dans {2,3,4} : vecteur de 2,3 ou 4 éléments entiers
  • mat*x* avec * dans {2,3,4} : matrice de 2,3 ou 4 lignes (ou colonnes) (abrv : mat* )
  • opérateur swizzle (mélange des tableaux) : var.xyzw, var.yzx, var.yyy
    • Soit vec4 var = vec4(1.0,-3.0,0.1,0.5);
    • var.y est un réel (la deuxieme valeur de var, ici -3.0)
    • var.zzx est un vec3 de valeur (0.1,0.1,1.0)

GLSL : OpenGL Shading Langage

  • Des boucles : while,for...
  • Des branchements : if... then... else
  • Définition de fonctions ou procédures
  • Opérations vectorielles : dot, cross, length, normalize
  • Trigonométrie : cos, sin, tan

Plus d'information sur la carte de réference rapide WebGL

Variables des shaders

  • Chaque variable (globale) correspond à une donnée.
  • Chaque donnée possède (en plus de son type) un qualificateur précisant sa nature
  • Des données qui traversent le pipeline
    • Des données qui entrent dans le pipeline (qualificateur attribute)
    • Des données qui transitent (entre les shaders) (qualificateur varying)
  • Des données qui ne traversent pas le pipeline mais servent à en contrôler ou modifier l'exécution (qualificateur uniform)

Variables entrantes

  • Doivent avoir le qualificateur attribute
  • Sont différentes pour chaque instance du shader
  • Sont différentes pour chaque sommet de la géométrie
  • Encodent une information par sommet (position,normale,couleur...)
  • Proviennent du CPU
  • Doivent être traitées (lues) par le shader
  • Sont en lecture seule
  • Exemple : La position des sommets dans l'espace 3D

Variables de transition

  • Doivent avoir le qualificateur varying
  • Sont différentes pour chaque instance du shader
  • Sont écrite par une étape du pipeline (le vertex shader)
  • Sont envoyées à l'étape suivante du pipeline (le fragment shader)
  • Servent a communiquer entre les étapes du pipeline
  • Sont interpolées lors de l'étape de rasterisation
  • Correspondance de nom et de type
  • La variable varying toto d'une étape se retrouve comme la variable varying toto de l'étape suivante
  • Variables de contrôle

    • Doivent avoir le qualificateur uniform
    • Sont identiques pour chaque instance du shader
    • Ne traversent pas le pipeline
    • Proviennent de l'application
    • Permettent la communication entre le shader (GPU) et l'application (CPU)
    • Contrôlent l'eécution du pipeline (ce sont des paramètres)
    • Exemple : matrices de transformations, position des lumières

    En pratique en Three.js et WebGL

    • Il existe des variables prédéfinies pour chaque étape
      • position contient la position d'un sommet dans l'espace 3D (c'est une variable attribute)
      • modelViewMatrix contient la matrice de transformation entre un objet et la caméra (c'est une variable uniform)
      • projectionMatrix contient la matrice de projection liée à la caméra (c'est une variable uniform)
      • gl_Position doit être rempli avec la position projetée des sommets (c'est une variable varying)
      • gl_FragColor doit être rempli avec la couleur du fragment
    • Plus d'info dans la carte de réference rapide WebGL

    Premiers Shaders

    Que faire avec ces shaders ?

    Si on ne fait pas la projection, on ne voit pas grand chose

    Commençons par simuler le pipeline fixe : le matériau MeshBasicMaterial

    Simuler le pipeline fixe de Three.js

    Vertex Program (rappel)

    Pipeline

    Simuler le pipeline fixe de Three.js

    Vertex Program

    • Doit (au minimum) calculer la projection des sommets sur le plan image
      • Réaliser la multiplication de la position des sommets (position) par les matrices de transformations et de projection
      • La variable modelViewMatrix contient la matrice des transformations
      • La variable projectionMatrix contient la matrice de la projection
      • gl_Position doit contenir la position projetée des sommets
    • 
             void main() {
        gl_Position = projectionMatrix *
                      modelViewMatrix *
                      vec4(position,1.0);
      }
              

    Simuler le pipeline fixe de Three.js

    Fragment Program (rappel)

    Pipeline

    Simuler le pipeline fixe de Three.js

    Fragment Program

    • Doit (au minimum) calculer la couleur des fragments
      • Remplir la variable gl_FragColor
      
             void main() {
        // définir la couleur sous la forme RGBA (0.0 - 1.0)
        gl_FragColor = vec4(0.0,0.5,0.5,1.0);
        }
              

    Résultat du premier shader

    Interactivité et communication avec le shaders

    Passage de paramètres

    • Pas encore tout a fait ca
    • La couleur finale est écrite en dur dans le shader
    • Comment passer des paramètres depuis WebGL aux shaders ?
    • Mécanisme de variables uniform

    Passage de paramètres

    • Déclarer (dans le script) une variable myUniforms contenant les variables uniformes
      
            var myUniforms = 
            {
                color :
        {
          type :'c', // une couleur
          value : new THREE.Color( 0xffffff )
        },
      }
              
    • Integrer notre variable dans le materiau
      
            var material = new THREE.ShaderMaterial( {
        uniforms : myUniforms,
          vertexShader: document.getElementById( 'vertexShader' ).textContent,
          fragmentShader: document.getElementById( 'fragmentShader' ).textContent
      } );
              
    • Modifier la variable depuis le javascript
      
              myUniforms.color.value = new THREE.Color(value);
      
                  

    Résultat du shader (avec interface)

    Opérations par sommet

    • Opérations réalisée dans le vertex shader
    • Modifier la géométrie par sommet
    • Exemple
      • Ajouter un attribut par sommet : amplitude de la déformation
        
                myAttributes = 
                {
                  amplitude: {
                    type: 'f', 
                    value: [] // un tableau vide
                  }
        
                
      • Remplir le tableau des attributs
      • 
                var verts =
                mesh.geometry.vertices; // Trouver le nombre de sommets
        
              var values =
                attributes.amplitude.value; // récupérer le pointeur sur le tableau
        
              for(var v = 0; v < verts.length; v++) { // remplir le tableau de valeurs aléatoires
                values.push(Math.random() * 0.01);
                      }
        
                  

    Opérations par sommet

    • Déplacer chaque sommet le long de sa normale
      
      
              attribute float amplitude;
      
      
              void main() {
              vec3 myPosition = position + normal*amplitude; 
              gl_Position = projectionMatrix *
              modelViewMatrix *
              vec4(myPosition,1.0);
      }
             
      
              
    • la variable normal est un attribut automatique donné par Three.js

    Résultat du shader

    Un peu d'animation

    • Déplacer chaque sommet le long de sa normale d'une amplitude variable en fonction du temps
      
      
              uniform float multiplicateur; // entre -1 et 1
      
              void main() {
              vec3 myPosition = position + normal*multiplicateur; 
              gl_Position = projectionMatrix *
              modelViewMatrix *
              vec4(myPosition,1.0);
      }
             
      
              
    • Faire varier multiplicateur en fonction du temps (fct animate)

    Résultat du shader

    Opérations par fragments

    • Opérations réalisée dans le fragment shader
    • Modifier la couleur par fragment
    • Exemple
      • Moduler la couleur en fonction de la position du fragment a l'écran
        
               void main() {
               gl_FragColor = vec4(gl_FragCoord.xy*0.001,0.5,1.0);
               }
        
                  
      • gl_FragCoord donne les coordonnées (en pixel) du fragment

    Résultat du shader

    Résultat du shader - Exemple ShaderToy

    Beaucoup d'opérations possibles

    • ShaderToy : uniquement un fragment shader
    • Beaucoup de fonctions (noise, utilisation de textures, calcul physiques...)
    • La semaine prochaine :
      • Eclairage de Phong, evaluation de BRDF
      • Utilisation de textures
      • Normal mapping