Du Java, LWJGL et OpenGL


  • Modérateurs

    Ça y est enfin! Un tuto sur OpenGL, depuis le temps que vous le demandiez!

    OpenGL étant une librairie bas niveau extrêmement grande et puissante, nous ne verrons pas toutes les possibilités qu'elle nous offre.
    Si vous voulez vraiment toutes les infos, direction le site d'OpenGL: http://www.opengl.org/

    Ce tutoriel sera écrit au fur et à mesure.
    Et j'utiliserai Eclipse, si vous utilisez un autre IDE, vous pouvez suivre les tutos sur le wiki de LWJGL

    Lexique disponible en bas du post, les mots contenus seront indiqués ainsi 😉

    Aussi, si vous voyez une méthode qui ne "possède" pas de classe et qui n'est pas contenue dans la classe courante, cela signifie qu'elle fait partie de OpenGL, référez-vous au GitHub si besoin!

    Bref, c'est parti pour le tutoriel!

    Mettre en place un projet pour OpenGL

    Commencez par créer le projet. C'est une étape assez basique mais voilà à quoi cela ressemble de mon côté:

    Parce que j'aime pas avoir de projet complétement vide quand je commence à coder, voici la classe principale:

    package fr.mff.tutos.ogl;
    
    public class OpenGLTuto1
    {
        public static void main(String[] args)
        {
    
        }
    }
    

    Pour ce tutoriel, nous utiliserons LWJGL que vous pouvez récupérer en cliquant ici
    Prenez la version la plus stable et prenez la version compilée (les sources ne vont serviront à rien ici)
    <– le logo de LWJGL

    Une fois l'archive téléchargée et décompressée, clic droit sur votre projet, Build Path... -> Configure et ajoutez ces fichiers

    Allez on y est presque, il faut encore ajouter les fichiers natifs nécessaires !
    Pour cela, cliquez sur la flèche à gauche de lwjgl.jar et double cliquez sur "Native Location", choisisez External Folder et cherchez le dossier avec les fichiers natifs de LWJGL (inclus dans le téléchargement)

    Voilà ce que ça donne pour moi:

    Maintenant, il faut tester que ça marche!

    package fr.mff.tutos.ogl;
    
    import org.lwjgl.LWJGLException;
    import org.lwjgl.opengl.Display;
    
    public class OpenGLTuto1
    {
        public static void main(String[] args)
        {
            try
            {
            Display.create();
            }
            catch(LWJGLException e)
            {
            e.printStackTrace();
            }
        }
    }
    

    :huh: "On lance ça et… Quoi une fenêtre qui apparaît puis disparaît en moins d'une seconde ?! :o"

    ❗

    Display.create();
    

    Cette instruction crée le contexte OpenGL et une fenêtre pour ce dernier. Mais comme on ne fait que la créer et qu'on arrive à la fin de main(), elle est directement détruite.

    Ça y est, vous êtes prêt pour créer le prochain Portal !

    #Dessiner avec OpenGL(Dessiner avec OpenGL)
    Avant de pouvoir commencer à dessiner un rectangle, nous avons besoin de nous occuper de quelques "détails".

    Il faut mettre en place les matrices pour dessiner:

    package fr.mff.tutos.ogl;
    
    import org.lwjgl.LWJGLException;
    import org.lwjgl.opengl.Display;
    import org.lwjgl.opengl.GL11;
    
    public class OpenGLTuto1
    {
        public static void main(String[] args)
        {
            try
            {
                Display.create();
                GL11.glMatrixMode(GL11.GL_PROJECTION);
                GL11.glLoadIdentity();
                GL11.glOrtho(0, 800, 0, 600, 1, -1);
                GL11.glMatrixMode(GL11.GL_MODELVIEW);
            }
            catch(LWJGLException e)
            {
                e.printStackTrace();
            }
        }
    }
    
    GL11.glMatrixMode(GL11.GL_PROJECTION);
    GL11.glLoadIdentity();
    GL11.glOrtho(0, 800, 0, 600, 1, -1);
    GL11.glMatrixMode(GL11.GL_MODELVIEW);
    

    :huh: "Qu'est-ce que c'est tout ça ? :o"

    ❗ GL11.glMatrixMode(…) nous permet de choisir dans quelle matrice nous allons faire les changements.
    Le premier appel permet de choisir la matrice de projection, l'équivalent de votre fenêtre.

    ❗ GL11.glLoadIdentity(); : cette méthode permet de charger une matrice par défaut pour la matrice actuelle.

    ❗ GL11.glOrtho(0,800,0,600,1,-1); : Cette méthode va modifier la matrice de façon à ce que l'on dessine sous dans un rectangle de 800x600 pixels avec une profondeur max de 1 et une profondeur min de -1.

    ❗ Le second appel à GL11.glMatrixMode(…) nous permet de sélectionner la matrice modèle, celle où on va tout dessiner 😉

    Ensuite, il faut faire la boucle de rendu, c'est assez simple:

    package fr.mff.tutos.ogl;
    
    import org.lwjgl.LWJGLException;
    import org.lwjgl.opengl.Display;
    import org.lwjgl.opengl.GL11;
    
    public class OpenGLTuto1
    {
        public static void main(String[] args)
        {
            try
            {
                Display.create();
                GL11.glMatrixMode(GL11.GL_PROJECTION);
                GL11.glLoadIdentity();
                GL11.glOrtho(0, 800, 0, 600, 1, -1);
                GL11.glMatrixMode(GL11.GL_MODELVIEW);
                while(!Display.isCloseRequested())
                tick();
            }
            catch(LWJGLException e)
            {
                e.printStackTrace();
            }
        }
    
        private static void tick()
        {
            Display.update();
            Display.sync(60);
        }
    }
    
    while(!Display.isCloseRequested())
        tick();
    

    Tant que l'utilisateur ne veut pas fermer la fenêtre, on continue à dessiner.

    private static void tick()
    {
        Display.update();
        Display.sync(60);
    }
    

    Display.sync(60); fait en sorte que la boucle tourne à 60 Hz (ou FPS, comme vous voulez)
    Display.update(); met à jour la fenêtre, tout simplement.

    Et donc si on lance l'application, on a une fenêtre géante qui coupe le programme quand on la ferme, tout va pour le mieux! 😄

    On ajoute une ligne avant Display.sync(…) pour dessiner un rectangle à (0;0) de 100x100 pixels:

    glRecti(0, 0, 100, 100);
    

    Et voilà!

    Et si on ajoute une variable pour "déplacer" le rectangle:

    private static void tick()
    {
        ticks++;
        glRecti(ticks, 0, ticks+100, 100);
        Display.update();
        Display.sync(60);
    }
    

    Horreur et damnation!

    On dirait que le rectangle s'étale! :s

    Il nous manque quelque chose: vider le buffer de couleurs.
    En gros, vider "l'écran" pour redessiner.

    :idea: "Donc je dois dessiner un rectangle de la taille de l'écran!"
    Ça marchera, mais ce n'est pas du tout le plus simple, il suffit d'ajouter cette ligne tout au début de la méthode tick():

    glClear(GL_COLOR_BUFFER_BIT);
    

    Et c'est propre maintenant!

    Le maître des couleurs

    Pour choisir la couleur de dessin, il faut utiliser cette instruction:
    glColor3f(R, G, B);
    où R, G, B sont des float normalisés
    Par exemple:
    glColor3f(1, 0, 0); est rouge vif.
    glColor3f(1, 1, 0); est jaune.
    Si on ajoute cette ligne avant notre dessin du rectangle:

    Un rectangle rouge!

    OpenGL direct

    C'est maintenant que les choses sérieuses commencent… Enfin ce n'est que le début du début ❗

    :huh: "C'est bien beau de dessiner un rectangle, mais si je veux dessiner un losange, je fais comment ?"

    ❗ D'abord, on va afficher notre rectangle, mais du autre manière, légèrement plus complexe:

    glBegin(GL_QUADS);
    glVertex2d(ticks, 0);
    glVertex2d(ticks+100, 0);
    glVertex2d(ticks+100, 100);
    glVertex2d(ticks, 100);
    glEnd();
    

    :huh: "Et oh! Tu nous balances du code comme ça, je comprends rien !"

    ❗ glBegin(GL_QUADS); va dire à OpenGL "prépare toi on va dessiner un super quadrilatère!"

    glVertex2d(ticks, 0);
    glVertex2d(ticks+100, 0);
    glVertex2d(ticks+100, 100);
    glVertex2d(ticks, 100);
    

    Chacune des instructions indique un point du quadrilatère "vertex" pour point en anglais, 2 pour... 2 et d pour double

    Et ensuite:
    glEnd(); pour dire à OpenGL "c'est bon, j'ai dessiné mon super quadrilatère"
    Donc, on a toujours le même résultat qu'avant.
    Mais en jouant avec les paramètres, on peut faire un losange!

    Ou ce truc...

    :huh: "Mais si je veux juste dessiner un triangle ?"
    ❗ C'est simple! Remplace GL_QUADS par GL_TRIANGLES et retire un des points!
    Exemple:

    glBegin(GL_TRIANGLES);
    glVertex2d(ticks, 100);
    glVertex2d(ticks+50, 100+100);
    glVertex2d(ticks+100, 100);
    glEnd();
    

    Et il en reste encore pleins!
    GL_POINTS Pour dessiner des points,
    GL_POLYGON Pour dessiner un polygone quelconque!

    Opération Transformation

    :huh: "C'est bien beau tout ça, mais si je veux faire bouger mon triangle, je suis obligé d'utiliser une variable ?"

    ❗ Et bien non! Il existe un moyen de bouger les points sans toucher modifier les glVertex !

    Je vous présente: glTranslate

    On ajoute glTranslated(1,0,0) avant glBegin et on supprime ticks

    private static void tick()
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glTranslated(1, 0, 0);
        glBegin(GL_TRIANGLES);
        glColor3f(0, 0, 1);
        glVertex2d(0, 100);
        glColor3f(0, 1, 0);
        glVertex2d(50, 100+100);
        glColor3f(1, 0, 0);
        glVertex2d(100, 100);
        glEnd();
        Display.update();
        Display.sync(60);
    }
    

    Et magie le triangle se déplace comme avant!

    :huh: "C'est quoi ces glColor en plein milieu du code de dessin et pourquoi mon triangle est multicolore ?"

    ❗ En utilisant *glColor *avant un appel à glVertex permet de spécifier la couleur de chacun des points et OpenGL va faire un dégradé entre chacun des points! C'est plus joli!

    ❗ Attention ! A chaque appel de glTranslate, tout ce qui est dessiné après est décalé, d'où le fait que le triangle se déplace.

    Tiens! Essayons de rendre le triangle deux fois plus grand!

    :idea: "Je sais! Il faut changer les points pour les éloigner!"

    C'est une bonne idée, mais ça va pas être possible pour de gros modèles et je ne parle même pas des possibilités de se tromper!

    On va utiliser une autre méthode de transformation: glScale!

    Juste avant glTranslate, on insère un appel à glScale:

    private static void tick()
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glScaled(2, 2, 2);
        glTranslated(1, 0, 0);
        glBegin(GL_TRIANGLES);
        glColor3f(0, 0, 1);
        glVertex2d(0, 100);
        glColor3f(0, 1, 0);
        glVertex2d(50, 100+100);
        glColor3f(1, 0, 0);
        glVertex2d(100, 100);
        glEnd();
        Display.update();
        Display.sync(60);
    }
    

    glScaled(2,2,2); rend le triangle deux fois plus grand sur les axes X, Y et Z

    :huh: "Mais je lance ça et s'affiche rien!"

    ❗ Vous vous rappelez quand j'avais mentionné qu'à chaque appel à glTranslate tout est déplacé ? C'est la même chose pour glScale!
    Donc à chaque appel de glScale (soit 60 fois par seconde !), on fait agrandir notre triangle!

    :huh: "Ça fait beaucoup! Mais je fais comment ? J'ajoute glScaled(0.5,0.5,0.5) pour tout rétrécir à la fin du dessin ?"

    ❗ Même si cela marcherai sur une application aussi petite que la notre, ce n'est pas du tout viable, imaginez que vous oubliez de l'ajouter et que vous ne trouviez pas pourquoi votre application ne marche pas!

    La solution est d'appeler glPushMatrix et glPopMatrix!

    Ces méthodes ont pour but de sauvegarder l'état de la matrice (push) puis de le rétablir ensuite (pop)

    Non non… Pas cette matrice là

    Donc on ajoute%(#1E90FF)* avant les transformations et glPopMatrix après le dessin!

        private static void tick()
        {
            glClear(GL_COLOR_BUFFER_BIT);
            glPushMatrix();
            glScaled(2, 2, 2);
            glTranslated(1, 0, 0);
            glBegin(GL_TRIANGLES);
            glColor3f(0, 0, 1);
            glVertex2d(0, 100);
            glColor3f(0, 1, 0);
            glVertex2d(50, 100+100);
            glColor3f(1, 0, 0);
            glVertex2d(100, 100);
            glEnd();
            glPopMatrix();
            Display.update();
            Display.sync(60);
        }
    

    Et voilà! On voit notre triangle! Mais il est immobile maintenant, puisqu'on ne déplace plus tout 60 fois par seconde!

    :huh: "Et si on veut faire tourner le triangle ? On utilise glRotate(monAngle); ?"

    ❗ Tu n'es pas loin! Il faut utiliser glRotated(angle, axeX, axeY, axeZ) !

    Par exemple si on veut faire tourner le triangle sur lui-même:

    glRotated(45, 0, 0, 1);
    

    Pourquoi 0,0,1 pour les axes ? (0,0,1) est le vecteur qui représente l'axe Z, sauf que on le donne en plusieurs "morceaux": x, y et z
    Et pourquoi Z? me direz-vous. Il faut se représenter notre scène comme une scène en 3D et où tout est dessiné à Z = 0
    L'axe Z est donc celui "qui part au fond de l'écran" et on fait tourner les points autour!

    On ajoute ça après glTranslated et voilà ce que ça nous donne:

    private static void tick()
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glPushMatrix();
        glScaled(2, 2, 2);
        glTranslated(50, 0, 0);
        glRotated(45, 0, 0, 1);
        glBegin(GL_TRIANGLES);
        glColor3f(0, 0, 1);
        glVertex2d(0, 100);
        glColor3f(0, 1, 0);
        glVertex2d(50, 100+100);
        glColor3f(1, 0, 0);
        glVertex2d(100, 100);
        glEnd();
        glPopMatrix();
        Display.update();
        Display.sync(60);
    }
    

    Changez un peu la valeur de l'angle et…
    :huh: "Mais mon triangle ne tourne pas sur lui-même!"

    ❗ En effet! glRotate utilise comme "point de rotation" l'origine. Pour changer ce point, il faut faire une translation vers le point, puis l'annuler après la rotation!

    private static void tick()
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glPushMatrix();
        glScaled(2, 2, 2);
        glTranslated(50, 0, 0);
    
        glTranslated(50, 150, 0);
        glRotated(angle++, 0, 0, 1);
        glTranslated(-50, -150, 0);
    
        glBegin(GL_TRIANGLES);
        glColor3f(0, 0, 1);
        glVertex2d(0, 100);
        glColor3f(0, 1, 0);
        glVertex2d(50, 100+100);
        glColor3f(1, 0, 0);
        glVertex2d(100, 100);
        glEnd();
        glPopMatrix();
        Display.update();
        Display.sync(60);
    }
    

    J'en ai profité pour ajouter une variable angle, pour que l'on puisse voir la rotation en temps réel!

    Les textures!

    (Nous utiliserons cette texture)
    Alors là, va falloir s'accrocher!
    Va falloir:

    • Indiquer à OpenGL qu'on veut des textures
    • Charger la texture
    • La convertir en un format utilisable par OpenGL
    • Et gérer la transparence
      C'est parti!

    Indiquer à OpenGL qu'on veut des textures!
    Dans la méthode principale, juste avant la boucle, on ajoute glEnable(GL_TEXTURE_2D)

    public static void main(String[] args)
    {
        try
        {
            Display.create();
            GL11.glMatrixMode(GL11.GL_PROJECTION);
            GL11.glLoadIdentity();
            GL11.glOrtho(0, 800, 0, 600, 1, -1);
            GL11.glMatrixMode(GL11.GL_MODELVIEW);
            glEnable(GL_TEXTURE_2D);
            loadTexture();
            while(!Display.isCloseRequested())
                tick();
        }
        catch(LWJGLException e)
        {
            e.printStackTrace();
        }
    }
    

    Et c'est tout!

    Charger la texture
    On commence par créer une méthode loadTexture()

    private static void loadTexture()
    {
    
    }
    

    Ensuite il faut réserver une place pour la texture et la sélectionner:

    private static int textureID;
    
    private static void loadTexture()
    {
        textureID = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, textureID);
    }
    

    Puis on récupère tous les pixels présents dans l'image:

    private static void loadTexture()
    {
        textureID = glGenTextures();
        glBindTexture(GL_TEXTURE_2D, textureID);
        try
        {
            BufferedImage img = ImageIO.read(OpenGLTuto1.class.getResourceAsStream("/OpenGL_Logo.png"));
            int[] pixels = img.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    

    Enfin, on doit créer un buffer pour les pixels:

    FloatBuffer pixelsBuffer = BufferUtils.createByteBuffer(pixels.length * 4 * 4).asFloatBuffer();
    

    Cela permet de créer un buffer de qui contiendra tous les pixels (d'où pixels.length) où chacun des pixels à 4 valeurs: R,G,B et A et chacune de ces valeurs seront encodées avec un float (qui fait 4 fois la taille d'un byte).

    On ajoute les pixels dans le buffer:

    for(int y = img.getHeight()-1;y>=0;y–)
    {
        for(int x = 0;x <img.getwidth();x++){
            int pixel = pixels[y*img.getWidth()+x];
            float a = ((pixel >> 24) & 0xFF)/255f;
            float r = ((pixel >> 16) & 0xFF)/255f;
            float g = ((pixel >> 8) & 0xFF)/255f;
            float b = ((pixel >> 0) & 0xFF)/255f;
    
            pixelsBuffer.put( r);
            pixelsBuffer.put(g);
            pixelsBuffer.put(b);
            pixelsBuffer.put(a);
        }
    }
    

    ❗ Vous avez peut-être remarqué que l'on décrémente la variable y. En effet, les textures OpenGL sont orientées vers le haut alors que les fichiers images sont orientés vers le bas.

    Puis on finalise le buffer:

    pixelsBuffer.flip();
    

    Mais avant d'envoyer les pixels, il faut d'abord indiquer le filtre utilisé, nous utiliserons GL_LINEAR pour faire simple mais vous pouvez en essayer d'autres tels que GL_NEAREST
    Ainsi, on ajoute ces deux lignes:

    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
    

    Puis on envoie les pixels!

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.getWidth(), img.getHeight(), 0, GL_RGBA, GL_FLOAT, pixelsBuffer);
    

    GL_TEXTURE_2D : la cible
    0 : Niveau de MipMapping (on verra ça peut-être plus tard)
    GL_RGBA : format utilisé par OpenGL
    img.getWidth() : largeur de la texture
    img.getHeight() : hauteur de la texture
    0 : Bordures
    GL_RGBA : format des pixels
    GL_FLOAT : format d'une donnée
    pixelsBuffer : le buffer contenant nos pixels

    Et voilà! Notre texture est chargée!

    Maintenant, il faut l'utiliser!
    Juste après vider le buffer de couleurs, on bind la texture:

    glBindTexture(GL_TEXTURE_2D, textureID);
    

    Il faut aussi spécifier les coordonnées de textures!
    Pour cela, avant chaque appel à glVertex, on fait un appel à glTexCoord2d:

    glBegin(GL_QUADS);
    
    glTexCoord2d(0, 0);
    glVertex2d(0, 0);
    
    glTexCoord2d(0, 1);
    glVertex2d(0, 149);
    
    glTexCoord2d(1, 1);
    glVertex2d(300, 149);
    
    glTexCoord2d(1, 0);
    glVertex2d(300, 0);
    
    glEnd();
    

    Les coordonnées sont normalisées elles aussi. Donc ici, pour chaque point du rectangle, on associe un "point" dans la texture, soit toute la texture (on part de 0,0 et on passe par toutes les coordonnées "max" jusqu'à 1,1)

    On lance et…

    Catastrophe! Il y a des bordures blanches alors que la texture n'en a pas!

    Gérer la transparence
    Tout simplement, avant d'activer l'utilisation des textures, on redéfinit la méthode de superposition des couleurs ainsi:

    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    

    On active la superposition puis on redéfinit la méthode.

    Et c'est bon!

    #Les shaders!(Les shaders!)

    Premièrement: Qu'est un shader ?
    Si on entendant le nom 'shader' vous pensiez à cela:

    Vous n'auriez pas totalement tort.
    Mais cela ne donne pas la définition d'un shader !

    :huh: Donc, un shader, qu'est-ce que c'est ?

    ❗ Un shader est un petit programme (rarement plus d'une cinquantaine de lignes) exécuté par la carte graphique qui permet de modifier la géométrie ou l'affichage. Pour OpenGL, il est écrit dans un langage appelé GLSL.

    En voici un exemple:

    #version 120
    
    void main()
    {
        gl_Position = ftransform();
    }
    

    Nous allons commencer par créer une classe qui va gérer ces programmes pour nous:

    package fr.mff.tutos.ogl;
    
    public class Shader
    {
    
    }
    

    Généralement, un shader est créé à partir de deux fichiers: le "vertex shader" et le "fragment shader", qui s'occupent respectivement du positionnement dans l'espace et du choix de la couleur des pixels.

    Donc on met à jour notre classe pour ajouter un constructeur qui va prendre en compte le nom des deux fichiers:

    public Shader(String vertexShader, String fragmentShader)
    {
    
    }
    

    Le problème est qu'il faut compiler les shaders au lancement du programme, il faut donc dire à OpenGL de prendre le code source et de le compiler.

    Pour cela, il faut d'abord lire le code source.
    Comme la lecture n'est pas le sujet de ce tutoriel, voici le code qui va nous donner un objet String contenant le texte d'un fichier:

    public static String read(String fileName)
    {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
    
        try
        {
            InputStream in = OpenGLTuto1.class.getResourceAsStream(fileName);
            byte[] buffer = new byte[65565];
            int i;
            while((i = in.read(buffer, 0, buffer.length)) != -1)
            {
                out.write(buffer, 0, i);
            }
            out.flush();
            out.close();
            return new String(out.toByteArray(), "UTF-8");
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
    

    Et on mets à jour notre constructeur pour récupérer le code source:

    public Shader(String vertexShader, String fragmentShader)
    {
        String vertexSource = read(vertexShader);
        String fragmentSource = read(fragmentShader);
    }
    

    Rien de bien impressionnant pour le moment.

    Maintenant, nous allons faire plusieurs actions en un coup:

    1. Créer un pointeur pour le programme (ou ID)
    2. Attacher le code source
    3. Lier les sources au programme
    4. Compiler le programme

    Comme le code ne pas vraiment être découpé, sinon ça paraîtrai confus, voici le code qui gère cela:

    public Shader(String vertexShader, String fragmentShader)
    {
        String vertexSource = read(vertexShader);
        String fragmentSource = read(fragmentShader);
    
        this.programID = GL20.glCreateProgram(); // On crée le pointeur
    
        addSource(vertexSource, GL_VERTEX_SHADER); // On ajoute le code source du vertex shader à notre programme
        addSource(fragmentSource, GL_FRAGMENT_SHADER); // On ajoute le code source du fragment shader à notre programme
    
        glLinkProgram(programID); // On lie toutes les données ensemble
    
        if(glGetProgrami(programID, GL_LINK_STATUS) == 0) // On vérifie que tout est bien lié
        {
            System.err.println(glGetProgramInfoLog(programID, 1024));
            return;
        }
    
        glValidateProgram(programID); // On valide le programme
    
        if(glGetProgrami(programID, GL_VALIDATE_STATUS) == 0) // On vérifie que la validation n'a pas échouée
        {
            System.err.println(glGetProgramInfoLog(programID, 1024));
            return;
        }
    }
    
    private void addSource(String source, int type)
    {
        int shader = glCreateShader(type); // On crée la plage mémoire pour le shader
    
        glShaderSource(shader, source); // On attache la source
        glCompileShader(shader); // On compile le shader
    
        if(glGetShaderi(shader, GL_COMPILE_STATUS) == 0) // On vérifie qu'il n'y a pas d'erreurs
        {
            System.err.println(glGetShaderInfoLog(shader, 1024));
            return;
        }
    
        glAttachShader(programID, shader); // On attache le code compilé à notre programme
    }
    

    N'hésitez pas à poster un commentaire si vous ne comprenez ce code.

    Maintenant, nous allons créer une méthode qui va indiquer à OpenGL qu'on utilise un shader, la méthode bind():

    public void bind()
    {
        glUseProgram(programID);
    }
    

    Et c'est tout!
    Allons programmer des shaders maintenant!

    Programmation de shaders!

    Il est clair que ce tutoriel n'a pas pour but de vous apprendre le langage GLSL. Mais la syntaxe étant très similaire à celle du C (et donc de Java), vous ne devriez pas être trop perdus.

    Nous allons commencer par analyser un vertex shader basique:

    #version 120
    
    void main()
    {
        gl_Position = ftransform();
    }
    

    Nous précisons la version de GLSL utilisée, ici 120 (vieille de plus de 10-20 ans!)

    Et dans la méthode main(), là où le programme commence, nous assignons la valeur retournée par ftransform() à gl_Position. Cela permet de faire effectuer tous les calculs de positionnement du point par OpenGL.

    Ensuite, un fragment shader basique:

    #version 120
    
    void main()
    {
        gl_FragColor = vec4(1,1,1,1);
    }
    

    La même chose sauf que cette fois ci, nous assignons le vecteur (1,1,1,1) à gl_FragColor, c'est à dire la couleur blanc opaque.

    Voilà nos shaders sont prêts! On les ajoute dans la source en tant que basic.fs et basic.vs (vous pouvez mettre n'importe qu'elle extension, GLSL n'a pas d'extension "réservée") dans notre projet:

    Ensuite, après avoir charger la texture, on crée l'objet shader:

    public static void main(String[] args)
    {
        […]
        loadTexture();
    
        shader = new Shader("/basic.vs", "/basic.fs");
        […]
    }
    

    Puis on active le shader dans tick():

    private static void tick()
    {
        glClear(GL_COLOR_BUFFER_BIT);
        shader.bind();
    
        […]
    }
    

    Et on lance!

    :huh: Mais où est passée la texture ?

    ❗ Vous vous rappelez la ligne "gl_FragColor = vec4(1,1,1,1);" ?
    En utilisant cette ligne, chacun des pixels utilisés seront blanc opaque.

    Pour résoudre ce problème, il va falloir gérer les activations de textures.

    Activer une texture et l'utiliser avec GLSL

    [EN COURS]


    [En cours de rédaction]

    Lexique

    • Librairie bas niveau: une librairie où beaucoup d'instructions seront nécessaires pour une seule action.
    • Buffer: "Mémoire tampon", mémoire temporaire qui contient des données arbitraires attendant d'êtres utilisées.
    • Matrice: Sorte de tableau de nombres utilisée pour modifier des groupes de nombres, plus d'infos ici
    • Fichiers natifs: Code compilé uniquement utilisable pour la plateforme sous laquelle il a été compilé (e.g dll)
    • Contexte OpenGL: L'instance d'OpenGL qui va nous permettre de communiquer avec la carte graphique
    • Valeur normalisée: Une valeur normalisée est une valeur comprise entre 0 et 1

    Tutoriel disponible sur GitHub: https://github.com/jglrxavpok/MFF-Tutos-OpenGL/


  • Moddeurs confirmés Modérateurs

    de l'avenir dans ce tuto ca me plais et ca va nous être utile


  • Modérateurs

    Ajout d'un lexique!

    Et merci isador ^^


  • Moddeurs confirmés Modérateurs

    de rien, tu es a la hauteur du boulot fourni 🙂


  • Modérateurs

    Et encore, c'est pas grand chose là
    (enfin, niveau code, pas niveau rédaction, c'est la première fois que j'ai l'impression de faire un vrai tuto ^^)


  • Moddeurs confirmés Modérateurs

    ^^ fait une première a tout fait péter le champagne


  • Modérateurs

    Ajout d'une partie sur la couleur et du rendu direct et du lien vers le repo GitHub



  • @'isador34':

    ^^ fait une première a tout fait péter le champagne

    Hohoho !!! CHAMMMMMMMMMMPAGNE !!


  • Moddeurs confirmés Rédacteurs Administrateurs


  • Modérateurs

    @'robin4002':

    Youtube Video

    xD



  • Merci beaucoup, excellent début de tuto, ça va être très utile 😉 étant donné qu'on regarde la même série, je pourrais faire un ou deux tutos "de complétion" aux tiens, en tout cas GG 😄



  • @'robin4002':

    Youtube Video

    C'est exactement se a quoi je pensé :p. Cyprien et sa clics :p.


  • Moddeurs confirmés Modérateurs

    Tu pourrai nous montrer les autre paramètre autre que gl_quad et gl_triangle? Genre pour une forme avec un nombre indéfini de point


  • Modérateurs

    Ajout des transformations basiques!


  • Moddeurs confirmés Modérateurs

    voila la je vais attaquer le bordel ^^



  • dat tuto


  • Modérateurs

    Ajout du support des textures!



  • xavpok, j'ai une question un peu trop avancé par rapport au tuto, est ce que je la pose ici (pour que les autres la voient) ou je t'envoie un mp ? 😄


  • Modérateurs

    N'hésite pas à la poster ici 😉



  • Non en fait j'ai pu résoudre le problème (c'était un problème de shaders) 😉


Log in to reply