I. L'article original▲
Cet article est une adaptation en langue française de 2. OpenGL 4 Shaders, de Donald Urquhart.
II. Introduction▲
OpenGL 3 et suivants sont très orientés vers les shaders : on montre ici comment intégrer le code de shaders dans une application OpenGL. On ne donnera pas de détails au sujet du langage GLSL, le langage des shaders d'OpenGL, ce sujet est traité à part.
Ce tutoriel est un guide rapide sur la manière de faire fonctionner les shaders dans le framework actuel, le suivant traitera des matrices. On ne présentera que les shaders de type vertex et fragment, les geometric shaders seront présentés plus tard.
III. Code▲
Le code de ce tutoriel est assez minimal et se base sur le tutoriel GLSL. On commence par récupérer les fichiers shader.h et shader.cpp que l'on ajoute au projet Visual Studio.
#ifndef __SHADER_H
#define __SHADER_H
#if ( (defined(__MACH__)) && (defined(__APPLE__)) )
# include
<stdlib.h>
# include
<OpenGL/gl.h>
# include
<GLUT/glut.h>
# include
<OpenGL/glext.h>
#else
# include
<stdlib.h>
# include
<GL/glew.h>
# include
<GL/gl.h>
# include
<GL/glut.h>
# include
<GL/glext.h>
#endif
#include
<string>
class
Shader {
public
:
Shader();
Shader(const
char
*
vsFile, const
char
*
fsFile);
~
Shader();
void
init(const
char
*
vsFile, const
char
*
fsFile);
void
bind();
void
unbind();
unsigned
int
id();
private
:
unsigned
int
shader_id;
unsigned
int
shader_vp;
unsigned
int
shader_fp;
}
;
#endif
#include “shader.h”
#include
<string.h>
#include
<iostream>
#include
<stdlib.h>
using
namespace
std;
static
char
*
textFileRead(const
char
*
fileName) {
char
*
text;
if
(fileName !=
NULL
) {
FILE *
file =
fopen(fileName, &
#8220
;rt&
#8221
;);
if
(file !=
NULL
) {
fseek(file, 0
, SEEK_END);
int
count =
ftell(file);
rewind(file);
if
(count >
0
) {
text =
(char
*
)malloc(sizeof
(char
) *
(count +
1
));
count =
fread(text, sizeof
(char
), count, file);
text[count] =
&
#8216
;\0
';
}
fclose(file);
}
}
return
text;
}
Shader::
Shader() {
}
Shader::
Shader(const
char
*
vsFile, const
char
*
fsFile) {
init(vsFile, fsFile);
}
void
Shader::
init(const
char
*
vsFile, const
char
*
fsFile) {
shader_vp =
glCreateShader(GL_VERTEX_SHADER);
shader_fp =
glCreateShader(GL_FRAGMENT_SHADER);
const
char
*
vsText =
textFileRead(vsFile);
const
char
*
fsText =
textFileRead(fsFile);
if
(vsText ==
NULL
||
fsText ==
NULL
) {
cerr <<
&
#8220
;Either vertex shader or
fragment shader file not
found.&
#8220
; <<
endl;
return
;
}
glShaderSource(shader_vp, 1
, &
vsText, 0
);
glShaderSource(shader_fp, 1
, &
fsText, 0
);
glCompileShader(shader_vp);
glCompileShader(shader_fp);
shader_id =
glCreateProgram();
glAttachShader(shader_id, shader_fp);
glAttachShader(shader_id, shader_vp);
glLinkProgram(shader_id);
}
Shader::
~
Shader() {
glDetachShader(shader_id, shader_fp);
glDetachShader(shader_id, shader_vp);
glDeleteShader(shader_fp);
glDeleteShader(shader_vp);
glDeleteProgram(shader_id);
}
unsigned
int
Shader::
id() {
return
shader_id;
}
void
Shader::
bind() {
glUseProgram(shader_id);
}
void
Shader::
unbind() {
glUseProgram(0
);
}
void
main
(
) {
// Définit la position de l'arête courante.
gl_Position =
gl_ModelViewProjectionMatrix *
gl_Vertex;
}
void
main
(
) {
// Définit la couleur de sortie du pixel courant.
gl_FragColor =
vec4
(
1
.0
);
}
III-A. Opengl_3.h▲
Dans Opengl_3.h, qui contient la structure de la classe de contexte OpenGL, on s'assure de bien inclure la classe de shader :
#include
"shader.h"
Dans les variables privées du contexte, on ajoute une instance de variable de shader. Si on travaille avec plusieurs shaders, on peut utiliser un tableau ou une série de variables, avec une préférence pour le tableau.
class
OpenGLContext {
...
private
:
int
windowWidth; // Largeur de la fenêtre
int
windowHeight; // Hauteur de la fenêtre
Shader *
shader; // Shader GLSL
...
}
;
III-B. Opengl_3.cpp▲
Avec la variable de shader déclarée, on peut modifier la fonction setupScene pour la prendre en compte : on crée un objet de shader qui utilise shader.vert et shader.frag.
void
OpenGLContext::
setupScene(void
) {
glClearColor(0.4
f, 0.6
f, 0.9
f, 0.0
f); // // On définit la couleur à la valeur par défaut de XNA
shader =
new
Shader("shader.vert"
, "shader.frag"
); // On crée le shader en chargeant les shaders de type vertex et fragment.
}
Ensuite, tout ce qu'il reste à faire est, dans renderScene(), de lier et délier les shaders comme requis. Il n'y a pas encore de géométrie à afficher, on ne voit donc encore rien, mais cela va venir.
void
OpenGLContext::
renderScene(void
) {
glViewport(0
, 0
, windowWidth, windowHeight); // On définit la zone de dessin à la fenêtre
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT); // On vide les tampons.
shader->
bind(); // Lier le shader
shader->
unbind(); // Délier le shader.
SwapBuffers(hdc); // On échange les tampons pour afficher quelque chose.
}
III-C. Shader.cpp▲
Par rapport au fichier présenté en introduction et repris du tutoriel GLSL, on a dû effectuer un changement majeur : on lie deux variables au shader. Il s'agit des constantes in_Position et in_Color ; elles seront utilisées pour envoyer les positions et les couleurs au shader sans les lier et délier à chaque image. On a modifié comme ceci la fonction d'initialisation :
void
Shader::
init(const
char
*
vsFile, const
char
*
fsFile) {
...
glBindAttribLocation(shader_id, 0
, "in_Position"
); // Lier un attribut constant pour les positions des arêtes.
glBindAttribLocation(shader_id, 1
, "in_Color"
); // Lier un attribut constant pour la couleur.
...
}
III-D. Shader.vert▲
Voici un petit aperçu, supplémentaire au tutoriel GLSL, des changements apportés au shaders de type vertex et fragment dans GLSL 1.50.
Pour préciser la version employée, on utilise une ligne comme celle-ci (pour GLSL 1.50) :
#version
150
core
On peut ainsi utiliser toute version valide de GLSL. Dans la 1.50, on ne dispose plus de gl_ModelViewMatrix, gl_ProjectionMatrix, gl_Vertex et autres, on doit maintenant gérer et envoyer toutes ces informations soi-même aux shaders. Dans le prochain tutoriel, on présentera des modèles automanipulés, des matrices de vue et de projection, mais on se limitera ici aux bases.
Avec la création des deux variables in_Position et in_Color pour lier le shader, on doit les créer ici. Il s'agira de variables de type vec3 ; puisqu'on utilise GLSL 1.50, on doit également préciser s'il agit d'entrées in ou de sorties out du shader courant, il n'y a plus de variable uniforme ou variable à gérer.
On crée également une variable de sortie pass_Color, également un vec3, qui servira à passer une couleur de sortie du vertex shader au fragment shader. Heureusement, la définition de la position finale requiert un code similaire, la définition de la variable gl_Position.
#version
150
core
in vec3 in_Position;
in vec3 in_Color;
out vec3 pass_Color;
void
main
(
void
) {
gl_Position =
vec4
(
in_Position, 1
.0
);
pass_Color =
in_Color;?
}
III-E. Shader.frag▲
Dans le fragment shader, on déclare la version de GLSL utilisée et, puisqu'on a une variable de sortie du vertex shader, on doit créer une variable d'entrée de même nom et de même type dans le fragment shader.
À présent, les fragment shaders ne disposent plus de gl_FragColor : on doit maintenant déclarer manuellement une variable de sortie de type vec4, qui donnera la couleur au GPU. On l'appellera out_Color et on la définit à la valeur de pass_Color, la variable envoyée au shader.
#version
150
core
in vec3 pass_Color;
out vec4 out_Color;
void
main
(
void
) {
out_Color =
vec4
(
pass_Color, 1
.0
);
}
IV. Résultat▲
Si tout s'est bien passé, le projet devrait être prêt à utiliser des shaders et devrait se lancer sans erreur. Encore deux tutoriels : à la fin du quatrième, on affichera des choses à l'écran. OpenGL 3 et 4 requièrent plus de préparation, mais cela fait une grande différence à la fin, surtout quand il s'agit de vitesse : le rendu devrait être beaucoup plus efficace.
Les fichiers de projet Visual Studio 2010 de ce tutoriel sont disponibles au téléchargement.
Cliquez pour lire la vidéo
V. Remerciements▲
Merci à Alexandre Laurent pour son aide à la traduction et à Claude Leloup pour sa relecture orthographique !