1. Introduction

Ce tutoriel permettra à l'utilisateur débutant de comprendre l'utilisation basique de la caméra. Il nécessitera l'implémentation d'une gestionnaire d'événements pour le contrôle de la caméra au clavier. Pour plus d'information sur celui-ci, il faudra se référer au tutoriel portant sur le sujet.

Le programme développé permettra le changement des paramètres utilisés par la caméra.

2. Classes et fonctions utilisées

Classe :

Osg::Camera : permet la création et la manipulation basique de la caméra.

Fonctions :

setProjectionMatrixAsPerspective(double fovy, double aspectRatio, double zNear, double zFar) : cette fonction permet de régler les différents paramètres de la caméra, modifiant ainsi la vue.

setViewMatrixAsLookAt(const osg::Vec3d &eye, const osg::Vec3d &center, const osg::Vec3d &up) : cette fonction permet de régler l'orientation et la position de la caméra.

void setClearColor(const osg::Vec4 &color)  : cette fonction permet de changer la couleur par défaut du fond.

3. Mise en Pratique

3-A. Mise en place du programme principal

Afin d'implémenter le programme, il faut une scène. Elle sera très simple : un cube. Cette scène est implémentée dans une fonction dédiée à cela et qui peut évidemment être mise dans un fichier à part, ce qui est conseillé pour les programmes plus importants. Pour plus de détails sur l'implémentation de cette scène, se référer aux autres tutoriels et à la notion de graphe de scène. Il ne faut pas non plus oublier de faire les inclusions nécessaires au fonctionnement du programme :

 
Sélectionnez
#include <osgViewer/Viewer> 
#include <osg/Group> 
#include <osg/Geode> 
#include <osg/ShapeDrawable> 
#include <osg/Camera> 
#include <osgGA/GUIEventHandler> 
#include <iostream> 
 
Sélectionnez
osg::Node* CreateScene() 
{ 
     // Création de la scène : cube 
     /******************************************************************************** 
     **********************************************************************************/ 
     // Déclare un groupe servant de nœud principal : 
     osg::Group* root = new osg::Group(); 
     // Création du cube 
     osg::Box* unitCube = new osg::Box( osg::Vec3(0,0,0), 1.0f); 
     // Met le cube dans un drawable pour pouvoir l'ajouter ensuite à la scène 
     osg::ShapeDrawable* unitCubeDrawable = new osg::ShapeDrawable(unitCube); 
     // Déclare une instance de la classe Geode 
     osg::Geode* basicShapesGeode = new osg::Geode(); 
     // Ajoute unitCubeDrawable à ce dernier... 
     basicShapesGeode->addDrawable(unitCubeDrawable); 
     // Ajoute le Geode à la scène 
     root->addChild(basicShapesGeode); 
     return root; 
} 

Avec notre scène, il est possible maintenant de mettre en place la fonction principale, incomplète dans cette étape. Elle aura juste pour l'instant le rôle de mettre la scène dans la fenêtre de notre programme.

 
Sélectionnez
int main(int argc, char* argv[]) 
{ 
     // Création du viewer 
     /********************************************************************************* 
     *********************************************************************************/ 
     osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer; 

     // Création de la fenêtre de taille 512x512 placée en 100 100 
     viewer->setUpViewInWindow(100, 100, 512, 512); 

     // Caméra 
     /********************************************************************************** 
     **********************************************************************************/ 
     // Ici sera mise en place la caméra 

     // Définit la scène comme celle du viewer 
     viewer->setSceneData(CreateScene()); 

     // Événements 
     /*********************************************************************************** 
     **********************************************************************************/ 
     // Ici sera mis en place le gestionnaire d'événements 

     // Exécution du programme 
     /******************************************************************************* 
     *********************************************************************************/ 
     viewer->realize(); 
     while(!viewer->done()) 
     { 
          viewer->advance(); 
          viewer->eventTraversal(); 
          viewer->updateTraversal(); 
          // Ici sera déterminée la caméra 
          viewer->renderingTraversals(); 
     } 
} 

Comme vu dans le code plus haut il manque notamment le gestionnaire d'événements. Aucun détail ne sera donné sur son implémentation dans ce tutoriel. Pour plus d'information se référer au tutoriel sur le sujet. Voici donc les fonctions et classes qu'il sera nécessaire d'ajouter en début de ce programme :

 
Sélectionnez
// Gestion des événements 
/***************************************************************************************** 
*****************************************************************************************/ 
class gestionnaireEvenements : public osgGA::GUIEventHandler 
{ 
public: 
     virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&); 
}; 

Ceci est la classe dérivée permettant la gestion des événements. Le gestionnaire se présentera, juste en dessous de cette classe, ainsi :

 
Sélectionnez
bool gestionnaireEvenements::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa) 
{ 
     switch(ea.getEventType()) 
     { 
          case(osgGA::GUIEventAdapter::KEYDOWN): 
          { 
               switch(ea.getKey()) 
               { 
                    case 'q': 
                         cible.set(cible.x()+0.1, cible.y(), cible.z()); 
                         oeil.set(oeil.x()+0.1, oeil.y(), oeil.z()); 
                         return false; 
                         break; 
                    case 'Q': 
                         cible.set(cible.x()-0.1, cible.y(), cible.z()); 
                         oeil.set(oeil.x()-0.1, oeil.y(), oeil.z()); 
                         return false; 
                         break; 
                    case 'd': 
                         cible.set(cible.x(), cible.y()+0.1, cible.z()); 
                         oeil.set(oeil.x(), oeil.y()+0.1, oeil.z()); 
                         return false; 
                    break; 
                    case 'D': 
                         cible.set(cible.x(), cible.y()-0.1, cible.z()); 
                         oeil.set(oeil.x(), oeil.y()-0.1, oeil.z()); 
                         return false; 
                    break; 
                    case 'z': 
                         cible.set(cible.x(), cible.y(), cible.z()+0.1); 
                         oeil.set(oeil.x(), oeil.y(), oeil.z()+0.1); 
                         return false; 
                    break; 
                    case 'Z': 
                         cible.set(cible.x(), cible.y(), cible.z()-0.1); 
                         oeil.set(oeil.x(), oeil.y()-0.1, oeil.z()); 
                         return false; 
                    break; 
                    case 'a' : 
                         near += 0.1; 
                         return false; 
                    break; 
                    case 'A' : 
                         near -= 0.1; 
                         return false; 
                    break; 
                    case 'e' : 
                         far += 0.1; 
                         return false; 
                    break; 
                    case 'E' : 
                         far -=0.1; 
                         return false; 
                    break; 
                    case 's' : 
                         fovy += 5; 
                         return false; 
                    break; 
                    case 'S' : 
                         fovy -= 5; 
                         return false; 
                    break; 
                    case 'x' : 
                         ratio += 0.1; 
                         return false; 
                    break; 
                    case 'X' : 
                         ratio -= 0.1; 
                         return false; 
                    break; 
                    case 'h' : 
                         cout << "commandes : " << endl 
                         << "q/Q -> deplacement en x" << endl 
                         << "d/D -> deplacement en y" <<endl 
                         << "z/Z -> deplacement en z" <<endl 
                         << "a/A -> deplacement du plan de clipping proche"<<endl 
                         << "e/E -> deplacement du plan de clipping loin" <<endl 
                         << "s/S -> changement de l'angle de vue" <<endl 
                         << "x/X -> changement du ration" <<endl; 
                         return false; 
                    break; 
                    default: 
                         return false; 
               } 
          } 
          default: 
               return false; 
     } 
} 

Une fois ce gestionnaire implémenté, il faut ajouter les lignes qui permettent de l'intégrer au programme à l'endroit indiqué plus haut dans la fonction principale :

 
Sélectionnez
// Événements 
/*********************************************************************************** 
*********************************************************************************/ 
// Création de l'objet de gestion d'événements clavier 
gestionnaireEvenements* evenements = new gestionnaireEvenements(); 

// Ajout des événements au viewer 
viewer->addEventHandler(evenements); 

La scène et le gestionnaire d'événements mis en place, il ne manque plus qu'à mettre en place la caméra.

3-B. Mise en place de la caméra

La caméra sous OpenSceneGraph fonctionne de la même manière que sous OpenGL. Ceci ne sera donc que des rappels.

Il faut tout d'abord définir le volume de l'espace vu par la caméra. Pour cela la fonction setProjectionMatrixAsPerspective(double fovy, double aspectRatio, double zNear, double zFar) sera utilisée.

Voici donc le détail des paramètres :

  • fovy : désigne l'angle de vue de la caméra, correspondant au champ de vue que l'utilisateur aura. Il s'agit d'un angle défini en degrés. Il ne peut pas être égal à 180° car la caméra serait alors définie sur un volume infini ;
  • aspectRatio : définit le rapport entre la hauteur et la largeur. Il est en général de 1. D'une autre valeur il peut déformer l'aspect de la scène ;
  • zNear et zFar : définissent les plans proches et lointains entre lesquels se trouve la scène visionnée. L'utilisateur ne verra pas un objet situé plus près de la caméra que le plan proche, ou un objet plus loin que le plan lointain. Leur valeur est donc la distance entre la caméra et le plan.

Maintenant que ces paramètres sont plus clairs, il est temps de mettre la caméra en place. La caméra utilisée est la caméra par défaut. Elle sera définie par des valeurs globales afin que le gestionnaire d'événements puisse y accéder. Dans un programme plus important il serait nécessaire de faire une classe afin de mettre en place des données membres et des accesseurs.

 
Sélectionnez
// Déclaration d'une variable pour les paramètres de la caméra : 
/****************************************************************************************** 
******************************************************************************************/ 
float near = 0.1; 
float far = 0.5; 
float fovy = 45.0; 
float ratio = 1.0; 

Les variables de type osg doivent être précédées de osg:: comme pour toute utilisation d'un espace de nom.

Ensuite il suffit d'appeler la fonction afin d'initialiser la caméra dans le programme

 
Sélectionnez
// Caméra 
/*********************************************************************************** 
**********************************************************************************/ 
// Change la matrice de projection 
viewer->getCamera()->setProjectionMatrixAsPerspective( 
fovy, 
ratio, 
near, 
far 
); 

Une simple fonction prenant en paramètre une couleur suffit pour définir le fond de notre scène à noir :

 
Sélectionnez
// Change la couleur du fond 
viewer->getCamera()->setClearColor(osg::Vec4(0.0, 0.0, 0.0, 0.0)); 

Pour la position initiale de la caméra, la position initiale convient tout à fait. Il n'est donc pas nécessaire de la modifier. Dans le cas contraire il faudrait simplement ajouter la fonction setViewMatrixAsLookAt(const osg::Vec3d &eye, const osg::Vec3d &center, const osg::Vec3d &up) comme il va être fait pour mettre la caméra à jour. Ces paramètres sont les suivants :

  • eye : point 3D qui définit la position de la caméra ;
  • center : point 3D qui définit le point regardé par la caméra.
    Autrement dit, le vecteur défini par les deux points eye et center indique la direction de la caméra.
  • up : point 3D qui définit l'axe vertical de la caméra. En général il est défini selon l'axe vertical du repère.

Afin de pouvoir changer la position de la caméra, il est nécessaire de déclarer des variables globales pour ces paramètres aussi :

 
Sélectionnez
// Déclaration d'une variable pour les paramètres de la caméra : 
/****************************************************************************************** 
******************************************************************************************/ 
osg::Vec3d oeil(0.0, -3.0, 3.0); 
osg::Vec3d cible(0.0, 0.0, 0.0); 
osg::Vec3d normale(0.0, 0.0, 1.0); 

Ensuite, il suffit de mettre ces fonctions dans la boucle d'exécution afin de mettre la caméra à jour à chaque changement. Ces lignes sont à ajouter à l'endroit indiqué plus haut, dans la fonction principale :

 
Sélectionnez
viewer->getCamera()->setProjectionMatrixAsPerspective( 
fovy, 
ratio, 
near, 
far 
); 
viewer->getCamera()->setViewMatrixAsLookAt(oeil, cible, normale); 

Lorsque toutes ces étapes sont effectuées avec succès, il suffit d'appuyer sur les touches indiquées pour voir les effets des paramètres changés !

Pour plus d'information se référer à la doc camera de OpenSceneGraph.

4. Aller plus loin

Ce programme ne propose que des transformations basiques : translations selon les axes du repère. Il est évidement possible de faire des rotations, ou de déplacer la caméra grâce à la souris.

Les rotations : pour le premier point : il faut utiliser les coordonnées polaires. En effet, il est facile de calculer l'angle entre la caméra et le repère grâce aux deux paramètres de visée, et ensuite d'augmenter ou de réduire cet angle. C'est juste une affaire de trigonométrie ! Pour faciliter les calculs il suffit d'annuler une composante, par exemple, la composante verticale.

Le déplacement à la souris : là aussi, ce sont des maths. Le plus simple étant de récupérer les composantes x et y du déplacement de la souris et de leur associer un déplacement ou un angle dans le repère de la scène. Cela nécessite un changement de repère, qui peut lui aussi être réalisé grâce à de la trigonométrie.

Les manipulators : si rien de tout cela ne correspond au résultat et à l'implémentation désirée, il est également possible de séparer le code du déplacement de la caméra et de faire ces transformations grâce à des matrices. Pour cela il existe une classe : les manipulators. Voici le lien de la documentation à ce sujet :

documentation Manipulators

5. Remerciements

Merci à _Max_ et jacques_jean pour les corrections.