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 -