1. Avant-Propos

1-1. Pourquoi cette série d'article ?

Beaucoup d'entre nous jouent aux jeux vidéo, c'est un fait, mais beaucoup aussi souhaiteraient aller plus loin en prenant le rôle de créateur. Nombreux sont ceux qui souhaitent se lancer dans le développement de jeux ou d'applications 3D. Cependant la tâche est loin d'être simple, car il s'agit d'un des domaines les plus complexes de la programmation. Il existe de nombreux sites traitant le sujet avec plus ou moins de réussite, mais les ressources pour nous, francophones, sont bien plus rares. Il est difficile de trouver des articles sur l'apprentissage de l'OpenGL en français et il d'autant plus dur d'en trouver utilisant le langage Java.
Le niveau de difficulté des articles sera progressif. Chaque article traitera d'un point précis (ou d'un ensemble de points liés). J'esssaierai de suivre un ordre logique dans les thèmes abordés. C'est pourquoi il est conseillé de lire les articles dans l'ordre de parution si vous découvrez ou si vous débutez avec OpenGL. Il est donc normal que lors des premiers articles, les résultats ne soient pas à la hauteur de ce que vous pourriez ésperer.
Non, vous ne developperez pas un clône de Doom3 cette semaine :), cependant ce jeu est l'exemple parfait pour montrer ce qui est réalisable avec de l'OpenGL et de bonnes connaissances de maths, mais bon on peut toujours rêver ;).
À propos des maths, ce sera justement le moment pour vous de ressortir vos cours. J'éspère que les mathématiques ne vous font pas mal au coeur car si vous souhaitez développer des jeux ayant un minimum de qualité, vous retrouverez beaucoup de formules, de calculs, de géométrie spatiale...
Une connaissance relativement correcte du Java est tout de même indispensable pour suivre correctement les articles.

1-2. OpenGL

Pour ceux qui ne se sont pas arrêtés à mes dernières lignes, OpenGL est une API de rendu graphique, utilisée pour faire de la 2D et de la 3D. Ce qui rend son utilisation intéressante est qu'elle se sert de la carte graphique pour effectuer ses calculs. D'autre part, il s'agit d'une API disponible sur de nombreux systèmes, ce qui en fait donc un choix parfait pour le développement d'application portable.
Pour une description plus détaillée, reportez-vous à la FAQ OpenGL ou encore à mon autre article sur les API 3D

1-3. Pourquoi Java ?

Malgré tous les préjugés qui l'entoure, Java est une solution tout à fait viable pour le développement d'applications et de jeux 3D. D'une part, grâce au Java et à des IDE des Eclipse, on obtient une très grande productivité de développement. D'autre part, et je pense que c'est le plus important, l'utilisation de Java et d'OpenGL permet de développer des applications 3D multiplateformes (sans nécessiter de recompilation).
Cependant, OpenGL reste de l'OpenGL, il est donc possible d'utiliser les connaissances acquises dans ces articles avec n'importe quel langage permettant d'utiliser OpenGL, avec très peu voir aucune modification de code. Les principales différences se situent au niveau de la gestion de l'affichage (fenêtrage) et du passage de donnée (textures, VBO...). En Java, on utilisera principalement les NIO avec leur différentes classes de Buffer.
De plus j'ai aussi dû faire un choix au niveau du binding OpenGL utilisé (http://info-rital.developpez.com/tutoriel/java/api/3d/). Ayant le choix entre LWJGL et le JSR-231 (anciennement JOGL), j'ai finalement opté pour le second. Non pas que j'ai une préfèrence particulière ou même que l'un soit plus performant que l'autre, mais que je devais faire un choix, je me suis basé sur des critères pas forcement très représentatifs. Si j'ai choisis le JSR-231, c'est parce qu'il s'agit du binding développé par Sun eux-même. Généralement les gens font plus confiance dans ce qui est officiel. De plus, dans la prochaine version de Java (au doux nom code de Mustang), de nombreuses possibilités d'interaction entre Swing et OpenGL vous seront offertes en utilisant le JSR-231.
Quoi qu'il en soit LWJGL est simple à utiliser, et puis comme je l'ai dit plus haut, pour 99% du code, il sera le même que vous utilisiez LWJGL ou le JSR-231. Il vous suffira de regarder quelque codes sources pour effectuer les modifications adéquates. Il se peut même que je consacre un article à ça.

2. Installation

Avant de pouvoir commencer à developper, il va falloir installer le nécessaire. Je me passerai de l'installation de Java, bien que je vous conseille très fortement d'installer la version 1.5 du JDK de Sun. Faites un "java -version" dans une invite de commande ou un terminal pour connaître la version de Java installée sur votre ordinateur (si Java il y a, mais cela va de soit)
Il y a plusieurs façon d'utiliser le JSR-231. Contrairement à la plupart des extensions Java, il est complètement déconseillé d'installer directement dans votre répèrtoire d'installation de Java, les fichiers du JSR-231. Cela risquerait de poser des problèmes en cas de conflits entre versions. Les API Java ne s'entendent pas très bien quand deux versions différents sont confrontées (jalousie, tout ça...), notemment quand une application chargée par JavaWebStart propose une version différente que celle installée sur votre système.

2-1. Téléchargement

Il va d'abord vous falloir récuperer les fichiers du JSR-231. Pour cela rendez-vous sur le site https://jogl.dev.java.net/. Vous avez maintenant plusieurs choix. Soit vous téléchargez la version courante du JSR-231, soit une "nightly build". Il s'agit d'une version fraîchement compilée, non-officielle. Mais les différences sont relativements minimes. Elles le seront plus d'ici quelques mois. De plus, étant donné que le JSR-231 n'est pas encore sortie en version "stable", je vous conseille de mettre à jours plus ou moins régulièrement votre version.
Il y a au moins deux fichiers à récuperer : jogl.jar et jogl-natives-xxx-yyy.jar où xxx n'est pas le nom d'un film mais votre système d'exploitation et yyy votre architecture matérielle.
Vous pouvez aussi récuperer les sources et la documentation de l'API, mais c'est facultatifs.

2-2. Installation manuelle

Nous allons installer ces fichiers directement dans le répertoire de votre programme (ou dans un répertoire à part).
Commencez par créer un répertoire (appelons-le "lib") dans lequel nous plaçons jogl.jar et nous dézippons jogl-natives-xxx-yyy.jar. Ce dernier contient la partie native, propre à chaque plateforme, alors que jogl.jar n'est que l'interface (au sens programmation), qui fait le pont entre l'application Java et les binaires natifs.
Pour lancez votre application Java, vous devrez utiliser la commande suivante :
java -classpath lib/jogl.jar:. -Djava.library.path=lib/ VotreAppli "lib/" et "VotreAppli" sont bien sûr à remplacer par le bon chemin vers vos fichiers du JSR-231 et par le nom de votre classe d'entré de votre application Java.

2-3. JavaWebStart

Pour utiliser le JSR-231 dans vos applications distribuées par JavaWebStart, il suffit de placer dans le descripteur JNLP, dans la section <resources> la ligne suivante :
<extension name="jogl" href="http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp" />

2-4. Eclipse

Pour utiliser le JSR-231 dans votre projet avec Eclipse, allez dans le Java Build Path, ajoutez en tant que Jar externe, jogl.jar. Puis allez dans les propriétés de ce dernier, puis Native Library et joignez le second jar (non décompressé cette fois-ci).

2-5. Note pour Windows

Sous Windows, il peut y avoir des conflits entre DirectDraw et OpenGL. C'est pourquoi il est conseillé de lancer votre application en ajoutant : -Dsun.java2d.noddraw=true
Sous JavaWebStart, il faut ajouter, dans la section resources :
<property name="sun.java2d.noddraw" value="true"/>
Il existe aussi un bug concernant les drivers ATI (plutôt flou), mais si jamais vous remarquez des fuites de mémoires, ajoutez la propriété "jogl.GLContext.nofree".

3. Fonctionnement d'OpenGL

OpenGL dispose de nombreuses fonctionnalités pour effectuer un grand nombre d'actions comme la gestion de :

  • la caméra
  • la rotation 3D des objets
  • le remplissage des faces
  • les textures
  • la lumière

Et bien d'autres choses encore...
Mais nous ne feront pas de 3D pour le moment car de nombreuses connaissances préalables vous seront nécessaires.

3-1. Tout est une question de contexte...

Nous avons vu que OpenGL permet de faire des choses merveilleuses. Cependant, pour pouvoir gérer tout cela, il nous faut une surface dessinables et un "contexte" dans lequel OpenGL puisse pleinement s'épanouïr. C'est là qu'intervient Java.
Il existe plusieurs types de contextes, je me contenterai du plus courrant, qui est celui que l'on utilise dans l'énorme majorité des cas.
Il va nous falloir créer une fenêtre (Frame) et un composant de type GLCanvas pour pouvoir dessiner dessus.

3-2. GLEventListener

Pour réagir avec OpenGL, il va falloir implémenter la classe GLEventListener. Il s'agit d'un méchanisme de callback qui va permettre d'utiliser OpenGL avec notre surface de dessin.
Cette interface dispose de 4 méthodes qui sont :

  • init()
  • display()
  • reshape()
  • displayChanged()

init() : cette méthode est appelée une fois. Elle contiendra toute la partie initialisation. display() : cette méthode sera appelée en boucle. Elle contiendra la partie affichage. reshape() : cette méthode est appelée lors du redimensionnement de la fenêtre. Elle permettra de réajuster certaines valeurs pour ne pas avoir un affichage diforme.

3-3. Y a de l'animation

Pour pouvoir appeler en boucle la méthode display(), qui se chargera de créer la nouvelle image qui sera affichée sur l'écran, nous pourrions utiliser une boucle (for, while...).
Cependant les développeurs du JSR-231 ont pensé à nous (ils sont gentils :)) et nous ont développé la classe Animator qui se chargera d'appeler la méthode display(). Elle créé un thread à part dans lequel sont effectués les appels à display(). De plus elle se charge de faire une petite pause après chaque redessinage de l'image pour permettre au CPU de souffler un peu et de s'occuper des autres threads voir même des autres programmes. Il est cependant possible de supprimer cette pause avec la méthode setRunAsFastAsPossible(true);
Cette classe dispose de deux méthodes principales : start(); et stop(); que nous utiliserons pour lancer et arrêter l'animation.
Il existe une autre classe, qui hérite de Animator. Il s'agit de FPSAnimator. Cette classe permet de fixer un FPS (frames-per-second, le nombre d'images par seconde) pour éviter d'utiliser tout le CPU. Mais bon il ne s'agit que d'une estimation que l'on donne et la valeur ne sera pas garantie.

3-4. En avant le code

Voici le code de cet article. Nous le réutiliserons et nous l'élargirons au fur-et-à-mesure dans les articles qui suiveront.
Le code est volontairement très commenté, même sur des parties qui peuvent sembler très simples pour certains. Le code source constitue une partie de l'article, il est important que chaque ligne soit bien comprise.

Article1.java
Sélectionnez

package developpez.opengl;

import java.awt.*;
import java.awt.event.*;

import javax.media.opengl.*;
import com.sun.opengl.util.*;

/**
 * Article1.java
 * author: InfoRital
 * 
 * Code source du premier article.
 * Article1 implémente {@link GLEventListener} pour obtenir le méchanisme de callback
 *
 */

public class Article1 implements GLEventListener {

	public static void main(String[] args) {
		/**
		 * Création d'une fenêtre 
		 * Nous utilisons la classe Frame de AWT
		 * plutôt que la classe JFrame de Swing
		 * pour une question de performance
		 */
		Frame frame = new Frame("Article1");
		
		/**
		 * Création du Canvas OpenGL
		 * pour pouvoir dessiner dessus
		 */
		GLCanvas canvas = new GLCanvas();

		/**
		 * Nous attachons ensuite le méchanisme de callback
		 * à notre surface dessinable
		 */
		canvas.addGLEventListener(new Article1());
		
		/**
		 * Nous attachons maintenant notre 
		 * surface dessinable à notre fenêtre 
		 */
		frame.add(canvas);
		
		/** 
		 * Création de l'Animator
		 * comme expliqué dans l'article
		 */
		final Animator animator = new Animator(canvas);
		
		/**
		 * Le code qui suit permet de gérer la fermeture
		 * de la fenêtre par l'utilisateur
		 */
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				/**
				 * Nous créons un nouveau thread autre
				 * que la queue d'évenements AWT
				 * pour être d'arrêter l'Animator avant
				 * de quitter complétement
				 */
				new Thread(new Runnable() {
					public void run() {
						animator.stop();
						System.exit(0);
					}
				}).start();
			}
		});
		
		/**
		 * Nous donnons une taille à la fenêtre 
		 * et nous l'affichons
		 */
		frame.setSize(300, 300);
		frame.setVisible(true);
		
		/**
		 * Nous démarrons l'Animator qui
		 * va se charger de faire des appels
		 * à la méthode display();
		 */
		animator.start();
	}

	/**
	 * init() sera appelée une fois au début de l'animation
	 * C'est dans cette méthode que nous nous chargerons de toute
	 * les opérations d'initialisation
	 */
	public void init(GLAutoDrawable drawable) {
		
		/**
		 * GLEventListener renvoie un contexte (drawable)
		 * que nous allons utiliser pour instancier un objet
		 * de type GL, qui nous permettra d'utiliser
		 * les fonctions OpenGL, bien que dans cet article
		 * cela reste encore inutile
		 */
		GL gl = drawable.getGL();

		/**
		 * Cette fonction permet de désactiver
		 * la syncronisation verticale, indépendement
		 * de la plateforme utilisée
		 */
		gl.setSwapInterval(1);

	}

	/**
	 * reshape() sera appelée si la fenêtre d'affichage est redimensionnée
	 */
	public void reshape(GLAutoDrawable drawable, int x, int y, int width,
			int height) {
		GL gl = drawable.getGL();

	}
	/**
	 * display() sera appelée en boucle tout au long de l'application
	 * par la classe Animator. C'est dans cette fonction qu'on fera
	 * tout ce qui doit être affiché
	 */
	public void display(GLAutoDrawable drawable) {

		GL gl = drawable.getGL();

	}
	
	/**
	 * displayChanged() est appelée si le mode d'affichage par exemple
	 * est modifié. Cependant nous n'implémenterons pas cette méthode.
	 */
	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
			boolean deviceChanged) {
	}

}
				

4. Conclusion

Nous avons vu dans cet article comment créer une fenêtre dans laquelle il est possible de dessiner avec OpenGL.
Dans le prochain article, nous commencerons à utiliser les fonctions OpenGL pour initialiser notre scène.

4-1. Démo JavaWebStart

Le résultat de chaque article sera présenté sous forme de démonstration JavaWebStart.
Il vous suffit de cliquer sur le lien suivant pour lancer la démo (bien que celle-ci soit vraiment très basique) :
Démo JWS
Il vous faut bien sûr avoir Java (1.4 au minimum) d'installé.