Pipeline Graphique OpenGL

Precedently, in Rendu3D

Méthode de rendu

  • Projection des sommets dans le plan image
  • Rasterisation : Discretisation du plan image et remplissage
  • Génère un ensemble de fragments
  • Calcul d'une couleur (éq. du rendu) pour chaque fragment
  • Elimination des parties cachées : Z Buffer

Precedently, in Rendu3D

Rappels mathématiques

  • Coordonnées homogènes (vecteurs à 4 composantes en 3D)
  • Une matrice de transformation (4x4) pour passer d'un repère 3D dans un autre
  • Multiplication de matrice non-commutative !!
  • $X' = M \times X$
  • La projection est (aussi) exprimée par une matrice de transformation

Aujourd'hui

  • Pipeline graphique
  • Des exemples

OpenGL
  • Des détails pratiques sur OpenGL
  • Ex : glClear(...) : Nettoyage du Frame buffer
GobLim
  • Moteur 3d developpé au sein de l'équipe SIR
  • Basé sur openGL 4.5
  • A utiliser en TP

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 4.4

Pipeline

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

Programmation du pipeline graphique

Shader, OpenGL et GLSL

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

OpenGL : Open Graphics Library

  • Librairie graphique multi-plateforme bas niveau
  • Communication avec le GPU
  • Machine à états (pas de modification des états au cours de l'exécution du pipeline)
  • De nombreuses évolutions majeures très récentes (version 4.6 : Aout 2017)

GLSL : OpenGL Shading Langage

  • Programmation du pipeline graphique
  • Langage de haut niveau basé sur du C
  • Des évolutions conjointes à OpenGL

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 OpenGL

Shaders en GLSL

  • Ecrit en GLSL (ou HLSL, CG...)
  • Peuvent être compilés au vol
  • Doivent être liés (activés) avant le rendu
  • Pour plus de facilité : Dans un fichier à part
  • Un code identique pour tous les éléments
  • Vertex Shader : Pour chaque sommet entrant
  • Fragment Shader : Pour chaque fragment entrant

Shaders en pratique

Program (Opengl > 4.0)
  • Un entier symbolisant un espace mémoire GPU
  • Contient une chaine de caractère (code du shader)
  • A un type : GL_VERTEX_SHADER,...
  • Doit être compilé, et lié (chargé en mémoire)
  • Doit être activé à chaque utilisation du pipeline
  • glUseProgram(GLint program)

Shaders en pratique

PipelineProgram (OpenGL > 4.2)
  • Symbolise l'ensemble du pipeline graphique
  • Permet de définir pour chaque stage un Program
  • S'occupe de la liaison entre shaders
  • Permet des changements de Program au vol

Shaders en pratique

GLProgram
  • Est une instance d'un shader GLSL
  • Chargé en mémoire graphique
  • Une étape du pipeline graphique
  • Possède son propre espace mémoire et un manager de variables

Shaders en pratique

GLPipeline
  • Symbolise l'ensemble du pipeline graphique
  • Pointe sur un ensemble de GLProgram
  • S'occupe de la liaison entre shaders
  • Permet des changements de GLProgram au vol

Mécanisme de communication

Comment échanger des informations ?

  • Entre les étapes du pipeline
  • Entre l'application CPU et le shader GPU
  • Des informations de natures diverses
  • Un qualificateur pour les variables

Mécanisme de communication

Communication entre shaders : Le qualificateur in

  • Pour les élements traversant le pipeline (flux de données)
  • Une entrée du programme
  • En lecture uniquement
  • Ex : position, couleur d'un sommet
  • Ex : couleur interpolée, profondeur d'entrée d'un fragment

Mécanisme de communication

Communication entre shaders : Le qualificateur out

  • Pour les élements traversant le pipeline (flux de données)
  • Une sortie du programme
  • En écriture uniquement
  • Sera transmise à l'étape suivante du pipeline
  • Ex : position projetée d'un sommet
  • Ex : couleur,profondeur finale d'un fragment

Mécanisme de communication

Communication CPU-shaders : Le qualificateur uniform

  • Variable identique pour tous les éléments
  • Transmise au shader par l'application
  • Un espace mémoire GPU
  • Ne varie pas au cours du pipeline
  • Ex : matrice de projection

Mécanisme de communication

Gestion des variables uniform

Mode immédiat
  • Fonction glUniform**(...)
  • Ex : glUniform3f(int locGPU,float a,float b,float c)
  • glGetUniformLocation(int shader, string nom)
    • Adresse mémoire GPU de la variable
  • Le shader doit être activé
  • A transmettre pour chaque frame ("oubli" des valeurs)
  • Un appel par changement de valeur

Mécanisme de communication

Gestion des variables uniform

Mode Direct State Access (>3.2)
  • Bloc mémoire persistantréservé (Uniform Buffer Object)
  • Un seul appel OpenGL pour changer TOUTle UBO
  • N'importe ou dans le code !

Mécanisme de communication

Gestion des variables uniform

Le manager de variables
  • Un parser automatique de variables uniform
  • Un manager pour chaque GLProgram
  • Un ensemble de GPUVariable : pointeurs sur la mémoire GPU (buffer)
  • Erreurs affichées sur la console

Mécanisme de communication

Gestion des variables uniform

Des GPUVariables
  • Des templates : GPUfloat, GPUvec3, GPUmat4...
  • Contient un pointeur sur le buffer GPU correspondant
  • Une fonction GPU* getGPU*(string nomGPU) pour l'initialiser
    • Ex : GPUfloat* mon_pointeur = getGPUfloat("ma_variable_du_shader")
  • Une fonction void Set(* v) pour écrire dans le buffer GPU
    • Ex : mon_pointeur->Set(42)
  • Contient une copie des données sur le CPU aussi

A retenir

  • Qualificateur uniform
    • Variable globale
    • Vient du CPU
    • Identique pour tous les éléments
  • Qualificateur in
    • Entrée du shader (RO)
  • Qualificateur out
    • Sortie du shader (WO)

Charger les objets sur le GPU

Vertex Buffer Object- Etape 1

  • A partir d'un tableau (positions, couleur, normales...) sur CPU
  • Le charger sur la mémoire GPU
  • Attention : OpenGL > 4.5 !!!
  • GLuint monVBO;
  • Générer un tableau vide : glCreateBuffers(1,&monVBO);
  • Remplir le tableau : glNamedBufferData(monVBO,sizeof(montableauCPU),
    &montableauCPU,GL_STATIC_DRAW)

Charger les objets sur le GPU

Vertex Arrays Object- Etape 2

  • On a plusieurs tableaux !
  • A chaque image : Activer chaque tableau
  • Chaque appel est couteux
  • Encapsuler l'ensemble des tableaux
  • Dans un objet qui sotcke tout (attributs, tableaux ...)
  • GLuint monVAO;
  • Générer un ensemble vide : glCreateVertexArrays(1,&monVAO);

Charger les objets sur le GPU

Le lien tableau-variable in - Etape 3

  • Comment lier chaque tableau à une variable indu vertex shader ?
  • Activer l'indice choisi sur le CPU
  • Spécifier le format de l'attribut
  • Indiquer le VBO correspondant
  • Indiquer un éventuel décalage d'indice
  • Lier l'indiceCPU et l'indice du shader
  • glEnableVertexArrayAttrib(monVAO,index);
  • glVertexArrayAttribFormat(monVAO,index,3,GL_FLOAT,GL_FALSE,0);
  • glVertexArrayVertexBuffer(monVAO,index,monVBO,0,sizeof(float) * 3);
  • glVertexArrayBindingDivisor(monVAO,0,0);
  • glVertexArrayAttribBinding(vao, indexDansShader, index);

Charger les objets sur le GPU

Le lien tableau-variable in - Etape 3.5

  • Problème : il faut connaitre l'indice pour chaque shader au chargement...
  • Solution : une convention sur les indices (0 : Position, 1:couleur,
    2 : normale,...)
  • Spécifier directement l'indice dans le shader
  • layout (location = 0) in vec3 Position;

Envoyer les données dans le pipeline

  • Beaucoup de méthodes
  • Après avoir lié le VAO glBindVertexArray(monVAO);
  • La plus simple : glDrawArrays(GL_TRIANGLES,0,nbElements);
  • Au moins simple : glDrawElementsInstancedBaseInstance(....)
  • la documentation openGL est votre amie
  • Lors du prochain TP !

Quelques exemples

Un modèle d'entrée

BunnyDef

Quelques exemples

  • Un programme basique
  • Équivalent du pipeline fixe (pré-OpenGL 3.0)
  • Objectif :
    • Réaliser la projection de la géométrie sur le plan image
    • Assigner une couleur unique à chaque sommet
    • Assigner à chaque fragment la couleur interpolée de chaque sommet
  • Données du pipeline
    • Un tableau de positions dans l'espace 3D (exprimées en fonction d'un repère local)
    • Une matrice (transformation et projection) pour transformer depuis l'espace local jusqu'à l'espace 2D du plan image

Du coté CPU

  • Récuperer l'adresse de la variable sur la mémoire graphique
    Lors de l'initialisation
      Gluint mvpLoc = glGetUniformLocation(program,"MVP");
      
  • A chaque frame :
  • Calculer et transmettre la matrice de transformation glUniformMatrix4fv(mvpLoc,1,FL_FALSE,&myCPUMatrix);
  • Activer le pipeline
  • Envoyer la géométrie dans le pipeline
  • Désactiver le pipeline

Du coté CPU

  • Récuperer la GPUVariable pointant sur la mémoire graphique
    Lors de l'initialisation
    modelViewProj = vp->uniforms()->getGPUmat4("MVP");
    
  • Calculer et transmettre la matrice de transformation
  • Activer le pipeline
  • Envoyer la géométrie dans le pipeline
  • Désactiver le pipeline
    A chaque frame, pour un objet o
    mat4 M = o->frame()->getTransformMatrix();
    modelViewProj->Set(M);
    m_ProgramPipeline->bind();
    o->drawGeometry(GL_TRIANGLES);
    m_ProgramPipeline->release();
    

Vertex Program

Pipeline1
  • Variables venant du CPU
    • uniform : Une matrice de transformation MVP
  • Données d'entrée pour chaque occurence
    • in : La position 3D d'UN sommet
  • Données de sortie pour chaque occurence
    • out : La position 2D dans le plan image : gl_Position (obligatoire)
    • out : Une couleur

Vertex Program

ShaderVP1

Vertex Program


#version 420	
layout(std140) uniform CPU
{
	mat4 MVP;
};
layout (location = 0) in vec3 Position;
out gl_PerVertex {
        vec4 gl_Position;
		};		
out vec3 v_Color;
void main()
{
  gl_Position = MVP * vec4(Position,1.0);  
  v_Color = vec3(0.2,0.3,0.7);
}

Fragment Program

Pipeline4
  • Variables venant du CPU
    • Aucune
  • Données d'entrée pour chaque occurence
    • in : La couleur interpolée d'UN fragment
  • Données de sortie pour chaque occurence
    • out : Une couleur (finale) pour chaque fragment

Fragment Program

ShaderFP

Fragment Program


#version 420 

in vec3 v_Color;

out vec4 Color;

void main()
{
	Color = vec4(v_Color,1.0);
}

Résultat

BunnyColor

Un autre exemple

  • Et si on défini la couleur dans l'application ?
  • Objectif :
    • Réaliser la projection de la géométrie sur le plan image
    • Assigner à chaque fragment une couleur définie par l'application
  • Données du pipeline
    • Un tableau de positions dans l'espace 3D (exprimées en fonction d'un repère local)
    • Une matrice (transformation et projection) pour transformer depuis l'espace local jusqu'à l'espace 2D du plan image
    • Une couleur définie par l'application

Du coté CPU

  • Récuperer la GPUVariable pointant sur la mémoire graphique
    Lors de chaque frame
    	glUniform3fv(glGetUniformLocation(fp->getProgram(),"CPUColor"),vec3(0.5,0.4,0.6)));
    	
    Lors de l'initialisation
    	GPUvec3* couleur = fp->uniforms()->getGPUvec3("CPUColor");
    	
  • Calculer et transmettre la valeur
    Quelque part dans le code
    		 couleur->Set(vec3(0.5,0.4,0.6);
    		

Fragment Program

Pipeline4
  • Variables venant du CPU
    • uniform : Une couleur
  • Données d'entrée pour chaque occurence
    • aucune
  • Données de sortie pour chaque occurence
    • out : Une couleur (finale) pour chaque fragment

Fragment Program


#version 420 

layout(std140) uniform CPU
{
	vec3 CPUColor;
};

out vec4 Color;

void main()
{
	Color = vec4(CPUColor,1.0);
}

Résultat

BunnyColor

Un troisième exemple

  • Visualisons l'information de normale
  • Objectif :
    • Réaliser la projection de la géométrie sur le plan image
    • Assigner à chaque sommet une couleur (la valeur de la normale)
  • Données du pipeline
    • Un tableau de positions dans l'espace 3D (exprimées en fonction d'un repère local)
    • Un tableau d'attributs : la normale
    • Une matrice (transformation et projection) pour transformer depuis l'espace local jusqu'à l'espace 2D du plan image

Vertex Program

Pipeline1
  • Variables venant du CPU
    • uniform : Une matrice de transformation MVP
  • Données d'entrée pour chaque occurence
    • in : La position 3D d'UN sommet
    • in : La normale d'UN sommet
  • Données de sortie pour chaque occurence
    • out : La position 2D dans le plan image
    • out : Une couleur

Vertex Program

ShaderVP2

Vertex Program


#version 420	
layout(std140) uniform CPU
{
	mat4 MVP;
};
layout (location = 0) in vec3 Position;
layout (location = 2) in vec3 Normal;
out gl_PerVertex {
        vec4 gl_Position;};
out vec3 v_Color;

void main()
{
  gl_Position = MVP * vec4(Position,1.0);  
  v_Color = abs(Normal);
}

Résultat

BunnyN

Eclairage de Phong

BunnyColorPhong

Eclairage de Phong + couleur par sommet

BunnySColorPhong

Effet cartoon

BunnyToon

Reflexions de l'environnement

BunnyReflectHigh

Couleur procédurale + Ambiant Occlusion + Phong

BunnyHQR

Ou effectuer les calculs ?

le cas du vertex program

  • une opération de coût X pour chaque sommet
  • Un modèle 3D en temps réel : environ (10 - 100 000) sommets
  • Information pour chaque sommet
  • Interpolation linéaire du résultat
  • Dépend de la géométrie
  • Dépend du calcul (interpolation linéaire)

Ou effectuer les calculs ?

le cas du fragment program

  • une opération de coût X pour chaque fragment
  • Dépend de la couverture de l'écran par l'objet
  • Dépend des recouvrements (Z-buffer) ... et donc de l'objet aussi
  • ar exemple (pour un plan face à la caméra) : 1024*1024 :environ 1 million
  • Pas besoin d'interpolation

Illumination diffuse

Par sommet

BunnySoft
Environ 13k évaluations

Illumination diffuse

Par fragment

BunnySoft
Environ 1M évaluations

Reflexions de l'environnement

Par sommet

BunnyReflectLow
Environ 13K évaluations

Reflexions de l'environnement

Par fragment

BunnyReflectHigh
Environ 1M évaluations

Des questions ?