Navigation▲
Tutoriel précédent : geometry shaders |
I. Feedback de transformation▲
Jusqu'à présent nous avons toujours envoyé les données des sommets à la carte graphique et, grâce à ceux-ci, juste dessiné des pixels dans le tampon de rendu. Comment pouvons-nous récupérer les sommets après qu'ils ont été traités par le vertex shader et le geometry shader ? Dans ce chapitre, nous allons voir une méthode pour faire cela, connue sous le nom de feedback de transformation (« transform feedback »).
Jusqu'à présent nous avons utilisé les VBO (Vertex Buffer Objects) pour stocker les sommets à utiliser dans les opérations de rendu. L'extension du feedback de transformation permet aux shaders d'écrire les sommets dans ces tampons. Vous pouvez par exemple construire un vertex shader qui simule la gravité et écrire les positions à jour des sommets dans le tampon. De cette façon, ces données n'ont pas à faire d'allers-retours entre la mémoire graphique et la mémoire principale. En plus, vous obtenez l'avantage des grandes capacités de traitement parallèle des GPU.
II. Retour basique▲
Nous allons commencer à partir de zéro afin que le programme final montre clairement combien il est simple d'utiliser le feedback de transformation. Malheureusement, il n'y a pas d'image de présentation cette fois, car nous n'allons rien dessiner dans ce chapitre ! Bien que cette fonctionnalité puisse être utilisée pour simplifier les effets telles les simulations de particules, l'explication est que cela dépasse le cadre de ces articles. Une fois que vous avez compris les bases du feedback de transformation, vous serez capable de trouver et comprendre de nombreux articles sur ces sujets.
Commençons par un simple vertex shader.
const
GLchar*
vertexShaderSrc =
GLSL
(
in
float
inValue;
out
float
outValue;
void
main
(
)
{
outValue =
sqrt
(
inValue);
}
);
Ce vertex shader ne semble pas logique. Il ne définit pas la variable gl_Position et il ne prend qu'un simple nombre flottant arbitraire comme entrée. Heureusement, nous allons utiliser le feedback de transformation pour capturer le résultat, ce que nous allons voir dans un moment.
GLuint shader =
glCreateShader
(
GL_VERTEX_SHADER);
glShaderSource
(
shader, 1
, &
vertexShaderSrc, nullptr);
glCompileShader
(
shader);
GLuint program =
glCreateProgram
(
);
glAttachShader
(
program, shader);
Compilez le shader, créez un programme et attachez le shader, mais n'appelez pas la fonction glLinkProgram (doc) ! Avant la liaison du programme, nous allons indiquer à OpenGL quels attributs de sortie nous souhaitons capturer dans un tampon.
const
GLchar*
feedbackVaryings[] =
{
"
outValue
"
}
;
glTransformFeedbackVaryings
(
program, 1
, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
Le premier paramètre est explicite, le deuxième et le troisième indiquent la longueur du tableau des noms en sortie et le tableau lui-même et le dernier paramètre indique comment la donnée doit être écrite.
Les deux formats suivants sont disponibles :
- GL_INTERLEAVED_ATTRIBS : écrit tous les attributs dans un seul objet tampon ;
- GL_SEPARATE_ATTRIBS : écrit les attributs dans plusieurs objets tampons ou à différents endroits dans un tampon.
Quelquefois, il est utile d'avoir des tampons séparés pour chaque attribut, mais gardons la démonstration simple. Maintenant que vous avez spécifié les variables de sortie, vous pouvez lier et activer le shader program. C'est ainsi, car le processus de liaison doit connaître les sorties.
glLinkProgram
(
program);
glUseProgram
(
program);
Après cela, créez et liez le VAO :
GLuint vao;
glGenVertexArrays
(
1
, &
vao);
glBindVertexArray
(
vao);
Maintenant, créez un tampon avec quelques données en entrée pour le vertex shader :
GLfloat data[] =
{
1
.0f
, 2
.0f
, 3
.0f
, 4
.0f
, 5
.0f
}
;
GLuint vbo;
glGenBuffers
(
1
, &
vbo);
glBindBuffer
(
GL_ARRAY_BUFFER, vbo);
glBufferData
(
GL_ARRAY_BUFFER, sizeof
(
data), data, GL_STATIC_DRAW);
Les nombres dans data sont les nombres pour lesquels nous souhaitons que le shader calcule la racine carrée et que le feedback de transformation nous aidera à récupérer.
Vous connaissez maintenant la manœuvre des pointeurs de sommets :
GLint inputAttrib =
glGetAttribLocation
(
program, "
inValue
"
);
glEnableVertexAttribArray
(
inputAttrib);
glVertexAttribPointer
(
inputAttrib, 1
, GL_FLOAT, GL_FALSE, 0
, 0
);
Le feedback de transformation va retourner les valeurs de outValue, mais premièrement nous devons créer un VBO pour les stocker, tout comme pour les sommets en entrée :
GLuint tbo;
glGenBuffers
(
1
, &
tbo);
glBindBuffer
(
GL_ARRAY_BUFFER, tbo);
glBufferData
(
GL_ARRAY_BUFFER, sizeof
(
data), nullptr, GL_STATIC_READ);
Notez que nous passons un nullptr pour créer un tampon assez grand pour contenir tous les résultats, mais sans spécifier une donnée initiale. Le type approprié est GL_STATIC_READ, qui indique que nous souhaitons que OpenGL écrive dans ce tampon et que notre application lise les données de celui-ci (voir la documentation pour l'utilisation des types).
Nous avons maintenant réalisé toutes les préparations pour le processus de
calcul. Comme nous ne souhaitons rien dessiner, le rasterizer doit être désactivé :glEnable
(
GL_RASTERIZER_DISCARD);
Pour lier, comme feedback de transformation, le tampon que nous avons créé ci-dessus nous devons utiliser une nouvelle fonction appelée glBindBufferBase (doc).
glBindBufferBase
(
GL_TRANSFORM_FEEDBACK_BUFFER, 0
, tbo);
Le premier paramètre est obligatoirement GL_TRANSFORM_FEEDBACK_BUFFER permettant ainsi les évolutions futures. Le deuxième paramètre est l'index de la variable de sortie, qui est simplement 0, car nous n'en avons qu'une. Le dernier paramètre spécifie l'objet tampon à lier.
Avant de faire l'appel à la fonction de dessin, vous devez entrer en mode feedback de transformation.
glBeginTransformFeedback
(
GL_POINTS);
Cela ramène sûrement des souvenirs des jours datant de la fonction glBegin (doc) ! Tout comme le geometry shader dans le précédent chapitre, les valeurs possibles pour le mode de primitive sont quelque peu limités :
- GL_POINTS — GL_POINTS
- GL_LINES — GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY
- GL_TRIANGLES — GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY
Si vous n'avez qu'un vertex shader, comme il en est maintenant, la primitive doit correspondre à celle qui va être affichée.
glDrawArrays
(
GL_POINTS, 0
, 5
);
Même si nous travaillons maintenant avec des données, les nombres simples peuvent être vus comme des « points », donc nous allons utiliser ce mode.
Fermez le mode de feedback de transformation :
glEndTransformFeedback
(
);
Normalement, à la fin de l'opération de dessin, nous aurions dû alterner les tampons pour afficher le résultat à l'écran. Nous souhaitons toujours nous assurer que l'opération soit bien finie avant d'essayer d'accéder aux résultats, donc nous forçons l'exécution du tampon de commandes d'OpenGL :
glFlush
(
);
La récupération des résultats est aussi simple que de copier les données d'un tableau :
GLfloat feedback[5
];
glGetBufferSubData
(
GL_TRANSFORM_FEEDBACK_BUFFER, 0
, sizeof
(
feedback), feedback);
Si vous affichez les valeurs du tableau, vous devriez voir les racines carrées des données en entrée :
printf
(
"
%f %f %f %f %f
\n
"
, feedback[0
], feedback[1
], feedback[2
], feedback[3
], feedback[4
]);
Félicitations, vous savez maintenant comment utiliser votre GPU pour effectuer des tâches généralistes avec les vertex shader. Bien sûr, un vrai framework de calcul sur GPU comme OpenCL est généralement une meilleure solution pour cela, mais l'avantage d'utiliser le feedback de transformation est que vous pouvez réutiliser les données dans des opérations de rendu, par exemple en lisant le tampon du feedback de transformation comme tampon pour effectuer une opération classique de dessin.
Si vous avez une carte graphique et un pilote qui le supporte, vous pouvez aussi utiliser les compute shaders dans OpenGL 4.3. Ils ont été conçus pour des tâches encore moins liées au dessin.
Vous pouvez trouver le code complet ici.
III. Feedback de transformation et geometry shaders▲
Lorsque vous incluez un geometry shader, les opérations de feedback de transformation vont capturer les sorties du geometry shader au lieu de celles du vertex shader. Par exemple :
// Vertex shader
const
GLchar*
vertexShaderSrc =
GLSL
(
in
float
inValue;
out
float
geoValue;
void
main
(
)
{
geoValue =
sqrt
(
inValue);
}
);
// Geometry shader
const
GLchar*
geoShaderSrc =
GLSL
(
layout
(
points) in
;
layout
(
triangle_strip, max_vertices =
3
) out
;
in
float
[] geoValue;
out
float
outValue;
void
main
(
)
{
for
(
int
i =
0
; i <
3
; i++
) {
outValue =
geoValue[0
] +
i;
EmitVertex
(
);
}
EndPrimitive
(
);
}
);
Le geometry shader prend un point fourni par le vertex shader et en génère deux autres pour former un triangle avec chaque point ayant une plus grande valeur.
GLuint geoShader =
glCreateShader
(
GL_GEOMETRY_SHADER);
glShaderSource
(
geoShader, 1
, &
geoShaderSrc, nullptr);
glCompileShader
(
geoShader);
...
glAttachShader
(
program, geoShader);
Compilez et attachez le geometry shader au programme pour commencer à l'utiliser.
const
GLchar*
feedbackVaryings[] =
{
"
outValue
"
}
;
glTransformFeedbackVaryings
(
program, 1
, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
Bien que la sortie provienne maintenant du geometry shader, nous n'avons pas changé le nom, donc le code est inchangé.
Comme chaque sommet en entrée va générer trois sommets en sortie, le tampon du feedback de transformation nécessite d'être trois fois plus grand que le tampon d'entrée :
glBufferData
(
GL_ARRAY_BUFFER, sizeof
(
data) *
3
, nullptr, GL_STATIC_READ);
Lorsque nous utilisons un geometry shader, la primitive spécifiée à la fonction glBeginTransformFeedback (doc) doit correspondre au type de sortie du geometry shader :
glBeginTransformFeedback
(
GL_TRIANGLES);
La récupération des résultats fonctionne comme avant :
// Récupère et affiche les résultats
GLfloat feedback[15
];
glGetBufferSubData
(
GL_TRANSFORM_FEEDBACK_BUFFER, 0
, sizeof
(
feedback), feedback);
for
(
int
i =
0
; i <
15
; i++
) {
printf
(
"
%f
\n
"
, feedback[i]);
}
Bien que vous deviez faire attention au type de la primitive et à la taille de vos tampons, l'ajout d'un geometry shader ne change pas grand-chose.
Le code complet peut être trouvé ici.
IV. Variable de feedback▲
Comme nous l'avons vu dans le chapitre précédent, les geometry shader ont la propriété unique de générer un nombre variable de données. Heureusement, il existe des méthodes pour connaître le nombre de primitives écrites grâce aux objets requêtes (« query objects »).
Tout comme pour tous les autres objets en OpenGL, vous devez d'abord le créer :
GLuint query;
glGenQueries
(
1
, &
query);
Ensuite, juste avant d'appeler glBeginTransformFeedback (doc), vous devez indiquer à OpenGL de garder la trace du nombre de primitives écrites :
glBeginQuery
(
GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query);
Après la fonction glEndTransformFeedback (doc), vous pouvez arrêter l'« enregistrement » :
glEndQuery
(
GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
La récupération des résultats s'effectue ainsi :
GLuint primitives;
glGetQueryObjectuiv
(
query, GL_QUERY_RESULT, &
primitives);
Vous pouvez alors afficher la valeur avec le reste des données :
printf
(
"
%u primitives written!
\n\n
"
, primitives);
Notez que cela indique le nombre de primitives, pas le nombre de sommets. Comme nous avons 15 sommets, 3 par triangle, nous avons 5 primitives.
Les objets de requêtes peuvent aussi être utilisés pour enregistrer les choses comme GL_PRIMITIVES_GENERATED lorsque n'avez que des geometry shader et GL_TIME_ELAPSED pour mesurer le temps occupé par le serveur (la carte graphique) pour effectuer son travail.
Regardez le code complet si vous êtes bloqué.
V. Conclusion▲
Vous connaissez maintenant assez à propos des geometry shader et du feedback de transformation pour faire faire des choses intéressantes à votre carte graphique, autres que du dessin ! Vous pouvez même combiner le feedback de transformation et le rendu pour mettre à jour les sommets et les dessiner en même temps !
VI. Exercices▲
- Essayez d'écrire un vertex shader qui simule la gravité afin de faire suivre les points autour du curseur de la souris grâce au feedback de transformation pour mettre à jour les sommets (Solution).
VII. Remerciements▲
Cet article est une traduction autorisée dont le texte original peut être trouvé sur open.gl.
Navigation▲
Tutoriel précédent : geometry shaders |