Les bases pratiques d'OpenGL


  • Modérateurs

    Sommaire

    Introduction

    Bonjour chers lecteurs,

    Vu les nombreuses demandes d'aide qui pourraient être réglées avec un minimum de connaissances des bases d'OpenGL, voici un petit tutoriel pratique sur les bases de cette librairie graphique.

    XOXOXO -jglrxavpok

    Le code de ce tutoriel est disponible ici.

    Le but de ce tutoriel est de vous apprendre à utiliser les bases d'OpenGL pour vous aider à faire vos mods, il n'ira donc pas dans les détails du fonctionnement de la librairie.
    Par soucis de simplicité, les diverses commandes vont être présentées pour le rendu dans un Gui mais les idées restent les mêmes dans n'importe quel contexte.

    Pré-requis

    Pour ce tutoriel, vous devez avoir suivi au moins les tutoriels suivants:

    Code de base et ressources([size]Code de base et ressources)

    Dans ce tutoriel nous utiliserons les deux images suivantes:


    Et voici l'arborescence associée:

    Voici maintenant les classes de base que l'on va utiliser pour le tutoriel, vous n'avez évidemment pas besoin d'avoir les mêmes, cela permet juste de suivre les modifications.

    package fr.minecraftforgefrance.tutorial;
    
    import fr.minecraftforgefrance.tutorial.client.ClientEventHandler;
    import net.minecraftforge.common.MinecraftForge;
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.common.Mod.EventHandler;
    import net.minecraftforge.fml.common.event.FMLInitializationEvent;
    
    @Mod(modid = OpenGLPratique.MODID, version = OpenGLPratique.VERSION)
    public class OpenGLPratique
    {
        public static final String MODID = "openglpratique";
        public static final String VERSION = "1.0";
    
        @EventHandler
        public void init(FMLInitializationEvent event)
        {
            MinecraftForge.EVENT_BUS.register(new ClientEventHandler());
        }
    }
    
    
    package fr.minecraftforgefrance.tutorial.client;
    
    import fr.minecraftforgefrance.tutorial.OpenGLPratique;
    import net.minecraft.client.Minecraft;
    import net.minecraft.client.gui.ScaledResolution;
    import net.minecraft.client.gui.inventory.GuiInventory;
    import net.minecraft.client.renderer.Tessellator;
    import net.minecraft.client.renderer.VertexBuffer;
    import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
    import net.minecraft.util.ResourceLocation;
    
    import static org.lwjgl.opengl.GL11.*;
    
    /**
    * Ceci est une classe utilisée pour l'exemple et n'est probablement pas utilisable en pratique sans faire des gros changements.
    *
    * Permet de dessiner le logo de MinecraftForgeFrance et le modèle du joueur.
    */
    public class Renderer {
    
        private static final ResourceLocation opaqueLogo = new ResourceLocation(OpenGLPratique.MODID, "logo_mff128x128.png");
        private static final ResourceLocation transparentLogo = new ResourceLocation(OpenGLPratique.MODID, "transparentlogo_mff128x128.png");
        private static final int WIDTH = 128;
        private static final int HEIGHT = 128;
    
        public static void drawOpaqueSprite(ScaledResolution resolution) {
            Minecraft.getMinecraft().getTextureManager().bindTexture(opaqueLogo);
            Tessellator tessellator = Tessellator.getInstance();
            VertexBuffer buffer = tessellator.getBuffer();
            buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
            float w = WIDTH / ((float)resolution.getScaleFactor());
            float h = HEIGHT / ((float)resolution.getScaleFactor());
            buffer.pos(0,0,0).tex(0, 0).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(0,h,0).tex(0, 1f).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(w, h,0).tex(1f, 1f).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(w,0,0).tex(1f, 0).color(1f, 1f, 1f, 1f).endVertex();
            tessellator.draw();
        }
    
        public static void drawTransparentSprite(ScaledResolution resolution) {
            Minecraft.getMinecraft().getTextureManager().bindTexture(transparentLogo);
            Tessellator tessellator = Tessellator.getInstance();
            VertexBuffer buffer = tessellator.getBuffer();
            buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
            float w = WIDTH / ((float)resolution.getScaleFactor());
            float h = HEIGHT / ((float)resolution.getScaleFactor());
            buffer.pos(0,0,0).tex(0, 0).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(0,h,0).tex(0, 1f).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(w, h,0).tex(1f, 1f).color(1f, 1f, 1f, 1f).endVertex();
            buffer.pos(w,0,0).tex(1f, 0).color(1f, 1f, 1f, 1f).endVertex();
            tessellator.draw();
        }
    
        public static void drawModel(ScaledResolution resolution) {
            Minecraft mc = Minecraft.getMinecraft();
            GuiInventory.drawEntityOnScreen(100,100,resolution.getScaleFactor()*10, 0f, 0f, mc.player);
        }
    }
    
    

    Le gestionnaire d'événements

    package fr.minecraftforgefrance.tutorial.client;
    
    import net.minecraft.client.gui.ScaledResolution;
    import net.minecraftforge.client.event.RenderGameOverlayEvent;
    import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
    
    public class ClientEventHandler {
    
        @SubscribeEvent
        public void onGuiDrawing(RenderGameOverlayEvent.Pre event) {
            if(event.getType() == RenderGameOverlayEvent.ElementType.HOTBAR) {
                draw(event.getResolution());
            }
        }
    
        /**
        * C'est la méthode que l'on va modifier dans ce tutoriel
        */
        private void draw(ScaledResolution resolution) {
            Renderer.drawTransparentSprite(resolution);
            Renderer.drawModel(resolution);
        }
    }
    
    

    Remarquez bien la méthode ClientEventHandler::draw(ScaledResolution), c'est ici que tout va se passer par la suite.
    Si vous lancez le jeu avec le code tel quel, vous devriez avoir quelque chose comme ceci:

    Et voilà vous êtes prêts!

    Commandes

    Premier points

    Avant de commencer à apprendre les commandes de base, il faut que je vous apprenne deux-trois choses sur la façon dont on accède à ces commandes. De plus, pour simplifier, le préfixe GL11. sera omis. C'est possible en ajoutant ceci dans les imports de la classe dans laquelle vous travaillez:

    import static org.lwjgl.opengl.GL11.*;
    

    Points importants:

    • Il est possible (au moins jusqu'à la version 1.11, incluse, de Minecraft) de remplacer les commandes d'OpenGL par des fonctions de GlStateManager, elles sont même préférables à utiliser pour le modding. (les correspondances seront explicitées dans ce tutoriel). Cependant, pour rester général, ce tutoriel utilisera les commandes d'OpenGL directement; cela vous permettra d'utiliser OpenGL en dehors de Minecraft!
    • Format d'une commande OpenGL: gl<nom de la commande><type des paramètres><f, i, b, ub, d, ou vide>(<arguments>)
    • Une commande OpenGL agit toujours sur le rendu fait après la commande. De plus certaines commandes ne remplacent pas mais ajoutent aux effets déjà présents dû à cette même commande (ce sera plus clair dans les exemples), ces fonctions seront explicitées dans ce tutoriel
    • Juste pour ce tutoriel: tous les bouts de code donnés sont considérés comme le contenu de la fonction draw() de ClientEventHandler
    • Juste pour ce tutoriel: tous les appels à la classe Renderer sont à remplacer par votre code de rendu (du genre model.render())

    Voilà, vous êtes prêts pour de vrai cette fois!

    glColor<nombre><type>: Multiplier la couleur

    Cliquez pour avoir la branche de cette partie.
    Propriétés à noter:

    • Remplace totalement la valeur précédente donnée
    • Remplaçable par GlStateManager::color

    La commande glColor permet de multiplier la couleur de rendu par des flottants, dissection avec un exemple:

    glColor3f(multiplicateurRouge, multiplicateurVert, multiplicateurBleu);
    glColor4f(multiplicateurRouge, multiplicateurVert, multiplicateurBleu, multiplicateurAlpha);
    

    Chaque couleur peut être décomposée en trois composantes rouge, vert et bleu (l'espace RGB en anglais) et cette commande permet d'agir sur chacune de ses composantes:
    chacun des termes va multiplier la composante choisie par le terme donné.

    Merci Wikipedia!

    Pour ne garder que le bleu du logo du site, on peut faire ainsi:

            glColor3f(0f, 0f, 1f);
            Renderer.drawTransparentSprite(resolution); // rendu du logo du site avec uniquement le bleu
    
    

    Et pour mettre le logo en opacité à 25%, sans bleu et ajouter le rouge et le vert à 50%:

            glColor4f(0.5f, 0.5f, 0f, 0.25f);
            Renderer.drawTransparentSprite(resolution); // rendu du logo du site à 25% d'opacité, pas de bleu, et division par 2 du rouge et du bleu
    
    

    Attention, cette méthode remplace la couleur précédente:

    glColor3f(0f, 0f, 0f);
    glColor4f(1f, 1f, 1f, 1f);
    Renderer.drawTransparentSprite(resolution); // rendu du logo en couleurs normales!
    

    Ici la couleur est normale au lieu de dessiner le logo en noir!

    glTranslate<type>: faire une translation

    Cliquez pour avoir la branche de cette partie.
    Propriétés à noter:

    • Opération de transformation: on déplace le contenu
    • Des translations successives s'ajoutent!
    • Dans les Gui (à cause de la projection orthographique pour ceux qui veulent le détail), la coordonnée sur Z n'influence pas la taille de l'objet.
    • Remplaçable par GlStateManager::translate

    Avant de vous introduire à glTranslate, il faut que vous voyez glPushMatrix et glPopMatrix.
    Ces deux méthodes vous permettent de sauvegarder la transformation courante:

    • glPushMatrix: sauvegarde la transformation sur une pile de sauvegarde
    • glPopMatrix: restaure la pile en haut de la pile et la retire de cette pile

    Elles s'utilisent donc ainsi:

    glPushMatrix();
    // transformations: translations, rotations, homothéties
    Renderer.drawTransparentSprite(resolution);
    glPopMatrix();
    

    Explication de Wikipédia:
    [quote]
    informatique, une pile (en anglais stack) est une structure de données fondée sur le principe « dernier arrivé, premier sorti » (ou LIFO pour last in, first out), ce qui veut dire, qu'en général, le dernier élément ajouté à la pile sera le premier à en sortir. Le fonctionnement est similaire à celui d'une pile d'assiettes : on ajoute des assiettes sur la pile, et on les récupère dans l'ordre inverse, en commençant par la dernière ajoutée.
    [/quote]

    La commande glTranslate(x, y, z) permet de faire des translations au contenu qui va être dessiné selon les 3 axes X, Y et Z (locaux, on verra plus tard pourquoi).
    L'unité des paramètres dépend énormément du contexte, par exemple pour les blocs, l'unité sera des blocs alors que pour les Gui, ce sera (à peu près) des pixels.

    glPushMatrix();
    // transformations: translations, rotations, homothéties
    // point important: dans un Gui, la coordonnée sur Z n'influence pas la taille
    // (mais permet d'afficher du contenu devant ou derrière quelque chose, selon la valeur choisie)
    glTranslatef(100f, 30f, -1f);
    Renderer.drawModel(resolution);
    glPopMatrix();
    

    Avant le glTranslate:

    Après le glTranslate

    On peut voir que les effets s'ajoutent en décomposant selon les 3 axes, par exemple: les deux codes suivants sont équivalents

    glTranslatef(100f, 0f, 0f);
    glTranslatef(0f, 30f, 0f);
    glTranslatef(0f, 0f, -1f);
    
    glTranslatef(100f, 30f, -1f);
    

    glRotate<type>: faire une rotation

    Cliquez pour avoir la branche de cette partie.
    Propriétés à noter:

    • Opération de transformation: on déplace le contenu
    • Des rotations successives s'ajoutent!
    • L'ordre des rotations a une importante!
      *Remplaçable par GlStateManager::rotate

    La commande glRotate est assez simple: elle prend 4 paramètres qui sont:

    • L'angle de rotation, en degrés, avec les angles positifs allant dans le sens horaire. (attention, si vous utilisez des matrices de rotation plus tard, ce sera en radians et dans le sens trigonométrique)
    • La coordonnée sur l'axe (Ox) de l'axe de rotation
    • La coordonnée sur l'axe (Oy) de l'axe de rotation
    • La coordonnée sur l'axe (Oz) de l'axe de rotation

    Un petit schéma:

    Exemple:
    Essayez ceci (explication après la vidéo):

    glPushMatrix();
    glTranslatef(100f, 100f, 0f);
    glRotatef(Minecraft.getSystemTime()*.125f, 0f, 0f, 1f);
    glTranslatef(-64f/resolution.getScaleFactor(), -64f/resolution.getScaleFactor(), 0f);
    Renderer.drawTransparentSprite(resolution);
    glPopMatrix();
    

    Vous voudriez avoir quelque chose de similaire à ceci: [video]https://youtu.be/UkAMfLfvIzY[/video]

    Dissection du code!

    glTranslatef(100f, 100f, 0f);
    

    On déplace notre contenu vers le centre (à peu près).

    glRotatef(Minecraft.getSystemTime()*.125f, 0f, 0f, 1f);
    

    Cette commande effectue une rotation d'un angle de Minecraft.getSystemTime().125f* (cela permet de faire un petit effet d'animation) autour de l'axe Z.

    glTranslatef(-64f/resolution.getScaleFactor(), -64f/resolution.getScaleFactor(), 0f); // on divise par le facteur de résolution pour faire des positions au pixel près 
    

    Alors c'est cette ligne qui apporte quelque chose d'important. Si on la retire, on obtient ceci: [video]https://youtu.be/B_dvCYeH9GU[/video]

    ❓ Pourquoi ça a changé ?
    ❗ Il s'avère que l'ordre des transformations importe beaucoup! En effet, cette ligne permet en quelque sorte de définir un point d'origine sur le sprite pour la rotation.

    Un petit schéma pour plus de détails:

    La rotation change les axes X,Y,Z locaux et la dernière translation (en rouge) est faite dans la nouvelle base (représentée en rose). La rotation se fait donc à partir du point spécifié (et on se retrouve à la position en noir).

    On peut bien évidemment faire des rotations autour des axes X et Y:

    glPushMatrix();
    glRotatef(-45f, 1f, 0f, 0f);
    glRotatef(-45f, 0f, 1f, 0f);
    Renderer.drawModel(resolution);
    glPopMatrix();
    

    Cet exemple vous donnera ceci:

    Comme dit précédemment, les rotations changent les systèmes d'axes. Ainsi le code suivant, qui inverse les rotations ne donne pas le même résultat:

    glPushMatrix();
    // inversion !
    glRotatef(-45f, 0f, 1f, 0f);
    glRotatef(-45f, 1f, 0f, 0f);
    Renderer.drawModel(resolution);
    glPopMatrix();
    

    Résultat:

    glScale<type>: homothéties et distortions

    Cliquez pour avoir la branche de cette partie.
    Propriétés à noter:

    • Opération de transformation: on déplace le contenu
    • Des homothéties successives s'ajoutent!
    • Remplaçable par GlStateManager::scale

    La commande glScale permet de faire des [infobulle=Une homothétie est une opération mathématique qui 'allonge' des vecteurs depuis un point de départ, un changement d'échelle, ou un redimensionnement en quelque sorte.]homothéties[/infobulle] sur chacun des trois axes. Voici les paramètres:

    • Multiplicateur sur l'axe (Ox)
    • Multiplicateur sur l'axe (Oy)
    • Multiplicateur sur l'axe (Oz)


    Merci Wikipédia encore!

    Pour chacun de ses paramètres, un multiplicateur égal à 1 équivaut à aucun changement. Une valeur entre -1 et 1 rétrécit l'objet alors qu'une valeur f telle que |f| > 1 agrandit l'objet.

    Voici un petit exemple avec le logo du forum (je n'utilise pas le modèle du joueur ici car son code de rendu fait aussi des translations, et un simple glScale ne va pas que changer sa taille, essayez par vous même):

    glPushMatrix();
    glScalef(10f, 1f, 1f);
    Renderer.drawOpaqueSprite(resolution);
    glPopMatrix();
    

    Avant:

    Après:

    Attention! Comme avec glRotate, cette commande affecte les suivantes: si vous multipliez par 0.5 sur l'axe X, votre prochaine translation sur cet axe sera elle aussi multipliée par 0.5!
    De plus, comme avec glRotate, l'ordre importe!
    Un exemple pour comparer:

    glPushMatrix();
    glTranslatef(100f, 0f, 0f);
    glScalef(2f, 1f, 1f); // On applique l'homothétie **après** la translation
    Renderer.drawOpaqueSprite(resolution);
    glPopMatrix();
    

    glPushMatrix();
    glScalef(2f, 1f, 1f); // On applique l'homothéthie **avant** la translation
    glTranslatef(100f, 0f, 0f);
    Renderer.drawOpaqueSprite(resolution);
    glPopMatrix();
    

    Il est aussi possible d'inverser des modèles et des images grâce à cette commande! Il suffit de donner une valeur négative dans l'un des paramètres:

            GlStateManager.disableCull(); // necessaire pour que ce rendu ('draw opaque sprite') fonctionne, plus d'infos plus loin
            glPushMatrix();
    
            // l'homothétie se fait depuis le point (0,0) et on décale l'image pour qu'elle apparaisse à l'écran
            // N'hésitez pas à mettre 10f sur l'axe Y pour que mieux comprendre
            glTranslatef(100f, 128f/resolution.getScaleFactor(), 0f);
            glScalef(1f, -1f, 1f); // On applique l'homothéthie **avant** la translation
            Renderer.drawOpaqueSprite(resolution);
            glPopMatrix();
            GlStateManager.enableCull(); // necessaire pour que ce rendu ('draw opaque sprite') fonctionne, plus d'infos plus loin
    
    

    glEnable: activer vos capacités!

    Propriétés à noter:

    • Change l'état d'OpenGL, pensez toujours à annuler des changements après votre rendu!
    • Vous pouvez retrouver une version pour une bonne partie des capacités dans GlStateManager.
    • Parfois remplaçable par GlStateManager::enable<capacité> ou GlStateManager::disable<capacité>

    D'inverse glDisable, la commande glEnable prend un unique paramètre: la capacité à activer (ou à desactiver).
    La liste de toutes les capacités possible est assez longue et il n'y aurait pas d'intérêt à tout expliquer ici.
    Vous pouvez cliquer sur ce lien si vous voulez vraiment la liste complète.

    En voici quelques unes qui pourraient vous être utile:

    • GL_SCISSOR_TEST: Permet de sélectionner la zone de dessin, une partie de ce tutoriel y est entièrement dédié.
    • GL_DEPTH_TEST: Permet d'activer le test de profondeur: les faces les plus proches de la caméra cachent celle de derrière
    • GL_CULL_FACE: Permet de n'afficher des polygones que dans un seul sens (voir section 'VertexBuffer')
    • GL_BLEND: Permet d'activer le mélange des couleurs et la transparence

    Points avancés

    Avant de s'attaquer à cette partie, je vous conseille d'avoir bien compris les parties précédentes et d'avoir acquis un peu d’aisance avec OpenGL.

    Utiliser plusieurs textures

    Cliquez pour avoir la branche de cette partie.

    Pour utiliser une texture, on doit l'attacher avec glBindTexture(GL_TEXTURE_2D, <votre n° de texture>). Cependant, Minecraft propose un moyen plus simple d'attacher une texture avec des ResourceLocation:

    mc.getTextureManager().bindTexture(<la ressource>);
    

    mc est une instance de Minecraft (Minecraft.getMinecraft()) par exemple.

    Pour utiliser plusieurs textures pour vos rendus, il faut grouper les éléments utilisant la même texture et utiliser la texture pour ce groupe.

    Exemple avec deux textures:

        private static final ResourceLocation opaqueLogo = new ResourceLocation(OpenGLPratique.MODID, "logo_mff128x128.png");
        private static final ResourceLocation transparentLogo = new ResourceLocation(OpenGLPratique.MODID, "transparentlogo_mff128x128.png");
    
        /**
            * C'est la méthode que l'on va modifier dans ce tutoriel
            */
        private void draw(ScaledResolution resolution) {
            glPushMatrix();
            Minecraft.getMinecraft().getTextureManager().bindTexture(transparentLogo); // on attache la 1ère texture
            drawRect(128, 128, resolution); // on dessine un 1er rectangle avec la texture transparente
    
            glTranslatef(100f, 100f, 0f);
            Minecraft.getMinecraft().getTextureManager().bindTexture(opaqueLogo); // on attache la 2ème texture
            drawRect(128, 128, resolution); // on dessine un 2nd rectangle avec la texture opaque
            glPopMatrix();
        }
    
        // Ne faites pas trop attention à ça, sert à dessiner un rectangle à l'écran; cette méthode sera expliquée plus tard dans le tutoriel
        private void drawRect(int width, int height, ScaledResolution resolution) {
            Tessellator tessellator = Tessellator.getInstance();
            VertexBuffer buffer = tessellator.getBuffer();
            buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX);
            float w = width / ((float)resolution.getScaleFactor());
            float h = height / ((float)resolution.getScaleFactor());
            buffer.pos(0,0,0).tex(0, 0).endVertex();
            buffer.pos(0,h,0).tex(0, 1f).endVertex();
            buffer.pos(w, h,0).tex(1f, 1f).endVertex();
            buffer.pos(w,0,0).tex(1f, 0).endVertex();
            tessellator.draw();
        }
    
    

    Résultat:

    Le scissor test: sélectionner une zone de rendu

    Cliquez pour avoir la branche de cette partie.
    Points importants:

    • Ce système n'est pas affectée par les transformations.
    • Les coordonnées données en paramètres sont des coordonnées en pixels correspondant à un rectangle sur l'écran.
    • Pas de remplacement dans GlStateManager.

    Vous voulez afficher votre contenu sur l'écran mais le garder dans un rectangle précis sur l'écran ?
    Le scissor test vous permet de faire exactement ceci.
    Il s'utilise ainsi:

            glEnable(GL_SCISSOR_TEST);
            glScissor(<x>,<y>, <largeur>,<hauteur>); // attention! Pour l'axe Y, le 0 est en **bas** de l'écran!
            // rendu
            glDisable(GL_SCISSOR_TEST);
    
    

    Un petit schéma:

            glEnable(GL_SCISSOR_TEST);
            glScissor(32,Minecraft.getMinecraft().displayHeight-128, 64,64); // attention! Pour l'axe Y, le 0 est en **bas** de l'écran!
            Renderer.drawOpaqueSprite(resolution);
            glDisable(GL_SCISSOR_TEST);
    
    

    Résultat:

    Bonus

    Quelques informations bonus pour approfondir vos connaissances.

    Le Vertex Buffer: prenez le pouvoir!

    Cliquez pour avoir la branche de cette partie.

    Précédemment dans ce tutoriel, on se basait soit sur la classe magique Renderer ou Gui pour le rendu; et si on choisissait nous-même comment on dessine notre contenu à l'écran?

    Dites bonjour au VertexBuffer!

    ❗ Ça en fait des membres !

    Effectivement, et il est assez facile de s'y perdre. Ce tutoriel vous permettra d'apprendre à utiliser certaines des méthodes de cette classe pour afficher du contenu.

    Accéder à une instance
    Pour utiliser le VertexBuffer, il nous faut une instance, je vous recommande de le faire ainsi:

            Tessellator tessellator = Tessellator.getInstance();
            VertexBuffer buffer = tessellator.getBuffer();
    

    Ordre des sommets
    Faites bien attention à définir vos sommets dans le sens inverse des aiguilles d'une montre, surtout lorsque vous faites un rendu 2D. En effet, Minecraft active par défaut le 'culling' et fais en sorte que seules les faces apparaissant dans le sens antihoraire, par rapport à la caméra, soient dessinées.
    Vous pouvez désactiver ce comportement ainsi, même si je vous le déconseille:

    glDisable(GL_CULL_FACE);
    
    begin

    Cette méthode prend deux paramètres:

    • Le mode de dessin: GL_QUADS, GL_TRIANGLES le plus généralement
    • Le format des sommets à spécifier, vous pouvez trouver ceux par défaut dans DefaultVertexFormats

    Cette méthode est à mettre obligatoirement avant chaque début de rendu!
    Le mode de dessin permet de choisir le contenu: des quads, des triangles, etc.
    Le format des sommets permet de choisir ce qui compose un sommet. Avec les différents formats disponibles dans DefaultVertexFormats, vous pouvez définir la position, les coordonnées de texture (optionnelles), la couleur du sommet (optionnelle), et un vecteur normal (optionnel).
    Petite note sur la couleur: si vous donnez une couleur pour définir vos sommets, glColor n'a plus aucun effet!

    Tessellator::draw()

    Cette méthode permet de terminer votre rendu (je vous conseille vivement de ne pas utiliser VertexBuffer::finishDrawing() si vous ne savez pas ce que vous faites).
    Exemple:

    tessellator.draw();
    

    Attention l'ordre d'utilisation des méthodes suivantes doit absolument suivre celui défini dans votre format!
    Par exemple, avec le format POSITION_TEX_COLOR, l'ordre à suivre est: pos, tex, color!

    pos

    Cette méthode prend une position (X,Y,Z) qui indique la position de votre sommet.

    tex

    Cette méthode prend une position (U,V) qui indique la coordonnée de texture:
    Un triangle utilisant les coordonnées (0;0), (0.5;0), (0.5;0.5) utilisera la partie surlignée en rose dans cet exemple:

    color

    Vous permet de definir la couleur de votre sommet, soit en RGBA avec des flottants, ou RGBA avec des entiers compris entre 0(rien) et 255(max).

    normal

    Permet de définir la normale de votre sommet.

    endVertex

    Finit la définition de votre sommet. Doit impérativement figurer à la fin de la définition d'un sommet.

    Exemple: dessiner le logo du forum
            // on récupère le buffer
            Tessellator tessellator = Tessellator.getInstance();
            VertexBuffer buffer = tessellator.getBuffer();
    
            // on commence à dessiner des quadrilatères où on donne la position et les coordonnées de textures
            buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX);
            float w = WIDTH / ((float)resolution.getScaleFactor());
            float h = HEIGHT / ((float)resolution.getScaleFactor());
    
            // on ajoute un sommet à (0,0) avec la coordonnée (0,0) sur la texture
            buffer.pos(0,0,0).tex(0, 0).endVertex();
            buffer.pos(0,h,0).tex(0, 1f).endVertex();
            buffer.pos(w, h,0).tex(1f, 1f).endVertex();
            buffer.pos(w,0,0).tex(1f, 0).endVertex();
    
            // on dessine le contenu
            tessellator.draw();
    
    

    (on suppose que 'WIDTH'=largeur du logo, 'HEIGHT'=hauteur du logo, 'opaqueLogo'=Localisation du logo)

    Le résultat, vous le connaissez:

    Un autre exemple avec l'attribut 'COLOR':

            // on commence à dessiner des quadrilatères où on donne la position, les coordonnées de textures et la couleur
            buffer.begin(GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
            float w = WIDTH / ((float)resolution.getScaleFactor());
            float h = HEIGHT / ((float)resolution.getScaleFactor());
    
            // on demande de ne garder que le vert de la couleur
            buffer.pos(0,0,0).tex(0, 0).color(0f, 1f, 0f, 1f).endVertex();
            buffer.pos(0,h,0).tex(0, 1f).color(0f, 1f, 0f, 1f).endVertex();
            buffer.pos(w, h,0).tex(1f, 1f).color(0f, 1f, 0f, 1f).endVertex();
            buffer.pos(w,0,0).tex(1f, 0).color(0f, 1f, 0f, 1f).endVertex();
    
            // on demande de ne garder que le rouge de la texture mais n'a aucun effet!
            glColor4f(1f, 0f, 0f, 1f);
            // on dessine le contenu
            tessellator.draw();
    
            glColor4f(1f, 1f, 1f, 1f);
    
    

    Le stencil buffer: sélectionner une zone de rendu, mais en mieux

    Cliquez pour avoir la branche de cette partie.
    Points importants:

    • Ce système peut être affecté par les transformations.
    • Les utilisations du stencil buffer sont assez variées et ce tutoriel ne donnera qu'un exemple de ce qui est possible. Je vous invite à regarder plus en détails si cela vous intéresse et voici quelques liens: Open.gl: Depth Stencils et Wiki de Khronos sur le Stencil Test
    • Pas de remplacement dans GlStateManager.

    Une utilisation possible du stencil buffer est analogue à celle du scissor test: choisir une zone de rendu. Cependant, le stencil buffer permet de choisir la forme de la zone et ne vous oblige plus à utiliser des coordonnées en pixels.
    Pour cet exemple, nous allons dessiner le logo du forum avec la forme du modèle du joueur.

            // Etape importante, permet de vérifier si le stencil buffer est activé et l'activer si ce n'est le cas
            Framebuffer framebuffer = Minecraft.getMinecraft().getFramebuffer();
            if( ! framebuffer.isStencilEnabled()) {
                framebuffer.enableStencil();
            }
    
            glEnable(GL_STENCIL_TEST);
    
            glStencilFunc(GL_ALWAYS, 1, 0xFF); // Tous les pixels affichés vont avoir la valeur 1 dans le stencil buffer
            glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
            glStencilMask(0xFF); // On va écrire dans le stencil buffer
            glDepthMask(false); // On n'écrit plus dans le depth buffer
            glColorMask(false, false, false, false); // on désactive le rendu des couleurs
            glClear(GL_STENCIL_BUFFER_BIT); // On vide le buffer
    
            // rendu du modèle
            glPushMatrix();
            glTranslatef(-80f, -40f, 0f); // on déplace le modèle en haut à gauche
            Renderer.drawModel(resolution); // On dessine le modèle du joueur
            glPopMatrix();
    
            // rendu du logo
            // On ne dessine les pixels que si le pixel que l'on va dessiner est à une position où le stencil buffer a une valeur égale (GL_EQUAL) à 1
            glStencilFunc(GL_EQUAL, 1, 0xFF);
    
            glStencilMask(0x00); // On n'écrit plus rien au stencil buffer
            glDepthMask(true); // On réactive l'écriture vers le depth buffer
            glColorMask(true, true, true, true); // On réactive le rendu des couleurs
    
            glPushMatrix();
            glScalef(1.5f, 1.5f, 1f); // on agrandit un peu le logo pour que le modèle rentre entièremen dedans
            Renderer.drawOpaqueSprite(resolution); // On dessine notre logo
            glPopMatrix();
    
            glDisable(GL_STENCIL_TEST);
    
    

    Les explications sont dans les commentaires du code mais si vous voulez plus de détails, voici une liste d'explications des différentes fonctions utilisées:

    Résultat:

    Conclusion

    Voilà, ce tutoriel est fini, j'espère qu'il vous a plu, et n'hésitez pas à poser des questions et à proposer des améliorations!

    Crédits

    Rédaction :
    jglrxavpok

    Correction :
    Folgansky


    Ce tutoriel de Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International



  • Très bon tuto ! Pas facile à prendre en main mais quand on comprend la logique ça va tout seul.



  • Salut,
    Avec un minimum de recherche, on se rend vite compte que la classe Tessellator permet de simplifier la tâche.



  • Ce message a été supprimé !