PGE (Plegat Graphic Engine) - Introduction

Article d'introduction sur le développement de PGE (Plegat Graphic Engine), moteur graphique basé sur JOGL (Java/OpenGL).

8 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1. Introduction

1-A. Présentation

Cet article inaugure toute une série sur la création d'un moteur graphique pour mon application de manipulation de modèles éléments finis.

Le but n'est pas de créer un moteur de jeu multiplateforme, aux capacités graphiques avancées utilisant les dernières technologies, mais de mettre en place un moteur graphique simple.

Ceci permettra d'aborder la programmation OpenGL en Java au travers de l'utilisation de l'API JOGL. Je n'aborderai donc pas explicitement la théorie concernant OpenGL, étant donné que le net regorge d'informations et de tutoriels, sauf ponctuellement si nécessaire. Les articles seront donc essentiellement orientés programmation Java et JOGL.

1-B. But

Plegat (en plus d'être mon pseudo) est le nom du logiciel que je développe. Plegat est un pré/postprocesseur pour logiciels éléments finis. Il est codé en Java, pour des raisons de facilité de portabilité, et il utilise OpenGL pour la partie affichage graphique.

Cela fait maintenant quelques années que je travaille sur son développement, et le moteur graphique n'avait jamais été repensé depuis les premières versions. La version actuelle n'est pas assez évolutive pour correspondre à mes besoins, et devait donc être recodée. Cette réécriture du moteur servira donc de support à cette série de tutoriels.

Le but final est donc d'obtenir :

  • un moteur graphique 3D évolutif ;
  • permettant trois types d'affichage : fil de fer, solide et texturé ;
  • contrôlable via le clavier et la souris ;
  • avec une gestion précise des éléments graphiques à afficher ;
  • possédant plusieurs modes de sélection à l'écran (picking, par zone...) ;
  • éventuellement extensible par un système de plugins.

Afin d'avoir une idée du rendu graphique souhaité, on pourra jeter un œil aux copies d'écran des logiciels suivants :

ainsi qu'aux vidéos de démonstration suivantes :

1-C. Technologies employées

Pour suivre ces articles, vous aurez besoin :

  • d'un JDK Java SE. J'utilise personnellement le JDK Java 6, pour des soucis de portabilité personnels, mais toute version supérieure du JDK pourra être utilisée sans problème. Vous pouvez télécharger le JDK sur le site d'OracleTéléchargement Java SE Development Kit (JDK) ;
  • de l'API OpenGL JOGL. Celle-ci est disponible sur le site JogAmpSite Jogamp, qui maintient et développe le binding OpenGL JOGL. Vous trouverez également sur ce site les API JOCL (binding OpenCL) et JOAL (binding OpenAL). La version actuelle de JOGL est la 2.0, mais encore en RC (Release Candidate) ;
  • de votre EDI préféré. Pour ma part, ce sera NetBeansSite Netbeans.

1-D. Installation de JogAmp

Vous pouvez télécharger une archive incluant tous les fichiers composant l'API JogAmp sur cette pagetéléchargement archive Jogamp. Choisissez l'archive correspondant à votre système d'exploitation (Win, Mac, Linux) et à votre architecture (32/64 bits). Les archives sont au format 7-zip.

Après avoir décompressé l'archive, les deux répertoires nécessaires pour la suite sont les répertoires "jar" et "lib". Le répertoire "jar" contient toutes les bibliothèques jar qui seront utilisées pour le développement de nos programmes. Le répertoire "lib" contient quant à lui les bibliothèques natives (*.dll sous Windows, *.so sous Linux) qui seront utilisées lors de l'exécution des programmes.

Il vous faut ensuite configurer votre EDI afin de pointer correctement sur ces deux répertoires. Pour cela, je vous renvoie vers la page du Wiki JogAmpwiki Jogamp - Configuration IDE qui présente le processus de configuration pour les EDI suivants : Eclipse, IntelliJ IDEA, et NetBeans.

2. Mon premier cube

2-A. Tout est relatif...

... il faut donc bien définir l'espace dans lequel nous allons travailler.
Il y a quatre choses très importantes à déterminer pour voir quelque chose à l'écran :

  • l'endroit où sont nos éléments à dessiner ;
  • l'endroit où l'on est ;
  • l'endroit où l'on regarde ;
  • où se trouve le haut.

Les éléments à dessiner seront pour le moment très simples. Nous commencerons par un cube unitaire (c'est-à-dire dont les côtés mesurent une unité), je vous fais grâce du triangle, un peu trop... basique ! Nous le placerons à l'origine de notre "univers" pour simplifier.

L'endroit où l'on est, celui où l'on regarde et le haut de la scène seront fixés dans ce premier article (ultérieurement, ils seront gérés par une caméra).

On définit tout d'abord une vision en perspective de notre scène grâce à l'instruction gluPerspective :

 
Sélectionnez

public void gluPerspective(double fovy,
                           double aspect,
                           double zNear,
                           double zFar)

fovy sera pris égal à 45°. aspect sera défini par le ratio entre la largeur de la fenêtre JOGL et sa hauteur. zNear sera égal à 0.1, et zFar à 100 (ces valeurs seront optimisées pour les prochains articles).

L'instruction JOGL permettant de définir la caméra (où l'on est, où l'on regarde, où est le haut) est gluLookAt :

 
Sélectionnez

public void gluLookAt(double eyeX,
                      double eyeY,
                      double eyeZ,
                      double centerX,
                      double centerY,
                      double centerZ,
                      double upX,
                      double upY,
                      double upZ)

Dans ce premier article, la caméra sera fixe. Les points de définition sont donnés par les paramètres suivants :

  • où l'on est : (eyeX,eyeY,eyeZ)=(3,5,2) ;
  • où l'on regarde : (centerX,centerY,centerZ)=(0,0,0) ;
  • où est le haut : (upX,upY,upZ)=(0,0,1).

Voilà. Notre environnement est défini, il ne reste plus qu'à y dessiner...

2-B. Le repère principal

Une des choses que j'apprécie, c'est de savoir où je suis... nous allons donc dessiner un repère à l'origine de notre univers. Ce repère sera composé de trois lignes de couleurs différentes, afin de repérer quels sont les différents axes :

  • l'axe X sera en rouge ;
  • l'axe Y sera en vert ;
  • l'axe Z sera en bleu.

Rien de compliqué pour le dessin, nous utiliserons les instructions glColor3d pour définir la couleur du trait, les traits étant tracés en mode GL_LINES.
Le dessin du repère principal sera affecté à une classe dédiée :

Classe PGEGlobalCoord
Sélectionnez

package pge_article_1;

import javax.media.opengl.GL2;

public class PGEGlobalCoord {

    public void draw(GL2 gl) {
    
        gl.glBegin(GL2.GL_LINES);
        
        // rouge : axe X
        gl.glColor3d(1, 0, 0);
        gl.glVertex3d(0, 0, 0);
        gl.glVertex3d(1, 0, 0);
        
        // vert :  axe Y
        gl.glColor3d(0, 1, 0);
        gl.glVertex3d(0, 0, 0);
        gl.glVertex3d(0, 1, 0);
        
        // bleu :  axe Z
        gl.glColor3d(0, 0, 255);
        gl.glVertex3d(0, 0, 0);
        gl.glVertex3d(0, 0, 1);
        
        gl.glEnd();
    }

}

2-C. Un cube à six faces

Pour le dessin du cube unitaire, une classe dédiée est également écrite. Le constructeur prend en paramètre la position dans l'espace du centre du cube, et une méthode draw permet de gérer l'affichage du cube. On y dessine les faces inférieures et supérieures du cube en mode GL_LINE_LOOP, puis les quatre arêtes joignant les deux faces en GL_LINES.
Pour la couleur, vous aurez droit à un joli cyan...

Classe PGEUnitCube
Sélectionnez

package pge_article_1;

import javax.media.opengl.GL2;

public class PGEUnitCube {

    public PGEUnitCube(double x,double y, double z) {
        this.x=x;
        this.y=y;
        this.z=z;
    }

    public void draw(GL2 gl) {
        
        // dessin face inférieure
        gl.glBegin(GL2.GL_LINE_LOOP);
        gl.glColor3d(0, 1, 1);
        gl.glVertex3d(this.x - 0.5, this.y - 0.5, this.z - 0.5);
        gl.glVertex3d(this.x + 0.5, this.y - 0.5, this.z - 0.5);
        gl.glVertex3d(this.x + 0.5, this.y + 0.5, this.z - 0.5);
        gl.glVertex3d(this.x - 0.5, this.y + 0.5, this.z - 0.5);
        gl.glEnd();

        // dessin face supérieure
        gl.glBegin(GL2.GL_LINE_LOOP);
        gl.glVertex3d(this.x - 0.5, this.y - 0.5, this.z + 0.5);
        gl.glVertex3d(this.x + 0.5, this.y - 0.5, this.z + 0.5);
        gl.glVertex3d(this.x + 0.5, this.y + 0.5, this.z + 0.5);
        gl.glVertex3d(this.x - 0.5, this.y + 0.5, this.z + 0.5);
        gl.glEnd();

        // dessin des quatre arêtes manquantes
        gl.glBegin(GL2.GL_LINES);
        gl.glVertex3d(this.x - 0.5, this.y - 0.5, this.z - 0.5);
        gl.glVertex3d(this.x - 0.5, this.y - 0.5, this.z + 0.5);
        gl.glEnd();

        gl.glBegin(GL2.GL_LINES);
        gl.glVertex3d(this.x + 0.5, this.y - 0.5, this.z - 0.5);
        gl.glVertex3d(this.x + 0.5, this.y - 0.5, this.z + 0.5);
        gl.glEnd();

        gl.glBegin(GL2.GL_LINES);
        gl.glVertex3d(this.x - 0.5, this.y + 0.5, this.z - 0.5);
        gl.glVertex3d(this.x - 0.5, this.y + 0.5, this.z + 0.5);
        gl.glEnd();

        gl.glBegin(GL2.GL_LINES);
        gl.glVertex3d(this.x + 0.5, this.y + 0.5, this.z - 0.5);
        gl.glVertex3d(this.x + 0.5, this.y + 0.5, this.z + 0.5);
        gl.glEnd();
    }

    private double x,y,z; // position du centre du cube
   
}

2-D. La fenêtre JOGL

Maintenant, nous avons tout pour dessiner notre cube... sauf une fenêtre JOGL. Nous allons créer une classe, que je nommerai PgePanel, dérivant de GLJPanel. Cette classe mère est choisie plutôt que GLCanvas car elle est plus adaptée à une utilisation en environnement Swing (GLCanvas étant plus adaptée pour AWT).

La trame de la classe PgePanel est la suivante :

Trame de la classe PGEPanel
Sélectionnez

package pge_article_1;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLJPanel;
import javax.media.opengl.glu.GLU;

public class PGEPanel_article_1 extends GLJPanel implements GLEventListener {

    public PGEPanel_article_1() {
        super();
        this.initComponents();
    }

    private void initComponents() {
        // initialisation des composants de PgePanel
    }

    public void display(GLAutoDrawable arg0) {
        // affichage de la scène
    }

    public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
        // mise à jour suite à redimensionnement de la fenêtre
    }

    public void dispose(GLAutoDrawable glad) {
        // libération des ressources OpenGL
    }

    public void init(GLAutoDrawable glad) {
        // initialisation OpenGL
    }

    private int winAW;      // largeur de la zone d'affichage
    private int winAH;      // hauteur de la zone d'affichage

}

On se contentera d'ajouter un écouteur JOGL dans la méthode initComponents(), ainsi que la création des deux objets PGEGlobalCoord et PGEUnitCube :

 
Sélectionnez

private void initComponents() {
    this.addGLEventListener(this);
    
    this.cube=new PGEUnitCube(0, 0, 0);
    this.coord=new PGEGlobalCoord();
}

La méthode reshape() redéfinit les valeurs de la largeur et de la hauteur de la fenêtre JOGL :

 
Sélectionnez

public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {
    System.out.println("panel reshape");

    // réinitialisation des dimensions de la zone d'affichage
    this.winAW = this.getWidth();
    this.winAH = this.getHeight();
}

Les méthodes dispose() et init() ne seront pas implémentées ici :

 
Sélectionnez

public void dispose(GLAutoDrawable glad) {
    System.out.println("running "+this.getClass().getName()+" dispose method");
}

public void init(GLAutoDrawable glad) {
    System.out.println("running "+this.getClass().getName()+" init method");
}

La méthode display() se chargera de l'affichage de la scène JOGL :

 
Sélectionnez

public void display(GLAutoDrawable arg0) {

    GL2 gl = arg0.getGL().getGL2();
    GLU glu = new GLU();
    
    gl.glViewport(0, 0, this.winAW, this.winAH);
    
    // initialisation de la matrice de projection
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    
    // définition de la matrice de projection
    // vue en perspective, angle 45°, ratio largeur/hauteur, plan de clipping "near" et "far"
    glu.gluPerspective(45, (double) this.winAW / (double) this.winAH, 0.1, 100.0);
    
    // position de la caméra : point (3,5,2)
    // point de visée: point (0,0,0)
    // vecteur "up": point (0,0,1)
    glu.gluLookAt(3, 5, 2, 0, 0, 0, 0, 0, 1);

    // initialisation de la matrice modèle
    // couleur de fond: noir 
    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glClear(GL2.GL_COLOR_BUFFER_BIT);

    // dessin des axes du repère global
    this.cube.draw(gl);

    // dessine un cube unitaire à l'origine du repère global
    this.coord.draw(gl);
     
}

2-E. Premier affichage de PGE

Il ne nous manque que la "main class" pour pouvoir tester notre fenêtre JOGL :

 
Sélectionnez

package pge_article_1;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.EventListener;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLProfile;
import javax.swing.JFrame;

/**
 * Article1.java
 * 
 * Code source du premier article.
 *
 */
public class Main_article_1 implements EventListener {

    public static void main(String[] args) {

        // Initialisation JOGL
        GLProfile glp=GLProfile.get(GLProfile.GL2); // profil Opengl Desktop 1.x à 3.0
        GLProfile.initSingleton(true);
        GLCapabilities caps=new GLCapabilities(glp);
        
        final JFrame frame = new JFrame("Article 1");
        PGEPanel_article_1 pane = new PGEPanel_article_1(caps);
        frame.getContentPane().add(pane);

        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.out.println("closing main window...");
                System.exit(0);
            }
        });

        frame.setSize(300, 300);
        
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                frame.setVisible(true);
            }
        });
    }
}

Il ne reste plus qu'à lancer l'exécution du programme pour obtenir notre premier cube sous PGE !

Image non disponible
Notre premier cube !!!

3. Conclusion

Ce premier article a permis de présenter PGE, et de commencer l'implémentation du moteur graphique par l'affichage d'une scène simple.
Dans le prochain article, nous mettrons en place un système de caméra mobile, ainsi qu'une gestion du clavier et de la souris.

Les sources de cet article sont disponibles en suivant ce lien : src_PGE_article_1.zipTBD.

4. Remerciements

Merci à Ricky81 et raptor70 pour leurs commentaires, à ClaudeLELOUP et _Max_ pour leur relecture orthographique, ainsi qu'à djibril pour son aide sur le KitOOoDVP.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 JM BORLOT. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.