TX52 : Intégration d`un moteur 3D sur PDA

Transcription

TX52 : Intégration d`un moteur 3D sur PDA
Jérôme Leducq
Emmanuel Trivis
TX52 : Intégration d’un moteur 3D sur PDA
GI05 – Automne 2007
Responsable : Mr. Stéphane Galland
Co-responsable : Mr. Olivier Lamotte
TX52 : Intégration d’un moteur 3D sur PDA
Introduction
Dans le cadre de l’UV TX52, nous avons pour rôle d’implémenter un moteur 3D sur
PDA. Ce système nous a été demandé dans le but d’être ensuite utilisé par le laboratoire du
SET. Notamment, un projet en rapport avec le véhicule intelligent du laboratoire pourrait voir
le jour et utiliser ce moteur 3D.
L’application devra répondre à certains critères comme par exemple la possibilité de
charger des modèles 3D issu de fichier 3D Studio Max, ou encore pouvoir utiliser les
périphériques du Pocket PC.
Ce projet porte sur deux domaines importants de l’informatique d’aujourd’hui.
Premièrement, la programmation embarquée, puisqu’il nous faudra nous soumettre à toutes
les contraintes et les limites que peut poser le développement sur un PDA. Le deuxième
domaine à prendre en compte est celui de la programmation graphique. Une grande partie du
projet sera donc de confronter ces deux technologies, afin d’obtenir la meilleur solution de
développement graphique sur appareil embarqué.
Outre le fait de présenter nos travaux, ce document est écrit dans l’optique d’une suite.
En effet, le projet n’étant pas terminé, nous allons ici décrire toutes les étapes de conception
mais aussi tous les problèmes de la programmation embarquée que nous avons pus rencontrer.
Nous ferons aussi un petit récapitulatif des différentes solutions pré existantes, et des
technologies utilisées. Enfin nous finirons par expliquer les points à améliorer et à continuer à
partir de notre solution.
-2-
TX52 : Intégration d’un moteur 3D sur PDA
Sommaire
1.
Description du sujet ...................................................................................................- 4 1.1. Moteur 3D..........................................................................................................- 4 1.2. PDA ...................................................................................................................- 4 2. Moteurs 3D existants..................................................................................................- 5 2.1. Solutions payantes..............................................................................................- 5 2.2. Solutions gratuites ..............................................................................................- 5 2.2.1.
API et Moteurs gratuits...............................................................................- 5 2.2.2.
Adaptation pour PDA d’un moteur existant pour PC...................................- 6 2.2.3.
Récupération de moteur ..............................................................................- 7 3. Solutions pour le développement................................................................................- 8 3.1. C++ & OpenGL ES ............................................................................................- 8 3.1.1.
OpenGL ES et GLUT ES............................................................................- 8 3.1.2.
PowerVR ....................................................................................................- 8 3.1.3.
Autres conditions........................................................................................- 9 3.2. La 3D sur Pocket PC avec Java ..........................................................................- 9 3.2.1.
JSR 184 ......................................................................................................- 9 3.2.2.
La machine virtuelle ...................................................................................- 9 3.3. Direct X pour périphériques................................................................................- 9 4. Développer sur un PDA ...........................................................................................- 10 4.1. Choix des Technologies....................................................................................- 10 4.2. L’environnement de développement pour PDA ................................................- 10 4.3. Le C++ et l’embarqué.......................................................................................- 12 4.4. Comment utiliser Vincent 3D et la GLUT ........................................................- 13 5. Nos travaux..............................................................................................................- 15 5.1. Structure d’un moteur 3D .................................................................................- 15 5.2. Benchmark .......................................................................................................- 16 5.3. Parsing d’un fichier 3D Studio Max..................................................................- 17 5.3.1.
Explication du système de Chunk .............................................................- 17 5.3.2.
Lecture du fichier 3D Studio Max.............................................................- 19 5.4. Affichage d’un objet.........................................................................................- 23 5.4.1.
Les primitives de rendues : .......................................................................- 23 5.4.2.
Les vertex array ........................................................................................- 23 5.4.3.
Les Vertex Buffers Objects.......................................................................- 25 5.5. Gestion des périphériques externes ...................................................................- 28 5.5.1.
Le périphérique stylet. ..............................................................................- 28 5.5.2.
Les événements clavier ............................................................................- 28 5.6. La navigation ...................................................................................................- 29 5.6.1.
La caméra.................................................................................................- 29 5.6.2.
L’utilisation des périphériques ..................................................................- 30 5.6.3.
La translation et le zoom...........................................................................- 30 5.6.4.
La rotation ................................................................................................- 30 6. Evolution de l’application ........................................................................................- 33 6.1. Placement des objets dans l’espace : octree, bsp ...............................................- 33 6.2. Rasteroid ..........................................................................................................- 33 -
-3-
TX52 : Intégration d’un moteur 3D sur PDA
1.
Description du sujet
1.1.
Moteur 3D
Un moteur 3D est une application informatique permettant de stocker et d’afficher des objets
3D sur un écran 2D. Ces affichages se font par l’intermédiaire de transformations
géométriques gérées par le moteur lui-même. Les objets sont eux stockées sous la forme de
polygones triangulaires dont les extrémités sont représentées par des vertex 3D (X, Y, Z).
Afin de développer un moteur 3D il existe 3 grandes API Graphique :
- OpenGL (Open Graphics Library) qui comme son nom l’indique est une librairie
totalement Open Source.
- DirectX qui est la libraire créée par Microsoft pour les plateformes Windows. En plus
de comprendre une librairie graphique, DirectX permet de faire de la programmation
sonore ou encore d’interagir avec les périphériques externes de jeux (manette,
joystick).
- Java 3D est beaucoup moins utilisée que les deux précédentes librairies car elle est
plus haut niveau et contraint les utilisateurs à utiliser le langage Java, et d’utiliser une
machine virtuelle.
Le développement d’un moteur 3D se fait par étape et peut être sans cesse amélioré pour
atteindre des rendus graphiques en temps réel de très bonne qualité. Seulement, plus la qualité
est bonne, plus le matériel demandé devient lourd et coûteux. Ce point là est très important
dans notre cas car les chipsets graphiques des PDA ne sont pas fait pour développer des
moteurs 3D. Il faut donc trouver des solutions de développement adéquates.
Il existe de nombreux moteurs 3D sur PC tel que les moteurs de jeux comme par exemple le
moteur privé de Doom 3 développé par John D. Carmack, ou encore des moteurs tout public
tel que le célèbre Ogre 3D. Seulement, sur PDA le choix ce restreint beaucoup nous verrons
les solutions existantes un peu plus bas.
1.2.
PDA
L’enjeu de développer un moteur 3D sur PDA est de pouvoir par la suite l’utiliser par le
laboratoire du SET de Belfort. En effet, pour l’instant le véhicule intelligent peut être déplacé
par ce même PDA sur lequel nous allons implémenter le moteur 3D. Par la suite nous voulons
pouvoir représenter en 3D l’environnement dans lequel le véhicule se déplace.
Le sujet de projet a été reprit d’un précédent groupe. En vue des nombreux problèmes qu’ils
ont rencontré face à l’adaptation au PDA, nous avons décidé, en accord avec nos encadrants,
de reprendre les travaux du début en passant du temps sur l’analyse des différentes solutions
mis à notre disposition. L’analyse a aussi bien porté sur les technologies à utiliser que des
outils de développement.
-4-
TX52 : Intégration d’un moteur 3D sur PDA
2.
Moteurs 3D existants
2.1.
Solutions payantes
Afin de montrer les différentes solutions 3D pour Pocket PC existantes, voici un petit
récapitulatif des solutions à licence payante.
Noms
EDGELIB 3.X
Coûts
Lite : 150€ / année
Standard : $450 +
$350 année
supplémentaire
full : $950 + $650
année
supplémentaire
XForge
N.C
Mobex3D
Binary license :
$2999, 4% royalty
Independent
Developer binary
license : $1000 +
$1999, 6% royalty
Liens
http://www.edgelib.com/
Descriptions
Une des
plateformes les
plus puissante
pour le
développement
d’application et de
jeux 3D destinée
aux Pocket PC
http://www.acrodea.co.jp/english Librairie fermée,
très puissante
utilisant OpenGL
ES
http://www.mobex3d.com
Moteur destiné au
développement
d’application 3D
et de jeux. Celuici a été utilisé à de
nombreuse reprise
pour des jeux à
licence
Voilà donc trois des nombreuses solutions payantes. Il en existe d’autres encore, seulement
les prix restant élevés dans notre cas nous opterons pour une solution gratuite.
2.2.
Solutions gratuites
2.2.1. API et Moteurs gratuits
Vincent 3D : http://www.vincent3d.com/
Vincent 3D est une implémentation de OpenGL ES très intéressante. En effet, celui-ci,
développé en C++, est conçu spécialement pour les plateformes portables telles que les PDA
ou autres Smartphones. Les sources sont ouvertes au publique et peuvent être utilisées sous
licence BSD. Vincent 3D n’est pas un moteur 3D en soit même mais permet de faciliter
l’implémentation de celui-ci sur un PDA. De plus, Vincent 3D a été certifié par la société
Khronos Group comme plateforme de développement sûr pour OpenGL ES.
Cette implémentation est l’une des plus puissante et convoitée dans le monde des solutions
gratuites pour Pocket PC.
-5-
TX52 : Intégration d’un moteur 3D sur PDA
Hybrid Rasteroid : http://www.hybrid.fi/
Hybrid Rasteroid est développé par la société finlandaise Hybrid Graphics aujourd’hui
rachetée par Nvidia.
Tout comme Vincent 3D, Hybrid Rasteroid est une librairie bas niveau basée sur OpenGL ES
et utilisée notamment pour le développement de jeux vidéos.
La version 3.1 de Hybrid Rasteroid est utilisable gratuitement pour utilisation et
développement y compris les usages commerciaux.
Rasteroid est utilisable sous le système d’exploitation Windows Mobile 2003 couplé aux
PDA: Acer n10/30, ASUS My Pal A620/716, Dell Axim X-series, hp iPaq hXXXX-series,
ViewSonic PC V36, Toshiba eXXX-series, ainsi que sur les téléphones portables Nokia
Series 60 utilisant le système d’exploitation Symbian.
Tout comme Vincent 3D cette implémentation a été certifiée par la société Khronos Group.
Yeti 3D GPL :
Yeti 3D est un moteur 3D écrit en C++ & OpenGL ES. Celui-ci semble beaucoup moins
avancé que les versions payantes du fait qu’il s’agit d’une version GPL (la version Pro existe
mais est payante). Etant donnée la qualité des sources et le peu de documentations existantes
autour de ce moteur, son utilisation pour notre projet ne semble pas une bonne solution.
2.2.2. Adaptation pour PDA d’un moteur existant pour PC
Pour ne pas partir de rien mais d’une solution Open Source gratuite, nous pouvons envisager
le fait de reprendre un moteur destiné aux PC et de l’adapter pour les périphériques de poche.
Cela dit, ceci est ambitieux car nous devons trouver un moteur peu gourmand qui pourrait être
supporté par un PDA. De plus la traduction du moteur ne pourrait pas toujours être complète
(ex : OpenGL -> OpenGL ES) du fait des fonctionnalités non présentes dans les librairies de
développement d’application 3D pour PDA.
De plus de nombreux moteurs 3D utilisent du OpenGL pur sans passer par des librairies
annexes comme GLUT ce qui nous demanderait un temps d’adaptation conséquent en vue de
nos connaissances dans le domaine.
Une autre solution serait de prendre un moteur 3D existant et codé en Java puis de le
transférer sur PDA avec une machine virtuelle adéquate (ex : MySaifu + Xith).
Nous préférons envisager que l’une des librairies gratuites détaillées dans la partie précédente
soit fonctionnelles; Cela dit l’adaptation d’un moteur peut être une solution de secours
intéressante. Et nous verrons plus bas que certains moteurs 3D existant sur PC nous ont
inspiré pour la structure de notre code. En effet, nous avons put apprendre comment
développer les parties du moteur à l’aide des moteurs PC, notamment lors de la lecture et de
l’affichage de fichiers 3D externes.
-6-
TX52 : Intégration d’un moteur 3D sur PDA
2.2.3. Récupération de moteur
Cette dernière possibilité nous verrait travailler directement à partir d’un moteur intégré à une
application/jeu. Pour cela il faudrait décortiquer ces programmes afin d’utiliser leurs moteurs
3D.
Cette option reste très laborieuse car dans la plus part de ces cas soit le moteur 3D a été conçu
juste pour l’application et est très peu modulable, ou soit la partie ouverte n’est qu’une
amélioration d’une partie fermée du moteur original (comme pour Quake 3 sur Pocket PC).
Voici une liste de quelques jeux utilisant des moteurs 3D :
• Quake 1,2,3 : http://www.pulsemobilegames.com/QuakeMobile.html
• Cube http://cube.sourceforge.net/
• …
-7-
TX52 : Intégration d’un moteur 3D sur PDA
3.
Solutions pour le développement
Nous allons ici voir les trois grandes optiques possibles dans le cas du développement d’un
moteur 3D. Celles-ci peuvent aussi être adaptées dans le cas de la reprise d’un moteur existant
(voir les solutions gratuites).
3.1.
C++ & OpenGL ES
3.1.1. OpenGL ES et GLUT ES
Nombreux des moteurs ou applications 3D présentés au dessus ont été développés en utilisant
les technologies OpenGL ES (Opengl for Embedded Systems) et Power VR. Nous allons voir
ici a quoi elles correspondent.
OpenGL ES est une API destinée à la programmation d’application 2D et 3D pour les
systèmes embarqués. OpenGL ES permet de réaliser de très belles applications 3D tout en
tenant compte des capacités d’une machine de type embarqué limité en ressource. Le
compromis se situe dans le fait que cette version est beaucoup plus légère qu’OpenGL, et
donc de nombreuses fonctionnalités ont été abandonnées.
La dernière version d’OpenGL ES est la 2.0 qui prend en compte des nouvelles technologies
d’OpenGL 2.0 comme par exemple les shaders.
Il existe peut de processeur graphique possédant les drivers OpenGL ES :
• ATI Imageon 2300 (OGLES 1.0)
• Imagination Technologies PowerVR MBX (OGLES 1.1) et PowerVR SGX (OGLES
2.0)
L‘atout principal dans le choix d’OpenGL ES est de pouvoir programmer des applications 3D
en utilisant notre base de connaissance en C++ et OpenGL. De plus il existe GLUT ES qui est
la boite à outils allégée GLUT pour les systèmes embarqués. Ce qui est un avantage de taille!
3.1.2. PowerVR
Le PDA que nous utiliserons lors de l’intégration du moteur 3D est un Dell Axim x51V.
Celui-ci a pour spécificité d’avoir un accélérateur graphique Intel 2700g. L’avantage avec ce
périphérique graphique c’est qu’il utilise lui-même un processeur graphique PowerVR MBX.
C’est pour cela qu’il est très confortable dans notre cas de travailler avec OpenGL ES (drivers
supportés).
-8-
TX52 : Intégration d’un moteur 3D sur PDA
De plus cela nous permet d’utiliser la librairie : Khronos OpenGL ES 1.x SDKs for PowerVR
MBX Family. Celle-ci est une librairie Tool Kit rajoutant de nombreuses fonctionnalités
(comme GLUT pour OpenGL).
3.1.3. Autres conditions
Afin de développer des pré requis sont nécessaires :
• Un éditeur (ex : Eclipse avec plugin C++).
• Le SDK Windows Mobile 5 nécessaire pour développer des applications compatibles
avec le système d’exploitation portant le même nom.
• Librairie OpenGL ES 1.0 PowerVR Insider.
OpengGL est utilisée avec le langage C++. C’est pour cela que nous allons voir l’alternative
Java offerte pour le développement d’un moteur 3D.
3.2.
La 3D sur Pocket PC avec Java
3.2.1. JSR 184
La JSR 184 API destinée à la J2ME (Java pour plateforme mobile) est utilisée pour
développer des applications 3D pour PDA. Cette API assez complète reste plus haut niveau
qu’OpenGL ES et possède des fonctionnalités assez proches de celles de Java 3D.
3.2.2. La machine virtuelle
Une fois la librairie graphique choisie il nous faut une machine virtuelle utilisable pour les
applications portables. Il en existe deux grandes :
• La KVM d’IBM.
• MySaifu : Machine virtuelle gratuite disponible pour Windows Mobile
(http://www2s.biglobe.ne.jp/~dat/java/project/jvm/index_en.html).
Seulement des tests réalisés par l’équipe précédente (TX précédente) montre que ces deux
machines virtuelles ne supportaient pas la JSR 184. Ceci reste à vérifier.
3.3.
Direct X pour périphériques
Une dernière solution de développement concerne l’utilisation de Direct3D (et DirectX)
managé pour les périphériques. Son développement est possible depuis la version 2.0 du .NET
compact Framework mais nécessite que le PDA ou le Smartphone possède un système
d’exploitation Windows Mobile 5.
Ses librairies sont principalement un sous ensemble des classes DirectX 9 adaptées encore une
fois aux périphériques mobiles, de ce fait leurs fonctionnalités sont plus proches de DirectX 8.
Microsoft propose une documentation détaillée ainsi qu’un bon nombre de didacticiels
concernant Direct 3D Mobile.
-9-
TX52 : Intégration d’un moteur 3D sur PDA
4.
Développer sur un PDA
4.1.
Choix des Technologies
Après notre analyse et les diverses comparaisons avec les modèles existants, notre choix se
porte sur Vincent 3D qui est l’implémentation d’OpenGL ES la plus reconnue dan le milieu
de la 3D embarqué. Le développement se fait lui en C++ comme la majorité des applications
Vincent 3D et aussi pour une raison de préférence de notre part.
Nous avons également eu le choix de plusieurs librairies outils, UG très souvent utilisé dans
les exemples de Vincent 3D et GLUT ES la version simplifiée de GLUT pour l’embarqué.
Cette fois encore, en vu de nos connaissances, GLUT ES a été préférée offrant plus de
fonctionnalités et de documentations qu’UG. Cependant, il faut noter que certaines fonctions
comme le gluLookAt sont absentes dans GLUT ES mais définit dans UG, cette fonction a
donc été implémentée à partir de la source d’UG.lib.
Un autre point important à noter est que lors de la création d’une fenêtre, GLUT ES n’affiche
ni les bordures (croix de fermeture) ni le clavier tactile, un problème d’interaction se pause
alors puisque aucune touche de clavier ne peut être saisie. En contre partie GLUT ES offre
des menus contextuels très ergonomiques et simples d’implémentations.
L’implémentation se fait à partir de l’IDE Microsoft Embedded Visual C++ 4.0 sur une
plateforme Windows, qui est généralement recommandé par la communauté de l’embarqué.
Nous verrons dans les parties suivantes comment mettre en œuvre ses technologies afin de
commencer les travaux dans de bonnes conditions.
4.2.
L’environnement de développement pour PDA
Sous Windows, l’idéal pour le C++ embarqué est sans contexte Visual Embbeded C++ 4.0
(dernière version en date). Seulement, il existe quelques petites choses à savoir afin de ne pas
bloquer sur des problèmes externes au code.
Une fois le logiciel installé (gratuit), la première chose à faire est de télécharger les services
pack de celui-ci. Pour le moment il en existe 4. Ces mises à jour sont nécessaires pour utiliser
Vincent 3D correctement. De plus nous remarquons que les diverses mises à jour joues sur la
rapidité de compilation et d’exécution. En effet, durant les Benchmarks nous avons remarqués
que plus le logiciel était mis à jour plus les résultats étaient satisfaisants (18 fps avec la mise à
jour 3 contre 25 fps pour la mise à jour 4).
La deuxième chose à faire est d’installer la SDK propre à Windows mobile pour PDA. En
effet, le développement embarqué ne se limite pas au PDA et à Windows mobile. C’est pour
cela que la SDK n’est pas comprise avec Visual Embbeded. Une fois la SDK installée, elle est
directement mise en relation avec l’environnement de développement. Il faut alors juste
préciser lors du début d’un projet que nous souhaitons l’utiliser (voir l’exemple ci-dessous).
- 10 -
TX52 : Intégration d’un moteur 3D sur PDA
Un des problèmes qui peut être rencontré est le choix de la SDK. En effet, ce n’est pas la SDK
Windows mobile 5 qui nous intéresse mais bien la SDK : Microsoft Pocket PC 2003 SDK
disponible et gratuite à cette adresse :
http://www.microsoft.com/downloads/details.aspx?FamilyId=9996B314-0364-4623-9EDE0B5FBB133652&displaylang=en
Le prochain module : Activ Sync est lui facultatif. Celui-ci permet à l’utilisateur de
synchroniser son PDA avec son PC et ainsi mettre à jour directement les nouvelles données
compilées. Dans notre cas, nous fonctionnons à l’aide de carte Compact Flash et de carte SD
pour transférer les programmes.
Une fois tous les outils installés, nous pouvons commencer le développement. Dés que le
programme est prêt, la compilation lance un émulateur Pocket PC sous Windows mobile. Le
fichier (.exe) correspondant à l’application est lui chargé à la racine du PDA. Il peut être
exécuté à partir de cet emplacement. Seulement, précisons que si nous avons besoin de dll
externes pour faire marcher notre programme nous aurons besoin de partager le dossier du PC
comprenant ces dll avec le PDA. Pour cela, il faut juste aller dans le menu « Emulator » de
l’émulateur et choisir l’option de partage de dossier. Une fois le dossier partagé il ne reste
plus qu’à déplacer le fichier à exécuter à l’emplacement où se trouve les dll.
Lorsque l’on veut tester les programmes sous PDA, il ne faut pas oublier de compiler le
programme en Release (mode ARM) et non en Debug. Sinon le programme sera plus lourd et
moins performant. Pour le reste, les manipulations se font de la même façon que sur
émulateur.
Enfin, nous avons été très vite confrontés à un problème lié à l’environnement de
développement. En effet, quand nous testons un programme sous l’émulateur, Visual
Embbeded C++ ne gère pas la sortie standard. Aucune console ne peut nous indiquer les
sorties du programme étant donné que celui-ci est exécuté sur un émulateur.
Pour remédier à ce problème, il nous a fallu créer un fichier de sortie qui sera lu après
l’exécution du programme, directement sur le PDA émulé. Voici les principales lignes de
code C++ utilisées pour la création de ce fichier :
- 11 -
TX52 : Intégration d’un moteur 3D sur PDA
//Ouverture du fichier de debuggage
FILE* debugFile = fopen ( "myfile.txt" , "wt" );
//Ecriture du fichier
int temp = 2 ;
char * temp2 = "bonjour" ;
fprintf(debugFile, "affichage de la variable temp : %d \n", temp);
fprintf(debugFile, "affichage de la variable temp : %c \n", temp2);
//Fermeture du fichier
fclose (debugFile);
Attention à la commande fprintf ! Car l’impression des variables se code différemment selon
leurs types :
- %d pour les entiers
- %f pour les flottants
- %c pour les caractères
- %x pour les hexadécimal (très pratique lors du parsing des chunks d’un fichier 3D
Studio Max).
4.3.
Le C++ et l’embarqué
En embarqué, la programmation C++ diffère du développement originel sur PC. Les librairies
standards disponibles sur PDA sont très réduites en vue de celles qui existent sur PC. Dans la
majorité des cas il faudra se restreindre aux librairies de bases.
Par exemple, de manière générale le développement sur PDA ne supporte pas la gestion de
flux, ainsi nous ne pourrons pas utiliser la librairie fstream.h pour coder des flux de données
vers un fichier. Il nous faudra utiliser les fonctions de bases comprit dans le fichier stdio.h.
En C++ embarqué, pour pouvoir utiliser des fonctionnalités plus complexes ou alors des
librairies du C++ non compatible avec le PDA, il faudra trouver des librairies externes ou
faire des adaptations. C’est notamment le cas d’OpenGL ES qui est une adaptation
d’OpenGL.
C’est donc ici que l’on rencontre les plus gros problèmes liés à ce type de programmation. La
reprise de moteur 3D PC existant est très difficile à mettre en place pour ces raisons. Par
exemple, nous avons plus d’une fois bloqué lors du test de bouts de code conçus pour PC sur
notre émulateur à cause du manque de librairie tel que io.h.
Pour information toutes les librairies standard qui peuvent être utilisées sont présentes dans
l’emplacement d’installation de la SDK du Pocket PC (emplacement par défaut : C:\Program
Files\Windows CE Tools\wce420\POCKET PC 2003\Include). De nombreuses fois nous
avons essayé de placer des librairies C++ standard dans ce dossier pour pouvoir les tester. A
chaque reprise les tests se sont révélés négatif.
- 12 -
TX52 : Intégration d’un moteur 3D sur PDA
4.4.
Comment utiliser Vincent 3D et la GLUT
Installer et utiliser Vincent 3D et la GLUT ES en duo avec Visual Embbeded C++ n’est pas
évident la première fois. Voici les étapes à réaliser pour pouvoir passer au plus vite au
développement du moteur sans avoir de problèmes annexes.
La première chose à faire est de télécharger les versions source et binaire de Vincent 3D et de
GLUT ES à l’adresse suivante : http://sourceforge.net/projects/ogl-es. Une fois téléchargés, il
faut sélectionner les fichiers DLL (version binaire) : libGLES_CM.dll, glutes.dll et les placer
dans un dossier qui sera à partagé avec le PDA. L’exécutable généré en compilant le moteur
sera à placer dans le répertoire contenant ces DLL pour fonctionner.
La deuxième étape est de configurer Visual C++ pour cela il faut suivre les étapes suivantes :
- Dans les options de Visual, il faut sélectionner les chemins d’accès aux librairies (.lib),
include (.h) et exécutable (.dll) de Vincent 3D et de la GLUT, comme présenté cidessous.
-
Ensuite dans les « Settings » du projet il faut faire le lien entre le projet et les bonnes
librairies : libGLES_CM.lib, glutes.lib et ug.lib.
- 13 -
TX52 : Intégration d’un moteur 3D sur PDA
-
Une fois les deux précédentes étapes exécutées nous pouvons enfin nous servir de la
GLUT en incluant les en-têtes de la librairie au début de nos fichiers sources :
#include <GLES/glutes.h>. La GLUT se sert de Vincent 3D pour fonctionner.
Attention, après la compilation et le lancement de l’émulateur, il ne faut pas oublier de
partager avec le PDA le dossier que l’on a crée avec les DLL, et placer l’exécutable dedans
pour que le programme fonctionne.
- 14 -
TX52 : Intégration d’un moteur 3D sur PDA
5.
Nos travaux
Maintenant que nous avons étudié en détail les solutions possibles pour l’implémentation d’un
moteur, que nous en avons choisi une en particulier, et que nous avons expliqué les prés
requis nécessaires, nous allons pouvoir présenter les travaux réalisés pour construire le
moteur.
5.1.
Structure d’un moteur 3D
Il n’existe pas de nomenclature à la réalisation de la structure d’un moteur 3D, chaque moteur
utilise sa propre structure et hiérarchie dans le but d’optimiser le rendu, et de rendre plus
facile l’utilisation du moteur. Néanmoins, il est possible de retirer plusieurs fonctionnalités
principales d’un moteur 3D.
Un moteur 3D est composé en plusieurs blocs répondant à une attente précise. Voici un
exemple des blocs essentiels pour un moteur 3D de base :
MOTEUR 3D
SceneGraph
Hierarchy
(Quadtree, BSP)
Stockage des
objets 2D/3D
Behaviour
- move
- rotate
Affichage de
la scène
Import / Export
Input
Fichiers 3D
(.3DS…)
Périphériques
(stylet, clavier…)
Tout d’abord, celui permettant l’affichage sur l’écran des objets de la scène. Il s’agit de la
dernière étape du moteur, celle la plus proche du matériel. Ce bloc communique donc avec
l’API graphique, dans notre cas OpenGL ES, pour récupérer les objets contenus dans une
hiérarchie et les afficher.
La hiérarchie constitue le deuxième bloc d’un moteur. Elle intègre l’utilisation d’arbre tel que
le bsp (Binarie Space Partion), ou l’Octree afin de diviser l’espace de la scène en région, et
ainsi optimiser le rendu à l’aide de divers algorithmes. Par exemple, en éliminant les facettes
qui ne sont pas visible par l’observateur pendant l’affichage.
- 15 -
TX52 : Intégration d’un moteur 3D sur PDA
Ensuite, vient le gestionnaire permettant de stocker les facettes d’objets, leurs positions et
orientations dans l’espace de la scène. Ce gestionnaire de ressources permet aussi le stockage
des textures, des matériaux pouvant être utilisés. Il permet, par exemple, d’éviter la
duplication d’information.
Les deux modules suivants permettent l’interaction avec l’extérieur.
Le premier propose de pouvoir charger les modèles venus de fichiers externes comme ceux de
3D Studio Max. Les objets contenus dans le fichier sont directement déposés dans le module
de stockage. De la même façon ce module permet l’export des objets.
Enfin, le dernier module permet au moteur de communiquer avec l’extérieur, par le moyen de
périphériques externes. Ce module passe directement par un sous-module appelé
« Behaviour » et stockant toutes les transformations possible sur la scène. Ainsi, nous
pourrons nous déplacer dans la scène avec le stylet par exemple.
Théoriquement, le moteur est posé. Les blocs qui le compose vont alors être réalisés et étudiés
un par un. Une application centrale rassemblant ses évolutions a été créée sous la forme d’un
benchmark.
5.2.
Benchmark
Afin d’appréhender l’évolution des performances de notre travail, il nous à été demandé de
réaliser un programme permettant d’afficher le nombre de frame par seconde (fps) du rendu
de notre travail dans 3 cas de figure :
- En faisant varier le nombre de facettes d’un objet.
- En faisant varier le nombre d’objets de facettes constantes dans une scène.
- En faisant varier la taille d’une texture d’un plan (programme indépendant).
Les performances de nos implémentations étaient validées à partir de 24fps.
Aperçus du benchmark
- 16 -
TX52 : Intégration d’un moteur 3D sur PDA
A ces fonctionnalités se sont ajoutées l’affichage d’objet en Vextex buffer Object,
l’importation d’objets à partir de fichiers 3DS, et la navigation dans la scène.
Le benchmark a donc été réalisé à l’aide de GLUT ES permettant d’afficher le nombre de FPS
directement sur l’écran principale.
Le calcul des « frames per second » se fait de la manière suivante : un compteur est
incrémenté à chaque passage du programme dans la fonction d’affichage (fonction appelée
par glDisplayFunc). Puis toutes les secondes, il est relevé l’augmentation du compteur,
donnant ainsi les fps de l’application. La fonction utilisée pour calculer le nombre de fps et
placée dans la fonction d’affichage est la suivante :
int GetFPS()
{
_count++;
if(((int)GetTickCount()-_duree)>1000)
{
_fps = _count;
_count = 0;
_duree = (int)GetTickCount();
}
return _fps;
}
L’affichage des fps à l’écran est réalisée par la fonction glutTrueTypeString permettant
d’afficher une chaîne de caractère de type WCHAR. Cette fonction prend en paramètre le type
de police de la chaîne, sa taille, son style, ses coordonnées sur l’écran, et la chaîne à afficher.
Il est à noter que le repère d’écriture est situé en bas à gauche de l’écran, avec l’axe des y
dirigé vers le haut.
WCHAR wtxt[150];
wsprintf(wtxt, L"%d fps ",GetFPS()) ;
glutTrueTypeString(L"Arial", 8, 1|2, 5, 5, wtxt);
Concernant les résultats de notre application, ils se sont avérés très infructueux car le seuil des
24 fps n’a été que très rarement atteint. Nous avons même remarqué que le programme tourne
« à vide » à 20fps. Cela peut être du à des fuites mémoires, ou encore le fait que GLUT ES
cale sa boucle de rendue sur la fréquence de l’écran du PDA.
Dans la prochaine partie, nous allons décrire la manière que nous avons utilisé pour réaliser le
module d’import de fichier sur le moteur 3D. Cet import ce fait à partir de fichiers issus de 3D
Studio Max.
5.3.
Parsing d’un fichier 3D Studio Max
5.3.1. Explication du système de Chunk
Un fichier 3D Studio Max (extension .3DS) possède une structure propre. Cette structure
permet aux utilisateurs de mettre en œuvre des parseurs capables de lire des fichiers 3DS
représentants aussi bien des objets simples, que des objets beaucoup plus complexes. Le
parseur n’a donc pas besoin d’être modifié suivant le type d’objet que l’on souhaite lire.
- 17 -
TX52 : Intégration d’un moteur 3D sur PDA
Un fichier 3DS est composé de ce qu’on appel des Chunks. Chaque chunk représente une
partie spécifique du fichier (liste des vertex, liste des matériaux…). Pour notre moteur nous
mettons en place un parseur qui récupère les données issues des bons chunks afin de procéder
par la suite à l’affichage de l’objet.
Voici la liste des principaux chunks existant :
MAIN CHUNK 0x4D4D
3D EDITOR CHUNK 0x3D3D
OBJECT BLOCK 0x4000
TRIANGULAR MESH 0x4100
VERTICES LIST 0x4110
FACES DESCRIPTION 0x4120
FACES MATERIAL 0x4130
MAPPING COORDINATES LIST 0x4140
SMOOTHING GROUP LIST 0x4150
LOCAL COORDINATES SYSTEM 0x4160
LIGHT 0x4600
SPOTLIGHT 0x4610
CAMERA 0x4700
MATERIAL BLOCK 0xAFFF
MATERIAL NAME 0xA000
AMBIENT COLOR 0xA010
DIFFUSE COLOR 0xA020
SPECULAR COLOR 0xA030
TEXTURE MAP 1 0xA200
BUMP MAP 0xA230
REFLECTION MAP 0xA220
[SUB CHUNKS FOR EACH MAP]
MAPPING FILENAME 0xA300
MAPPING PARAMETERS 0xA351
KEYFRAMER CHUNK 0xB000
MESH INFORMATION BLOCK 0xB002
SPOT LIGHT INFORMATION BLOCK 0xB007
FRAMES (START AND END) 0xB008
OBJECT NAME 0xB010
OBJECT PIVOT POINT 0xB013
POSITION TRACK 0xB020
ROTATION TRACK 0xB021
SCALE TRACK 0xB022
HIERARCHY POSITION 0xB030
Nous remarquons que chaque chunk possède un numéro hexadécimal propre. Ce numéro
(identité) va nous permettre d’identifier dans quel chunk nous nous trouvons lors de la lecture
du fichier. De plus, nous constatons que certain chunk sont contenus par d’autre chunk sous
forme d’une hiérarchie. Ceci nous permet de diviser les chunks en partie et sous parties. Par
exemple dans le chunk de matériaux (MATERIAL BLOCK), nous avons plusieurs sous
chunks représentant les propriétés d’un matériau (MATERIAL NAME, AMBIENT
COLOR…).
C’est pour cette raison qu’un chunk n’a pas forcement de donnée importante. En effet, il
contient pour information son numéro (hexadécimal) ainsi que sa taille. Seulement, celui-ci ne
contient pas toujours d’autres informations. Il est donc juste là pour nous situer et représenter
le père de sous chunks. C’est le cas par exemple du chunk numéro 0x4D4D qui représente le
premier chunk du fichier et qui est le père de tout les autres chunks.
- 18 -
TX52 : Intégration d’un moteur 3D sur PDA
Bien sûr, dans notre cas seul une petite partie des chunks nous intéresse. En effet, un moteur
graphique avancé les utiliserait tous, seulement dans notre cas, nous souhaitons pour le
moment juste récupérer l’objet et l’afficher. C’est donc pour cela, que par la suite, nous
traitons en détails seulement les chunks :
-
-
VERTICES LIST 0x4110
FACES DESCRIPTION 0x4120
FACES MATERIAL 0x4130
Maintenant que nous avons étudié la structure du fichier nous allons rentrer en détail sur la
manière de parser le fichier.
5.3.2. Lecture du fichier 3D Studio Max
La lecture du fichier est entièrement faite dans une fonction. Le principe majeur est d’ouvrir
le fichier et ensuite de le lire en bouclant tant que l’ont n’a pas lu tous les chunks qui nous
sont nécessaires. Dans cette boucle nous repérons le chunk en cour pour le traiter.
Il faut savoir que tout les chunks doivent être traités. En effet, un chunk contient toujours des
informations à lire (nom et taille), plus éventuellement d’autres données. Afin de faire avancer
le curseur de lecture, il nous faut donc lire aussi bien les chunks qui nous intéressent que les
autres.
Avant de commencer la lecture nous rappelons ici la structure dans laquelle sont stockées les
valeurs que nous lisons :
#define MAX_VERTICES 8000
#define MAX_POLYGONS 8000
// Structure d’un Vertex
typedef struct{
float x,y,z;
}vertex_type;
// Structure des polygones, les entiers représentent les
// index des vertices le composant
typedef struct{
int a,b,c;
}polygon_type;
// Coordonnées de Mapping
typedef struct{
float u,v;
}mapcoord_type;
// Structure de l’objet et de tous ces attributs
typedef struct {
char name[20];
int vertices_qty;
int polygons_qty;
vertex_type vertex[MAX_VERTICES];
polygon_type polygon[MAX_POLYGONS];
mapcoord_type mapcoord[MAX_VERTICES];
int id_texture;
} obj_type, *obj_type_ptr;
- 19 -
TX52 : Intégration d’un moteur 3D sur PDA
Cette structure nous permet de stocker les éléments nécessaires à l’affichage de notre objet :
- Les coordonnées de ces vertices.
- Les indices des polygones.
- Ainsi que les coordonnées de mapping.
Nous stockons des données annexes aussi, comme le nombre de vertices et de polygones qui
nous sont utiles lors de nos boucles de parcours ou encore le nom de l’objet.
Maintenant que nous avons expliqué la structure de stockage des informations, nous passons à
la lecture du fichier.
Voici la première étape d’initialisation des variables :
FILE * File;
//Fichier 3Ds
unsigned short int chunk_id; //Id d'un chunk
unsigned int chunk_lenght;
//Longueur d'un chunk
unsigned char l_char;
int i;
unsigned short nbElts;
//Char variable
//Index
//Nombre d’éléments dans un chunk
unsigned short face_flags;
//Information sur les faces
unsigned short version=0;
//Information sur la version de 3DS
unsigned int file_size=1;
//Taille du fichier
unsigned int file_cursor=0;
//Position du curseur dans le fichier
unsigned int chunk_cursor=0; //Taille du chunk en cours
bool end_parsing = false;
//Booléen indiquant si nous avons parsé tt les
//chunk utiles ou non
Cette première partie est assez explicite. Elle représente toutes les variables qui sont
nécessaires pour parcourir le fichier. Notre structure d’objet est elle passée en paramètre à
l’aide d’une référence pour pouvoir être complétée dans la fonction au fur et à mesure de la
lecture.
L’étape suivante est l’ouverture du fichier par l’intermédiaire de la fonction d’ouverture de
flux fopen. Attention, sur PDA nous ne pouvons pas utiliser d’autres fonctions pour la gestion
de flux tel que celles de la librairie fstream. C’est pour cela que nous utilisons la librairie
standard.
File=fopen (p_filename, "r"); //Ouverture du fichier 3DS
if (!File) return 0;
Ici, nous avons le début de la boucle de lecture (chaque tour dans la boucle représente un
chunk). Comme nous l’avons expliqué auparavant, un chunk possède automatiquement les
données sur son identité et sa longueur. C’est pour cela que nous lisons automatiquement ces
informations au début de la boucle. L’identité est stockée sur 2 bits et la longueur du chunk
sur 4 bits.
- 20 -
TX52 : Intégration d’un moteur 3D sur PDA
La longueur d’un chunk père représente la longueur de ses données propres ainsi que de celles
de tous ces fils. C’est pour cela que quand nous lisons le tout premier chunk, nous stockons sa
longueur et l’affectons à la longueur totale du fichier. En effet, le premier chunk est le père de
tout les autres chunks du fichier.
//Tant que nous n’avons pas parsé tout les chunks utiles
//chercher le prochain chunk
while(!end_parsing)
{
chunk_cursor=0; //Curseur du chunk repositionné à 0
nous
allons
//Lecture de l'id du chunk
chunk_cursor+=fread (&chunk_id, 1, 2, File);
//Lecture de la longueur du chunk
chunk_cursor+=fread (&chunk_lenght, 1, 4, File);
if (file_size==1) file_size = chunk_lenght;
Nous rentrons maintenant dans la partie la plus importante du code : la lecture des données
des chunks. Les 3 premiers que nous rencontrons ne sont pas importants. En effet, se sont
juste des chunks sans données essentielles, servant juste à regrouper des sous chunks. Nous
stockons tout de même la version du fichier 3Ds utilisé par l’intermédiaire du chunk
VERSION numéro 0x0002.
// Action exécuté suivant le chunk
switch (chunk_id)
{
case 0x4d4d: break;
case 0x0002: chunk_cursor+=fread (&version, 1, 4, File); break;
case 0x3d3d: break;
Le premier chunk important est celui représentant le nom du fichier. Nous le parsons caractère
par caractère et le stockons dans la structure définis plus haut.
case 0x4000:
i=0;
do
{
chunk_cursor+=fread (&l_char, 1, 1, File);
p_object->name[i]=l_char;
i++;
} while(l_char != '\0' && i<20);
break;
Les premières données essentielles à l’affichage sont les vertices. C’est le chunk 0x4110 qui
va nous permettre de les saisir. La première donnée que nous récupérons est le nombre de
vertices de l’objet. Ensuite pour chaque vertex nous allons récupérer les coordonnées X, Y et
Z (faisant la taille d’un float chacune) avant de passer au chunk suivant.
case 0x4100: break;
case 0x4110:
chunk_cursor+=fread (&nbElts, sizeof (unsigned short), 1, File);
p_object->vertices_qty = nbElts;
for (i=0; i<nbElts; i++)
{
- 21 -
TX52 : Intégration d’un moteur 3D sur PDA
chunk_cursor+=fread (&p_object->vertex[i].x, sizeof(float), 1, File);
chunk_cursor+=fread (&p_object->vertex[i].y, sizeof(float), 1, File);
chunk_cursor+=fread (&p_object->vertex[i].z, sizeof(float), 1, File);
}
break;
Les étapes suivantes sont très proches de la précédente. En effet, pour la liste de polygones
ainsi que les coordonnées de mapping nous récupérons leurs nombres. Après pour chacune
d’entre elle, on récupère soit les indices des vertex composant le polygone ou bien les
coordonnées U et V de mapping. Et ceci de la même façon que précédemment avec les
vertices.
case 0x4120:
chunk_cursor+=fread (&nbElts, sizeof (unsigned short), 1, File);
p_object->polygons_qty = nbElts;
for (i=0; i<nbElts; i++)
{
chunk_cursor+=fread (&p_object->polygon[i].a, sizeof (unsigned short), 1,
File);
chunk_cursor+=fread (&p_object->polygon[i].b, sizeof (unsigned short), 1,
File);
chunk_cursor+=fread (&p_object->polygon[i].c, sizeof (unsigned short), 1,
File);
chunk_cursor+=fread (&face_flags, sizeof (unsigned short), 1, File);
}
end_parsing = true;
break;
case 0x4140:
chunk_cursor+=fread (&nbElts, sizeof (unsigned short), 1, File);
for (i=0; i<nbElts; i++)
{
chunk_cursor+=fread (&p_object->mapcoord[i].u, sizeof (float), 1, File);
chunk_cursor+=fread (&p_object->mapcoord[i].v, sizeof (float), 1, File);
}
break;
Il ne reste plus qu’une seule étape pour la lecture de fichier. Il nous faut lire les chunks
inutiles. Pour cela, dans la clause default, nous lisons les chunk autres ainsi que leurs données
afin de faire avancer le curseur dans le fichier et passer au chunk suivant.
default:
int buffer[5000] = {0}; //Buffer
chunk_cursor += fread(buffer, 1, chunk_lenght - chunk_cursor, File);
break;
}
file_cursor+=chunk_cursor;
}
fclose (File);
//Fermeture du fichier 3DS
return (1);
L’étape de lecture d’un fichier 3D Studio Max est volontairement longue afin d’en saisir tous
les aspects. En effet, en se trompant dans la taille des données à lire nous pouvons nous perdre
très facilement dans le fichier. Aucun caractère de type ‘\n’ nous permet de nous repérer et
donc le moindre bit est important. De plus, les exemples présent sur le Web, utilisent dans la
- 22 -
TX52 : Intégration d’un moteur 3D sur PDA
majorité des cas, des librairies de flux qu’il ne nous est pas permit d’utiliser en embarqué. Cet
exemple permet donc de détailler la méthode de lecture utilisée dans le cadre d’un
développement sur PDA.
Bien sûr un parseur peut être beaucoup plus complet en prenant en compte plus de chunk.
Seulement pour l’affichage, le nôtre nous suffit. Nous allons donc pouvoir passer maintenant
à l’affichage des objets.
5.4.
Affichage d’un objet
L’affichage sous Opengl ES par Vincent 3D est très limité par rapport à OpenGL classique,
pour commencer, OpenGL ES ne dispose pas du mode immédiat permit par les « balises »
glBegin et glEnd, puis les autres fonctions d’affichages (les vertex arrays) sont limitées au
nombre de deux : glDrawArrays et glDrawElements.
Enfin le nombre de primitives de rendues est également réduit.
5.4.1. Les primitives de rendues :
Seule 7 primitives de rendues sont disponibles contre 10 pour OpenGL :
- GL_POINTS
- GL_LINE_STRIP
- GL_LINE_LOOP
- GL_TRIANGLE_STRIP
- GL_TRIANGLE_FAN
- GL_TRIANGLES
OpenGL ES ne dispose donc pas de GL_QUADS, GL_QUADS_STRIP et GL_POLYGON.
5.4.2. Les vertex array
5.4.2.1. L’affichage par vertex array
L’affichage par vertex array repose sur quelques fonctions simples d’utilisation.
Premièrement il faut spécifier les types de capacités à débloquer à l’aide de la fonction
glEnableClientState. Ceci permet alors de pouvoir les modifier et les utiliser.
glEnableClientState(GLenum array).
Avec array de type :
GL_COLOR_ARRAY (pour la couleur des sommets)
GL_MATRIX_INDEX_ARRAY_OES
GL_NORMAL_ARRAY (pour les normales des sommets)
GL_POINT_SIZE_ARRAY_ARRAY_OES
GL_TEXTURE_COORD_ARRAY (pour les coordonnées en texture des sommets)
GL_VERTEX_ARRAY (pour dessiner des sommets)
GL_WEIGHT_ARRAY_OES.
- 23 -
TX52 : Intégration d’un moteur 3D sur PDA
Il est à noter que plusieurs capacités peuvent être spécifiées en même temps. Par exemple
pour pourvoir spécifier une couleur à chaque sommet que l’on va dessiner il suffit d’écrire :
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
Ensuite, on charge en mémoire graphique les valeurs pour chaque capacité débloquée, en
spécifiant la dimension de l’espace dans lequel sont définit leurs types de données. Ceci, afin
de déterminer la taille en mémoire qu’elles vont prendre, le nombre de bytes inutiles entre 2
valeurs et enfin un pointeur sur le tableau de valeurs.
Par exemple pour déclarer des sommets ont utilise la procédure :
void glVertexPointer(GLint taille, GLenum type,
const GLvoid * pointer)
GLsizei stride,
taille : le nombre de dimension du sommet {2,3,4}.
type : le type de donnée des coordonnées du sommet {GL_BYTE,
GL_FLOAT, GL_SHORT, GL_FIXED)
stride : spécifie le nombre de byte inutile entre 2 sommets
consécutive (normalement attribué à 0)
pointer : un pointer sur le premier élément d’un tableau de sommet
Une fois ces deux étapes réalisées, il ne reste plus qu’à dessiner les sommets soit par
glDrawArrays, soit par glDrawElements.
Enfin, il faut de nouveau bloquer les capacités débloquées lors de la première étape à l’aide de
la procédure glDisableClientState.
5.4.2.2. La procédure glDrawArrays
La procédure glDrawArrays est la plus simple d’utilisation et de fonctionnalité des deux
fonctions d’affichages. Elle affiche count sommets suivant le type de primitives mode à partir
de l’indice first tu tableau préalablement chargé en mémoire. Les facettes sont donc affichées
dans l’ordre de leurs indices dans le tableau.
void glDrawArrays(GLenum mode, GLint first, GLsizei count)
mode : spécifie le type de primitives de rendu
first : spécifie l’indice du sommet de départ
count : spécifie le nombre d’indices à rendre
5.4.2.3. La fonction glDrawElements
La procédure glDrawElements est plus souple que la précédente. Elle permet l’affichage de
count sommets suivant le type de primitive mode dans l’ordre des indices définit dans un
tableau d’indice pointé par indices. Ainsi lorsque glDrawArrays devra nécessiter la répétition
d’éléments identiques du tableau d’éléments (au moins 2 dimensions), glDrawElements
répétera les indices sur un même élément (1 dimension en données non signée).
void glDrawElements(GLenum mode, GLsizei count, GLenum type,
const GLvoid * indices)
- 24 -
TX52 : Intégration d’un moteur 3D sur PDA
mode : spécifie le type de primitives de rendue.
count : spécifie lenombre d’éléments à rendre.
type : spécifie le type de donnée des indices {GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT}.
Indices : pointeur sur le tableau d’indices.
5.4.3. Les Vertex Buffers Objects
Dans le cas d’un affichage par vertex array réalisé à partir de la méthode décrite dans la partie
précédente, chaque affichage de frame nécessite le transfert mémoire (principale à graphique)
des informations sur les sommets des objets. Une amélioration du programme consiste à
réduire voir supprimer ces transferts pour permettre un gain de temps considérable. C’est à ce
moment que les VBOs, définis à partir d’OpenGL ES 1.1, interviennent.
5.4.3.1. Avantages des VBOs
La gestion de ces transferts mémoires est permise pour OpenGL ES par deux modes de
transferts, au lieu d’un pour les vertex array, et de trois pour OpenGL (le paramètre
GL_STREAM_DRAW est absent).
Le premier GL_STATIC_DRAW est destiné à définir des objets non déformable dans une
scène, c'est-à-dire n’allant être que très rarement (ou pas) modifier entre deux frames. Ainsi
les données ne sont chargées qu’une seule fois dans la mémoire graphique. Ce mode est par
conséquent celui qui offre le plus de performance dans notre cas.
Pour le second mode GL_DYNAMIC_DRAW ce sont les drivers graphiques qui choisissent
l’emplacement des données (mémoire graphique ou centrale). Ce mode est donc préféré pour
les objets animés pouvant être modifiés.
Un autre avantage avec l’utilisation des VBOs est que les données ne sont plus stockées dans
un tableau temporaire du programme mais dans un tableau gérer par OpenGL.
5.4.3.2. Utilisation des VBOs
L’utilisation d’un VBO se fait en 3 étapes : sa déclaration, son instanciation, et sa destruction.
Quant à l’affichage de l’objet qu’il définit, il reste assez proche de l’affichage classique sous
OpenGL ES.
La déclaration d’un VBO se fait par la procédure glGenBuffers, permettant de créer un buffer
à n dimension, pointé par le tableau à n dimension buffers.
void glGenBuffers(GLsizei n, GLuint * buffers)
n : spécifie le nombre de buffer object à générer
buffer : spécifie un tableau contenant le buffer object généré
- 25 -
TX52 : Intégration d’un moteur 3D sur PDA
Une fois le buffer créé, il faut le lier à une cible: GL_ELEMENT_ARRAY_BUFFER pour les
buffers objects d’indices et GL_ARRAY_BUFFER pour les autres. La procédure réalisant
cette tache est glBindBuffer.
void glBindBuffer(GLenum target, GLuint buffer)
target : spécifie le type de la cible du VBO. Doit être
GL_ARRAY_BUFFER s’il ne s’agit pas d’un buffer d’indices, ou
GL_ELEMENT_ARRAY_BUFFER sinon.
buffer : le nom du buffer object préalablement déclaré.
L’étape suivante consiste à initialiser le buffer par une valeur contenu dans un tableau
classique tache réalisée par la procédure glDataBuffer. Comme une allocation mémoire en C,
il faut spécifier la taille que les données du tableau vont prendre, et comme dans
l’introduction sur les VBOs (partie 5.4.3.1) fournir au chipset graphique le mode de stockage
mémoire.
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum
usage)
target : spécifie le type de la cible du VBO. { GL_ARRAY_BUFFER
,GL_ELEMENT_ARRAY_BUFFER }.
size : spécifie la taille des données en unités machine.
Data : spécifie les données du buffer object.
Usage : spécifie le mode d’usage des données { GL_STATIC_DRAW
,GL_DYNAMIC_DRAW}.
Enfin après le dessin, il est important de restituer les cibles des buffers utilisées à 0, et de rebloquer les capacités.
//coupure du lien des cibles dans le cas de l’utilisation d’un buffer
//d’indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//désactivation des capacités
glDisableClientState(GL_VERTEX_ARRAY);
Nous pouvons instancier un buffer object d’indices de la manière suivante :
//création du buffer object
glGenBuffers(1, &m_indexBuffer);
//linkage du buffer sur la cible correspondante
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
//allocation des données aux buffers d’indices (object.polygons_qty
// triangles de type GLushort)
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
object.polygons_qty*3 *
sizeof(GLushort),
object.polygon, GL_STATIC_DRAW);
Enfin, en dernier temps lorsque l’on est sûr que l’on n’affichera plus un Buffer Object on doit
le restituer à la mémoire par glDeleteBuffer, prenant en paramètre le nom du buffer.
Intéressons nous maintenant à l’affichage d’objets en utilisant les Vertex Buffer Objects.
- 26 -
TX52 : Intégration d’un moteur 3D sur PDA
5.4.3.3. L’affichage avec les VBOs
Comme pour l’affichage par vertex array la première étape consiste à débloquer les capacités.
Puisque cette fois les tableaux sont déjà en mémoire, il est inutile de les transférer, il suffit de
lier le pointeur du buffer à la cible correspondante par la procédure glBindBuffer. Puis de
déclarer la dimension de l’espace de définition des composants à afficher, ainsi que leurs
types de données et de définir le pointeur sur le tableau des données à NULL.
Exemple pour des sommets dans R3 de type float :
glVertexPointer(3,GL_FLOAT,0,NULL)
Enfin il suffit d’afficher les objets en utilisant les méthodes des vertex arrays. L’affichage par
glDrawArrays reste identique au vertex arrays, quant à celui par glDrawElements il varie dans
la mesure de la création d’un buffer d’indices.
5.4.3.4. glDrawElements et les VBOs
La nuance à apporter lors de la création du buffer d’indices, est que la cible pour un tableau
d’indice n’est plus GL_ARRAY_BUFFER mais GL_ELEMENT_ARRAY_BUFFER.
Hormis cette nuance l’utilisation des buffers d’indices est identique aux autres.
Lors de l’affichage par glDrawElement, si le pointer sur le tableau d’indices est NULL alors
le VBO actif ayant pour cible GL_ELEMENT_ARRAY_BUFFER est utilisée. Pour cette
raison il est nécessairement de le faire pointer sur le buffer object d’indices avant l’appel de
glDrawElements.
//linkage du buffer de sommets de 3 dimensions de type Glfloat
glBindBuffer(GL_ARRAY_BUFFER,m_vertexBuffer);
glVertexPointer(3, GL_FLOAT, 0, NULL);
//linkage du buffer d'indices de type GLushort
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
//affichage de object.polygons_qty facettes triangulaires
glDrawElements(GL_TRIANGLES,object.polygons_qty*3,GL_UNSIGNED_SHORT,
NULL);
//coupure du lien des VBOs
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
Nous disposons à présent de l’affichage d’objet pouvant être écrit en dur dans le programme,
ou bien être importé depuis un fichier. Intéressons nous maintenant à un autre bloc du moteur
3D : la communication de l’utilisateur vers le PDA.
- 27 -
TX52 : Intégration d’un moteur 3D sur PDA
5.5.
Gestion des périphériques externes
Les PDA ne dispose que de peu de périphériques. Le stylet peu remplacer la souris, et permet
d’utiliser un clavier virtuel, à cela s’ajoute différents boutons (haut, bas, gauche, droite et
milieu) et d’autres raccourci (agenda, courrier,…). GLUT ES offre des fonctions
performantes permettant de les utiliser.
Ces fonctions sont initialisées dans le main du programme et vont appeler la procédure
qu’elles ont eux en paramètre lorsque les évènements se produises.
5.5.1. Le périphérique stylet.
Dans son utilisation le stylet n’est pas aussi performant que la souris. Pour commencer, il ne
permet pas de différencier un clic gauche d’un clic droit. Windows mobile interprète le clic
gauche lors d’un court contact du stylet sur l’écran, et un clic droit lors d’un long contact fixe.
D’autre part, le mouvement du stylet peut être récupéré que lorsqu’il est en contact avec
l’écran. En d’autre terme les événements « OnmouseMove » de la plus part des langages
événementiels son inutile ici.
Avec GLUT ES les évènements « clic » sont récupérés dans la fonction passée en paramètre
de la procédure glutMouseFunc.
glutMouseFunc(void (*func)(int button, int state, int x, int y))
La fonction func est donc appelée lorsque le stylet entre en contact ou est enlevé d’écran.
Dans notre cas seul la valeur GLUT_LEFT_BUTTON de button peut être naturellement
récupérée. Button peut aussi prendre les valeurs GLUT_RIGHT_BUTTON,
GLUT_MIDDLE_BUTTON car la fonction glutSimulateButton peut lancer se type
d’événement. La valeur state peut valoir GLUT_UP lorsque le stylet arrête le contact ou
GLUT_DOWN lorsqu’il le commence. Enfin x, et y sont les coordonnées de l’endroit où à eu
lieu le contact, par rapport à l’écran dont le repère est situé en haut à gauche avec l’axe des x
dirigé vers la droite et l’axe des y vers le bas.
Un autre type d’événement de la souris concerne son déplacement sur l’écran, puisque le
stylet doit faire contact pour que son mouvement soit détecté, la fonction glutMotionFunc a
été utilisée. La fonction d’appel qu’elle définit, prend en paramètre uniquement les
coordonnées du stylet sur l’écran lors du passage dans la fonction, qui est appelée
régulièrement au court du déplacement.
5.5.2. Les événements clavier
Un des inconvénients de GLUT ES, est qu’il masque le clavier virtuel, ce qui rend inutile la
méthode prévue pour récupérer les touches cliquées (glutKeyboradFunc). Cette méthode sert
alors uniquement à savoir si le bouton central du PDA a été cliqué. Ce bouton correspond
alors au caractère de saut de ligne « \n ».
Les autres évènements qui nous intéressent, concernent le clic sur les boutons du PDA. Ils
sont alors dit « spéciaux » et sont rangés dans la même catégorie que les touches de clavier F1
- 28 -
TX52 : Intégration d’un moteur 3D sur PDA
à F12, fin de ligne, début de ligne, de saut de pages, et d’insert gérées également par
glutSpecialFunc.
Les deux fonctions appelées par ces méthodes prennent alors en paramètre : la position du
stylet dans la fenêtre lors du clic de la touche. Et la touche saisie, sous forme de caractère
pour la fonction glutKeyboardFunc et d’énumération pour la seconde.
Les valeurs de glutSpecialFunc disponibles réellement sur le PDA sont alors:
GLUT_KEY_UP
GLUT_KEY_DOWN
GLUT_KEY_LEFT
GLUT_KEY_RIGHT
Maintenant que nous pouvons utiliser les périphériques du PDA, voyons comment les utiliser
pour naviguer dans l’univers 3D.
5.6.
La navigation
5.6.1. La caméra
OpenGL ES et GLUT ES ne propose pas d’objet caméra à proprement parler. Le point de vu
de l’observateur est fixe dans l’espace. C'est-à-dire que lors du déplacement de la caméra,
intrinsèquement ce n’est pas la caméra qui bouge dans la scène mais la scène qui bouge par
rapport à la caméra.
Dans OpenGL, la fonction qui se rapporte le plus à une caméra est gluLookAt. Cette fonction
permet de définir la position (center), la cible (eye) et l’orientation de la « caméra ». Comme
il l’a été énoncé précédemment UG propose une procédure similaire ugluLookAt.
De même la gestion de l’angle d’ouverture, des projections n’est pas liée aux propriétés d’un
objet caméra, mais est réalisée par déformation de la matrice globale.
Les perspectives peuvent être directement générées par OpenGL ES, fournissant des outils
permettant la projection orthogonale (glOrthof, glOrthox) et perspective (glFrustrumf,
glFrustrumx). UG fournit également l’outil ugluPerspectivef simulacre de gluPersepectivef
permettant la projection en perspective tout comme le fait glFrustrumf mais de manière plus
intuitive [01].
- 29 -
TX52 : Intégration d’un moteur 3D sur PDA
5.6.2. L’utilisation des périphériques
La navigation permet à l’utilisateur de déplacer la caméra dans la scène ou encore de la
tourner autour du centre de l’univers. Pour cela, il interagi avec les périphériques mis à sa
disposition. Le stylet, et les boutons du PDA sont utilisés pour réaliser les 2 types de
navigation suivant les préférences de l’utilisateur.
Par conséquent, la navigation nécessite de calculer le mouvement du stylet pour faire
correspondre son delta déplacement à un angle de rotation ou à une distance de déplacement,
et dans le même but récupérer les évènements des touches « spéciales ».
Nous ne détaillerons pas la gestion des touches. Une valeur sera simplement affectée aux
variables de déplacement suivant le bouton saisi, dans la fonction appelée par
glutSpecialFunc.
Quant au déplacement avec le stylet, il nécessite de calculer son vecteur de déplacement à
chaque passage dans la fonction traitant de glutMotionFunc.
Ce calcul ce fait en plusieurs temps. Premièrement, il faut initialiser la position du stylet dans
l’écran. Ceci est fait lorsque qu’il rentre en contact, chose géré par la fonction appelée de
glutMouseFunc. Une fois cette valeur initiale allouée le delta de déplacement vaudra la
position courante du stylet donnée par la fonction appelée par glutMotionFunc déduit par la
valeur initiale. Par la suite la valeur courante deviendra la valeur initiale et les nouveaux delta
serons calculés jusqu’à ce que le stylet ne touche plus l’écran, (l’événement glutMotionFunc
ne sera plus appelé).
5.6.3. La translation et le zoom
La translation de la caméra ce fait par la méthode ugluLookAt, qui après le calcul de la
direction de visée de la caméra, déplace le référentiel de l’univers de l’inverse de la
translation de la caméra voulu. Le zoom quant à lui correspond à un rapprochement ou
éloignement de l’univers suivant la direction de visée de la caméra.
5.6.4. La rotation
Pour que la rotation soit cohérente et facile à utiliser, elle doit ce faire autour des axes de la
caméra définit dans le repère de l’univers à l’instant zéro. On tourne alors la scène de l’angle
voulu suivant l’axe désiré.
Un problème rencontré est le « gimbal lock », phénomène lié à l’utilisation de matrices de
rotation utilisant les sinus et cosinus. Ce qui peut engendrer la perte d’un degré de liberté donc
l’application de la rotation autour d’un axe. De plus puisque la rotation s’applique à tout
l’univers pour retrouver les axes de la caméra dans l’univers initial, il faut appliquer aux axes
de la caméra la rotation de l’univers inverse.
Un outil mathématique, permet de palier très efficacement et simplement à ces deux
problèmes : Les quaternions
- 30 -
TX52 : Intégration d’un moteur 3D sur PDA
5.6.4.1. Les quaternions
Les quaternions peuvent être vus comme une généralisation des nombres complexes destiné à
représenter l’espace comme « C » est capable de représenter le plan. Pour cela leur inventeur
le mathématicien Hamilton introduit un espace vectoriel à 4 dimensions, composé d’un
vecteur de ℜ 3 ( x, y, z ) T et d’un poids de ℜ t .
t
 x
On représente un quaternion q = t + xi + yj + zk par un vecteur   .
 y
 z 
La classe glQuaternion implémentée à cet effet définit ces quaternions.
Les quaternions montrent tous leurs intérêts lors de rotations. L’axe de rotation sera le vecteur
normé ( x, y, z ) T du quaternion, et l’angle le double de l’arc cosinus de son poids t .
La transformation Ta de ℜ 3 dans ℜ 3 donnée par :
Ta = axa −1 avec a = e Iθ et I 2 = −1
Ta est la rotation d’angle 2θ , d’axe dirigé par I unitaire passant par l’origine de repère affine
choisi.
Le quaternion unitaire q = t + ui + vj + wk
de cette transformation, définit par
q = [cos(θ ); I sin(θ )] peut s’exprimer sous la matrice M a par :
2vu − 2tw
2tv + 2wu 
1 − 2v ² − 2w²


M a =  2tw + 2uv 1 − 2 w² − 2u ² 2 wv − 2tu 
 2uw − 2tv
2tu + 2vw 1 − 2u ² − 2v ² 

M a est alors la matrice de la transformation Ta , qu’il suffit de multiplier à la matrice courante
GL_MODELVIEW d’OpenGL par la fonction glMultMatrix*, afin d’obtenir la rotation
voulue. Dans notre implémentation cette matrice est retournée en paramètre de la procédure
createMatrix de la classe glQuaternion.
Cette matrice nous permet également d’exprimer dans le repère de l’univers transformée, les
r r r
r
r
T
T
axes de la caméra xu , y u , z u définit dans son repère par x (1 0 0 ) , y (0 1 0 ) ,
r
T
z (0 0 1) , en les multipliants à la matrice M c du quaternion q c conjugué (inverse
également car normé) du quaternion q = t + ui + vj + wk de la transformation Ta :
r
r
r
r
r
r
xu = M c × x ; y u = M c × y ; z u = M c × z
getAxeX, getAxeY et getAxeZ de la classe glQuaternion réalisent ces opérations pour les axes
r r r
x , y, z et la matrice de transformation du glQuaternion courant.
Le conjugué q c de q définit donc la transformation inverse de celle de q , il s’exprime en
fonction de q = t + ui + vj + wk par :
- 31 -
TX52 : Intégration d’un moteur 3D sur PDA
q c = t − ui − vj − wk
La fonction conjugue de la classe glQuaternion retourne la conjugué du glQuaternion
courant.
Enfin l’un des grands avantages des quaternions est que la succession de rotations d’axes et
d’angles quelconque, correspond à la multiplication dans l’ordre des quaternions de ces
différentes transformations.
Par exemple le point N ' image de la rotation R1 suivit de la rotation R2 d’un point N , vaut :
N'= M × N
avec M la matrice de la transformation T du quaternion q = q1 × q 2 .
où q1 et q 2 correspondant aux quaternions des transformations R1 et R2 .
Le produit (non commutatif) de deux quaternions se calcule comme suit :
q1 ∗ q2 = tt '− xx '− yy '− zz '+ (tx '+ t ' x + yz '− y ' z )i + (ty '+ t ' y + x ' z − xz ') j + (tz '+ t ' z + xy '− x ' y )k
Pour cette raison l’opérateur * à été sur définit dans la classe glQuaternion.
Nos travaux se sont arrêtés à ce niveau. Durant cette phase d’implémentation nous avons
réalisé : l’importation de fichiers, leurs affichages, l’optimisation de cet affichage,
l’interaction de l’utilisateur avec les périphériques, et la navigation. Nous n’avons cependant
pas réussit à produire tous les blocs fonctionnels du moteur 3D, le stockage des ressources n’a
pas été abordé et la gestion hiérarchique est restée au niveau d’étude de faisabilité.
- 32 -
TX52 : Intégration d’un moteur 3D sur PDA
6.
Evolution de l’application
6.1.
Placement des objets dans l’espace : octree, bsp
L’étude du placement des objets dans l’espace a été commencée. Nous avons étudié différents
moteur pour PC/MAC afin d’y récupérer des structures d’octree, de bsp et de quadtree.
Le moteur de JAVA 3D, possède un moteur écrit en C++ et OpenGL « JOGL » qui aurait pu
contenir des structures simples à intégrer à notre programme. Néanmoins la partie
actuellement écrite en C++ de JOGL est devenue infime et ne contient pas les données que
nous voulions.
Suite à cela Ogre3D a été étudié. Ce moteur possède les trois types de structure,
malheureusement il ne semble pas construire lui-même de BSP mais le lit à partir de fichier
de terrain « QUAKE3 ».
Suite à ces deux constats le projet de construire une hiérarchie a été abandonné. Mais il reste
encore de nombreux moteur à analyser comme Irrlicht et Cristal Space tous deux open source
et appréciés. Contrairement au lieu de récupérer ces structures d’un moteur il serrait
intéressant de les implémenter.
Car il ne faut pas oublier que ces structures sont des outils d’optimisation très performants et
indispensables dans de nombreux algorithmes utilisés dans le monde de la réalité virtuelle.
6.2.
Rasteroid
Rasteroid est le conçurent direct de Vincent 3D. En effet, ces deux implémentations
d’OpenGL ES sont les seules certifiées par la société Khronos Group. L’utilisation de
Rasteroid à la place de Vincent 3D n’est pas une évolution en soit même. Elle permettrait
juste de comparer les résultats au niveau de la performance, pour pouvoir choisir la meilleure
implémentation.
- 33 -
TX52 : Intégration d’un moteur 3D sur PDA
Conclusion
Cette TX nous a introduit deux mondes bien distincts de l’informatique : La programmation
embarquée et graphique. Ce sujet intéressant nous a permis d’apprendre les différentes
fonctionnalités d’un moteur graphique, et de commencer à créer le notre.
Nous avons pu reprendre la programmation OpenGL avec C++ que nous avions vu en IN55,
et l’approfondir à travers les fonctionnalités d’OpengLG ES. La lecture de fichier 3D Studio
Max a été une expérience enrichissante et surement utile pour les futurs projets de moteur,
toutes plateformes.
Dans l’ensemble, les objectifs, que nous nous étions fixés, ont été atteints. En revanche, ce
projet pourrait très bien être repris et approfondi par la suite pour intégrer de nouveaux
objectifs d’amélioration (comme vu dans la dernière partie).
Bien que nous ne suivons pas la filière « Embarqué », nous avons su mener ce projet à terme.
Cette unité de valeur, et en particulier ce projet, nous ont permis de diversifier nos
connaissances et de découvrir un autre domaine que le notre.
- 34 -
TX52 : Intégration d’un moteur 3D sur PDA
Bibliographie
Biau Sandrine, Gordolo Romain, TX-Intégration d'un moteur 3D sur un PDA, 18 juin 2007.
Blythe David, Leech Jon, Munshi Aaftab, kronos group, OpenGL ES Common/Common-Lite
Profile Specification , version 1.1.10, 4 avril 2007
Silicon Graphics, Inc., OpenGL ES 1.1 Reference Manual
Silicon Graphics, Inc, the redBook second edition.
[01]Silicon Graphics, Inc, « projection transformations », the redBook second edition, p88.
TyphoonLabs, « Tutorial 7: Using Vertex Buffer Objects », TyphoonLabs OpenGL ES
tutorials.
ZeusCMD, « opengl ES tutorial », www.zeuscmd.com.
Martin Jean-Noël, « le corps des quaternions », MT51 : mathématiques pour l’image,
Chapitre 9, semestre de printemps 2007.
Damiano Vitulli, « The 3DS File Structure », http://www.spacesimulator.net/tut4_3dsloader.html
- 35 -