gKit2 light
afficher plusieurs triangles, modifier les paramètres uniform d'un shader program

cf tuto3GL.cpp

pour pouvoir dessiner quelque chose, il faut commencer par configurer le pipeline openGL, repassez dans introduction api 3d, openGL et pipeline graphique si nécessaire.

la configuration minimale est :

glClearColor() et glClearDepthf() définissent les valeurs par défaut utilisées pour "éffacer" l'image et le zbuffer, avec glClear( ). la taille de l'image est fournie par glViewport( ). Les images associées à la fenêtre s'appellent GL_BACK et GL_FRONT, c'est GL_FRONT qui est affichée dans la fenêtre, on dessine donc dans GL_BACK. pour une application simple, il suffit de fixer les valeurs une seule fois dans init( ) :

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
glViewport(0, 0, window_width(), window_height());
glClearColor(0.2f, 0.2f, 0.2f);
glClearDepthf(1.0f);
int window_height()
renvoie la hauteur de la fenetre de l'application.
Definition: window.cpp:29
int window_width()
renvoie la largeur de la fenetre de l'application.
Definition: window.cpp:25

remarque : si la fenetre de l'application change de dimension, il ne faut pas oublier de modifier glViewport(). si vous utilisez run( ) de window.h, c'est fait automatiquement.

puis effacer l'image et le zbuffer au début de draw( ):

glClear(GL_COLOR_BUFFER_BIT); // effacer l'image
glClear(GL_DEPTH_BUFFER_BIT); // effacer le zbuffer, si nécessaire

on peut combiner les deux, avec un OU binaire :

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // effacer l'image et le zbuffer

ztest et zbuffer

pour obtenir une visibilité correcte lorsque plusieurs triangles se dessinent sur le même pixel, il faut indiquer lequel garder, celui dont la profondeur est la plus petite, ou la plus grande, ou égale, etc.

glDepthFunc(GL_LESS); // conserver le triangle le plus proche,
glDepthFunc(GL_GREATER); // conserver le triangle le plus loin,
glDepthFunc(GL_ALWAYS); // conserver le dernier triangle dessine

remarque : pour conserver le triangle le plus loin, avec glDepthFunc(GL_GREATER), il faut initialiser le zbuffer correctement : avec la profondeur la plus petite, c'est à dire 0, dans le repère image, cf introduction api 3d, openGL et pipeline graphique.

glDepthFunc() permet de choisir le test, mais il faut aussi l'activer (ou le désactiver) :

glEnable(GL_DEPTH_TEST); // activer le ztest
glDisable(GL_DEPTH_TEST); // desactiver le ztest

élimination des faces arrières / back face culling

lorsque les triangles décrivent la surface d'un objet opaque, les triangles à l'arrière de l'objet ne sont pas visibles, il est assez simple de les détecter, cf introduction api 3d, openGL et pipeline graphique, par contre, il faut donner l'orientation normale des faces, sens trigo ou horaire, et choisir lesquelles on veut supprimer (avant ou arrière) et enfin, activer le test :

glFrontFace(GL_CCW); // les faces visibles / avant sont dans le sens trigo / counter clockwise
glFrontFace(GL_CW); // les faces visibles / avant sont dans le sens horaire / clockwise
glCullFace(GL_BACK); // eliminer les faces arrieres
glCullFace(GL_FRONT); // ou les faces avant ?
glEnable(GL_CULL_FACE); // activer le test
glDisable(GL_CULL_FACE); // desactiver le test

le test standard s'écrit :

glFrontFace(GL_CCW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

attributs de sommets, vertex array object, vao

pour dessiner des triangles, il faut décrire les informations associées aux sommets, indiquer ou les trouver, leur organisation mémoire, et indiquer à quelles entrées du vertex shader elles sont associées.

le cas général est présenté dans configurer un format de sommet, vertex array object, pour l'instant, la solution la plus simple est d'utiliser un tableau uniform déclaré par le vertex shader, sans description de format de sommet. c'est un objet openGL, appelé vertex array object qui stocke la description du format de sommets. il suffit donc de créer un vertex array object vide / par défaut.

la création des objets openGL utilise des fonctions de la forme glGenXXX( int n, GLuint *names ). cette famille de fonctions permet de créer plusieurs objets en même temps et renvoye un tableau d'identifiants des nouveaux objets. pour en créer un seul, on peut utiliser :

GLuint vao;
glGenVertexArrays(1, &vao);

il ne reste plus qu'à le sélectionner pour configurer le pipeline :

glBindVertexArray(vao);

uniforms et shader program

après avoir compilé et linké un shader program, cf compiler et linker un shader program, il faut le sélectionner pour configurer le pipeline :

GLuint program= ... ;
glUseProgram(program);

avant de pouvoir dessiner, il faut affecter une valeur à tous les uniforms utilisés par le shader program. l'affectation se fait en 2 étapes :

attention : ne pas oublier que glUniform() affecte une valeur à un uniform du program actuellement sélectionné par glUseProgram()...

tuto3GL.glsl déclare 3 uniforms :

la démarche est identique dans les 3 cas, même pour le tableau. par contre, il faut utiliser la bonne version de glUniform() à chaque fois.

pour time, 1 float, il faut utiliser glUniform1f() pour l'affectation (cf interface C openGL pour les conventions de nommage) :

GLint location= glGetUniformLocation(program, "time");
glUniform1f(location, 12);

pour color, 4 float, il faut utiliser... glUniform4f() :

GLint location= glGetUniformLocation(program, "color");
glUniform4f(location, 1, 1, 1, 1);

pour le tableau positions, les éléments du tableau sont des vec3. pour un seul vec3, on utiliserait glUniform3f( ), pour un tableau, les valeurs sont passées par pointeur, et c'est la variante glUniform3fv(location, count, data) qu'il faut utiliser. count indique le nombre d'élements vec3 à transférer.

#include "vec.h"
vec3 data[36]= { ... };
GLint location= glGetUniformLocation(program, "positions");
glUniform3fv(location, /* count */ 36, data);
vecteur generique, utilitaire.
Definition: vec.h:146

remarque : on peut utiliser les variantes pointeurs pour une valeur unique, il suffit de donner 1 comme nombre d'éléments à affecter. par exemple :

float data= 12;
GLint location= glGetUniformLocation(program, "time");
glUniform1fv(location, /* count */ 1, &data);

ou encore

#include "color.h"
Color data= Color(1, 1, 1);
GLint location= glGetUniformLocation(program, "color");
glUniform4fv(location, /* count */ 1, &data);
representation d'une couleur (rgba) transparente ou opaque.
Definition: color.h:14

pour un uniform, ou un tableau, de type matrice 4x4, comme les Transform, par exemple, il faut utiliser glUniformMatrix4fv(). Transform represente les matrices par 16 floats, organises par ligne, mais openGL utilise l'autre organisation, par colonne, il faut donc transposer les matrices avant de les affecter à un uniform, c'est le role du parametre transpose de glUniformMatrix().

GLint location= glGetUniformLocation(program, "matrix"); // cf declaration dans un shader : uniform mat4 matrix;
glUniformMatrix4fv(location, /* count */ 1, /* transpose */ GL_TRUE, t.data());
representation d'une transformation, une matrice 4x4, organisee par ligne / row major.
Definition: mat.h:21
const float * data() const
renvoie l'adresse de la premiere valeur de la matrice.
Definition: mat.h:75

uniforms.h définit plusieurs surcharges de la famille glUniform() pour les types les plus courants.

#include "uniforms.h"
program_uniform(program, "time", float(12));
program_uniform(program, "color", Color(1, 1, 1));
program_uniform(program, "matrix", Transform());
void program_uniform(const GLuint program, const char *uniform, const std::vector< unsigned > &v)
affecte un tableau de valeurs a un uniform du shader program.
Definition: uniforms.cpp:94

attention : vérifiez que la bonne surchage soit utilisee, si l'uniform est un scalaire : int, uint ou float, n'hesitez pas utiliser les notations litterales ou les constructeurs explicites :

glDraw( )

on peut enfin dessiner les 12 triangles, c'est à dire les indices de 0 à 36.

glDrawArrays(GL_TRIANGLES, 0, 36);

l'exemple complet est dans tuto3GL.cpp

résumé : configuration du pipeline

il faut fixer les paramètres, pour les applications simples, une seule fois dans init( ) :

// choisir dans quelle image dessiner, celle qui n'est pas affichée, GL_BACK
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
// definir la taille de l'image a dessiner,
glViewport(0, 0, width, height);
// fait par run( ) de window.h
glClearColor(0.2, 0.2, 0.2, 1); // definir la couleur par defaut
glClearDepthf(1.f); // profondeur par defaut
glDepthFunc(GL_LESS); // ztest, conserver l'intersection la plus proche de la camera
glEnable(GL_DEPTH_TEST); // activer le ztest
glFrontFace(GL_CCW); // description des faces dans le sens trigo
glCullFace(GL_BACK); // eliminer les faces arrieres
glEnable(GL_CULL_FACE); // activer le test

et pour dessiner, dans draw( ), il faut au minimum :

glBindVertexArray(vao); // un vertex array object
glUseProgram(program); // un shader program
glUniform(...); // donner une valeur a tous les uniforms du program
glDrawArrays(...);

éventuellement, on peut vérifier que tous les uniforms utilisés par le program ont bien une valeur ou vérifier que les attributs déclarés par le vertex shader sont bien paramétrés, cf récupérer les uniforms et les attributs utilisés par un shader program.