IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Les shaders dans OpenGL

Introduction à la programmation de shaders GLSL

Les shaders dans OpenGL

Introduction la programmation de shaders GLSL


prcdentsommairesuivant

2. Mise en place des shaders dans un programme

2-A. Utilisation des shaders (Documentation OpenGL 2.1)

Comme pour tout lment qui s'intgre dans OpenGL (textures, buffers…) il faut crer des objets OpenGL pour accueillir l'lment voulu (n'oubliez pas que, pour un programme shader, nous avons deux sources : une pour le vertex shader; une pour le fragment shader, donc nous avons besoin de deux objets).

  • GLuint glCreateShader (GLenum shaderType)

    shaderType : doit contenir GL_VERTEX_SHADER pour crer un vertex shader.
    retourne l'identifiant du shader.
  • GLuint glCreateShader (GLenum shaderType)

    shaderType : doit contenir GL_FRAGMENT_SHADER pour crer un fragment shader.
    retourne l'identifiant du shader.


Lecture des fichiers, le contenu est pass (encore une fois, il faut faire deux fois l'appel la commande) :

  • void glShaderSource (GLuint shader, GLsizei count, const GLchar ** string, const GLint * length)

    shader : contient l'identifiant du shader, retourn par glCreateShader().
    Count : indique le nombre d'lments dans string et length.
    string : est un pointeur sur le code source de notre ou nos shaders.
    length : indique la taille de notre ou nos shaders.


Comme tout programme, nous compilons le code (deux appels ncessaires, encore) :

  • void glCompileShader (GLuint shader)

    shader : contient l'identifiant de notre shader.


La vrification des erreurs est trs importante, sans quoi, vous ne savez pas si votre compilation fonctionne ou pas :

  • void glGetShader (GLuint shader, GLenum pname, GLint * params)

    shader : contient l'identifiant de notre shader.
    pname : contient un identifiant dfinissant la valeur qui va tre retourne. Ici nous utilisons GL_COMPILE_STATUS.
    params : contiendra la valeur retourne par OpenGL.


Et pour connatre l'erreur (si elle est prsente) :

  • void glGetShaderInfoLog (GLuint shader, GLsizei maxLength, GLsizei * length, GLchar * infoLog)

    shader : contient l'identifiant de notre shader.
    maxLength : contient la taille que vous avez alloue pour recevoir le message d'erreur.
    length : contiendra la taille du message retourn.
    infoLog : la chane de caractres qui contiendra le message d'erreur.


Maintenant que nos deux (vertex et fragment) shaders sont compils il faut crer le programme. Encore une fois, il nous faut un objet OpenGL et son identifiant :


Une fois l'ID cr, vous devez dire OpenGL quel shader vous voulez mettre dans le programme (opration de liage ('linking' en anglais)) :

  • void glAttachShader (GLuint program, GLuint shader)

    program : contient l'identifiant du Program Shader auquel nous voulons attacher le shader.
    shader : contient l'identifiant du shader que nous voulons attacher.


Nos deux shaders lis au programme vont tre mis ensembles pour former le programme. Cette dernire tape se fait avec la commande :

  • void glLinkProgram(GLuint program)

    program : contient l'identifiant du Program shader que nous voulons lier.


Enfin, le shader devrait tre prt tre utilis. Mais je conseille vivement de vrifier les erreurs avec :

  • void glGetProgram (GLuint program, GLenum pname, GLint * params)

    program : contient l'identifiant du Program Shader.
    pname : contient un enum dterminant ce que nous voulons en retour. Ici nous utilisons GL_LINK_STATUS.
    params : contiendra la valeur retourn par OpenGL.


Si une erreur est rapporte, vous pouvez en connatre la cause avec :

  • void glGetProgramInfoLog (GLuint program, GLsizei maxLength, GLsizei * length, GLchar * infoLog)

    program : contient l'identifiant du Program Shader.
    maxLength : contient la taille que vous avez alloue pour recevoir le message d'erreur.
    length : contiendra la taille du message retourn.
    infoLog : la chane de caractres qui contiendra le message d'erreur.


Maintenant, vous pouvez utiliser le shader avec :

  • void glUseProgram (GLuint program)

    program : contient l'identifiant du Program Shader.

Si vous ne voulez plus utiliser de shader, vous pouvez appeler cette commande en passant 0.

Une fois que vous avez fini d'utiliser avec vos shaders, vous allez devoir les dtruire.

Premire chose faire, nous arrtons de les utiliser :

  • void glUseProgram (GLuint program)

    program : contient l'identifiant du Program Shader. Ici nous mettons 0 pour ne plus utiliser de shader.


Aprs, nous dtachons les shaders du programme :

  • void glDetachShader (GLuint program, GLuint shader)

    program : contient l'identifiant du Program Shader.
    shader : contient l'identifiant du shader dtacher du programme.


Puis nous dtruisons le programme :

  • void glDeleteProgram (GLuint program)

    program : contient l'identifiant du Program Shader.


Puis les deux shaders :

  • void glDeleteShader (GLuint shader)

    shader : contient l'identifiant du shader dtruire.



Sachez que :

  • si des shaders sont encore lis un programme, l'appel glDeleteShader() va mettre les shaders en attente pour suppression et ils seront supprims lorsqu'ils seront dtachs (glDetachShader()) ;
  • si le programme est encore utilis, l'appel glDeleteProgram() va mettre le programme en attente pour suppression, et il sera supprim une fois inutilis (glUseShader(0)) ;
  • le fait de supprimer un programme dtache automatiquement les shaders qui lui sont attachs.


En prenant en considration tout ce qui vient d'tre dit, il est conseill de :

  • supprimer les shaders ds que vous les avez attachs (si vous ne voulez pas les attacher une autre fois, bien sur) ;
  • supprimer le programme une fois qu'il n'y en a plus besoin. La suppression du programme dtachera les shaders, ce qui fera qu'ils seront supprims tout de suite avec le programme.

2-B. Lecture d'un fichier texte pour l'intgrer comme shaders

Comme nous avons pu le voir, la commande glShaderSource() accepte un tableau de chaines de caractres et non le fichier directement. Ceci est fait pour que le code d'OpenGL soit le plus portable possible (par exemple nous pouvons mettre directement notre code source dans une variable dans le code de notre programme). Ici, nous allons voir comment nous pouvons lire un fichier pour OpenGL.

La premire chose faire sur notre fichier est de rcuprer la taille du fichier. Une mthode consiste mettre le pointeur du fichier directement sur la fin est rcuprer sa position :

Rcuprer la taille d'un fichier
Sélectionnez
long getFileSize(FILE* pFile)
{
    long length = 0;
 
    fseek(pFile,0,SEEK_END);
 
    length = ftell(pFile);
 
    // Ne pas oublier de mettre le fichier  son dbut, sinon on ne peut pas le lire
    fseek(pFile,0,SEEK_SET);
 
    return length;
}

Ensuite, il faut rcuprer le contenu du fichier en lui mme :

Rcuprer les donnes du fichier
Sélectionnez
char* fileContent = (char*)malloc(fileLength+1); // +1 pour terminer la chaine proprement
if ( fileContent == NULL )
{
    return -3;
}
 
fread(fileContent,fileLength,1,pFile);
// Termine le tableau qui contient le shader
    fileContent[fileSize] = '\0';
// Utilisation avec glShaderSource()
 
free(fileContent);

Vous pouvez voir que l'on rutilise la taille du fichier trouv plus tt.

Vous pouvez trouver le code que j'utilise pour charger des shaders, ici. Ci-dessous, nous allons voir les sections les plus importantes de ce code. Afin de simplifier le code, je n'ai pas fait une vrification minutieuse des erreurs.

Fonction de vrification de la compilation.
Sélectionnez
// Pour plus de simplicit, j'ajoute une fonction qui vrifie la compilation des shaders
char checkShaderCompilation(GLuint shaderID)
{
    GLint compilationStatus = 0;
 
    // Vrification de la compilation pour le vertex shader
    glGetShaderiv(vertexID, GL_COMPILE_STATUS, &compilationStatus);
    if ( compilationStatus != GL_TRUE )
    {
        // Nous savons que la compilation a chou, donc nous devons savoir pourquoi
        // Nous devons rcupr la taille du log
        GLint logLength = 0;
        GLchar* log = NULL;
 
        glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &logLength);
 
        // Maintenant que nous avons la taille, nous pouvons allou la mmoire suffisante pour ce log
        log = (GLchar*)malloc(logLength);
        if ( log == NULL )
        {
            fprintf(stderr,"Erreur d'allocation de mmoire pour le log de la compilation du shader\n");
            return 0;
        }
 
        glGetShaderInfoLog(shaderID, logLength, &logLength, log);
 
        // On peut afficher le message
        fprintf(stderr,"Erreur de compilation:\n%s",log);
 
        // Et on n'oublie pas de libr la mmoire
        free(log);
 
        return 0;
    }
 
    return 1; // Pas d'erreur
}

Le code qui suit charge et compile nos shaders :

Chargement et compilation des shaders
Sélectionnez
void loadShader()
{
    // Lecture des fichiers
    // Certaines personnes aiment avoir le fichier du vertex shader avec l'extension .vert
    // et le fichier du fragment shader avec l'extension .frag
    GLchar* vertexSource = (GLchar*)readFile("data/simple.vert");
    GLchar* fragmentSource = (GLchar*)readFile("data/simple.frag");
    GLint programState = 0;
    GLint vertexSize = 0;
    GLint fragmentSize = 0;
 
    // Cration des IDs
    vertexID = glCreateShader(GL_VERTEX_SHADER);
    fragmentID = glCreateShader(GL_FRAGMENT_SHADER);
 
    // Vrification des fichiers
    if ( !vertexSource || !fragmentSource )
    {
        // Ici, il faudrait faire en sorte que le programme s'arrte
        deleteShader();    // Nettoyage
        return;
    }
 
    // Chargement des sources dans OpenGL
    vertexSize = strlen(vertexSource);
    fragmentSize = strlen(fragmentSource);
    glShaderSource(vertexID, 1, (const GLchar**)(&vertexSource), &vertexSize);
    glShaderSource(fragmentID, 1, (const GLchar**)(&fragmentSource), &fragmentSize);
 
    // Compilation du vertex shader
    glCompileShader(vertexID);
    glCompileShader(fragmentID);
 
    // Vrification des erreurs
    if ( !checkShaderCompilation(vertexID) || !checkShaderCompilation(fragmentID))
    {
        deleteShader();
        return;
    }
 
 
    // Creation de l'ID pour le programme
    programID = glCreateProgram();
 
    // On attache les shaders ensemble
    glAttachShader(programID, vertexID);
    glAttachShader(programID, fragmentID);
 
    // On peut enfin passer aux liage.
    glLinkProgram(programID);
 
    // Et encore une fois on vrifie si tout se passe bien
    glGetProgramiv(programID , GL_LINK_STATUS  , &programState);
    if ( programState != GL_TRUE)
    {
        // On rcupre la taille du log
        GLint logSize = 0;
        GLchar* log = NULL;
 
        glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &logSize);
 
        // On peut allouer la mmoire, une fois que l'on a la taille du log
        log = (GLchar*)malloc(logSize);
        if ( log == NULL )
        {
            fprintf(stderr,"Erreur d'allocation de mmoire pour le log de la compilation du programme\n");
            deleteShader();
            return;
        }
 
        // Et on rcupre le log
        glGetProgramInfoLog(programID, logSize, &logSize, log);
 
        // On affiche
        fprintf(stderr,"Erreur lors du liage du shader:\n%s",log);
 
        free(log);
        deleteShader();
        return;
    }
 
    // Voil, nous sommes prt
    glUseProgram(programID);
}

Et pour notre premier essai, nous utilisons le vertex shader ci-dessous. Ce vertex shader reproduit le comportement du pipeline fixe afin de dfinir la position du sommet. La deuxime ligne copie la couleur du sommet au fragment shader.

Vertex shader
Sélectionnez
void main (void)
{
    gl_Position = ftransform();
    gl_FrontColor = gl_Color;
}

Le fragment shader suivant copie la couleur attribue aux sommets dans les pixels.

Fragment shader
Sélectionnez
void main(void)
{
    gl_FragColor = gl_Color;
}

Si vous lancez le programme, vous remarquez que rien de spectaculaire ne se passe. Il n'y a que le cube qui tourne et il a les couleurs comme indiques dans le programme OpenGL. En plus, si vous supprimez tout le code li au shader, ou mme, plus simple, si vous enlevez le code glUseShader() de la fonction loadShader(), vous aurez exactement le mme rsultat :

Notre premire application utilisant des shaders!
Notre premire application utilisant des shaders!

Vous pouvez tlcharger le projet (Code::Blocks / Windows) en cliquant iciProgramme OpenGL basique pour travailler avec les shaders

Voil, nous avons le premier programme OpenGL utilisant des shaders en tat de marche. Certes le rsultat n'est pas spectaculaire, mais cela est un trs bon dbut.

Maintenant, vous allez remplacer votre vertex shader, par le suivant :

Vertex shader 2
Sélectionnez
void main (void)
{
    gl_Position = ftransform();
    gl_Position = vec4(gl_Position.xyz * 0.3,gl_Position.w);
    gl_FrontColor = gl_Color;
}

Voil, notre cube est devenu tout petit. Effectivement, le vertex shader que j'ai fait rtrcit les dimensions des vertex :

Un plus petit cube
Un plus petit cube

Vous pouvez reprendre le premier vertex shader que je vous ai prsent, mais remplacer le fragment shader par le suivant :

Fragment shader 2
Sélectionnez
void main(void)
{
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}

Maintenant, notre cube est tout rouge alors que je n'ai touch aucune ligne de code du programme OpenGL :

Un cube rouge
Un cube rouge

Mais, sachez que les shaders ne permettent pas uniquement de changer la taille ou la couleur des objets, ils permettent beaucoup plus et nous allons le voir dans la suite de ce tutoriel.


prcdentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Alexandre Laurent. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.