Blog sur Flash,Flex,Papervision,A.I.R., …

Réaliser une visionneuse de panorama VR en AS3

7 juin 2007 par PeZ

Ce premier article se consacre à la réalisation de visionneuses en Flash qui exploitent des images panoramiques

I > Type d'images panoramiques

Les photos VR, appelées parfois « Photographies Immersives », sont des images panoramiques qui , lorsqu'elles sont exploitées dans une visionneuse spécifique, permettent au spectateur de se mettre au centre de l'environnement, de tourner autour de soi et de regarder dans toutes les directions.

En 2000, Apple a lancé son fameux QTVR qui reste aujourd'hui une des solutions les plus utilisées pour la visualisation de panoramiques. Nous allons voir comment il est possible de réaliser le même type d'application mais qui tourne avec le player Flash.

Tout d'abord, regardons les différents types d'images panoramiques qui existent :

- Planaire : Ne sont pas destinées à être utilisé par un player VR car elles ne donnent pas l'illusion de perspective.

- Cylindrique : Les panoramas cylindriques proviennent de l'assemblage de plusieurs clichés pris autour d'un axe fixe et permettent un champ de vision de 360° horizontalement et environ 60° verticalement. Idéal pour observer un environnement extérieur.

- Sphérique : Les panoramas sphériques permettent d'obtenir une immersion totale puisque le champ de vision est global ,360° en horizontal et 360° en vertical. Ils sont donc très utiles pour les environnements intérieurs.

- Cubique : Offrent aussi un champ de vision global. Il ne s'agit plus ici d'une seule image mais de 6 ,qui sont projetées sur un cube.

(Vous trouverez une représentation visuelle de ces types de panoramas ,qui sera peut-être plus parlante , ici)

II> Création d'images panoramiques

Le but ici n'étant pas d'expliquer comment réaliser ce type d'images, je vous laisse donc le soin de consulter les sites spécialisés sur ce sujet.

Après avoir parcouru quelques uns de ces sites, je n'ai malheureusement pas trouvé de scènes disponibles dans les 3 types qui nous intéressent et qui auraient pu servir d'exemple. Mais ce n'est pas grave, cela va nous permettre de s'intéresser à une autre source pour nos images panoramiques, les images de synthèse.

Nous allons utiliser ici 3DSMax afin de modéliser une scène intérieure et de l'exporter sous forme de panoramique cylindrique, sphérique et cubique. Là encore, il ne s'agit pas de faire un tutorial sur la modélisation , le mapping ou l'éclairage dans 3DS. Si vous n'y connaissez pas grand chose, les rendus sont disponibles en téléchargement.

Créez votre pièce, placez une caméra libre au milieu de celle-ci, à une hauteur d'environ d'1m60 et qui fait face à un des murs. Nous allons utiliser l'exporteur de panorama pour réaliser nos images. Pour le trouver, il faut aller dans l'onglet 'Utilitaires' -> 'Autres' -> 'Exportation de panorama' (panorama exporter dans le version us).

Un nouveau menu s'affiche alors. Cliquer sur 'Rendu' , une nouvelle fenêtre devrait apparaître.

Exporteur panorama

Nous allons prendre 1024*512 comme résolution de sortie et 36 comme largeur d'ouverture.
Cliquez alors sur 'Rendu' après s'être assuré d'avoir choisi la bonne caméra dans la liste.

Exporteur panorama

Le rendu effectué , la visionneuse de 3DS s'ouvre automatiquement. Il s'agit de ce type de visionneuse que nous allons réaliser en AS3.
Dans le menu fichier de la visionneuse, cliquez sur exporter. 3 options sont alors disponibles : 'Exporter cylindre', 'Exporter sphère', 'Exporter QuicktimeVR'.
Les 2 premières nous intéressent particulièrement. Faites alors une exportation en cylindre et une autre en sphère.

Exporteur panorama

Pour le rendu de type cubique, il faut utiliser une autre méthode que j'ai trouvé ici
Commencez par créer une boite au milieu de votre scène. Puis allez dans l'éditeur de matériau , sélectionnez un matériau vide et choisissez la texture réflexion/réfraction dans l'emplacement 'réflexion' (Cliquez sur le bouton 'Aucun' pour choisir la texture).

Dans les paramètres réflexion/réfraction, choisissez l'option 'Du fichier' avec une taille de 1024. Indiquez aussi un nom de préfixe de fichier ('rendu_cubique' par ex,) en cliquant sur l'emplacement 'Vers Fichier:'

Enfin cliquez sur le bouton 'Choisir Objet et rendu textures' afin d'obtenir le rendu en 6 faces.

Voilà, nous disposons maintenant de nos 3 exemples d'images panoramiques, il ne reste plus qu'à réaliser une visionneuse pour chacune d'elles :)

Les fichiers d'exemples sont téléchargeables ici

III> Réalisation des visionneuses

Afin de réaliser nos différentes visionneuses, nous allons nous servir du moteur 3D Papervision3D qui nous permettra de créer nos sphère, cylindre et cube sur lesquels seront appliquées les textures fraîchement crées.

Pour l'installation des sources, rendez-vous ici ou

Pour chacune des visionneuses, la démarche sera identique :

- Créer le monde 3D
- Ajouter notre objet 3D
- Appliquer l'image panoramique sur cet objet
- Ajouter les comportements de la souris

Visionneuse pour les images de type cylindriques

  1. Création du monde 3D

Créez un nouveau projet ActionScript ou un document Flash selon l'IDE que vous utilisez et donnez lui une taille de 800 * 512. Ajoutez ensuite le code de base afin de créer un monde 3D Grâce à Papervision3D :

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.     import flash.events.Event;
  5.    
  6.     import org.papervision3d.cameras.FreeCamera3D;
  7.     import org.papervision3d.scenes.MovieScene3D;
  8.  
  9.     [SWF(width='800',height='512',backgroundColor='0x000000',frameRate='30')]
  10.    
  11.     public class VisionneuseCylindre extends Sprite
  12.     {
  13.         private var container :Sprite;
  14.         private var scene :MovieScene3D;
  15.         private var camera :FreeCamera3D;
  16.            
  17.         public function VisionneuseCylindre()
  18.         {
  19.             container = new Sprite();
  20.            
  21.             container.x = stage.stageWidth/2;
  22.             container.y = stage.stageHeight/2;
  23.            
  24.             addChild( container );
  25.                                    
  26.  
  27.             scene = new MovieScene3D( container );
  28.            
  29.  
  30.             camera = new FreeCamera3D();
  31.             camera.z = -500;
  32.             camera.zoom = 5;
  33.            
  34.             stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
  35.            
  36.         }
  37.        
  38.         private function onEnterFrame( event: Event ): void
  39.         {
  40.            
  41.             scene.renderCamera( camera );
  42.  
  43.         }
  44.  
  45.  
  46.     }
  47.    
  48. }

  1. Ajout du cylindre

Pour ajouter un cylindre, nous allons nous servir de la primitive Cylinder :

Actionscript:
  1. package
  2. {
  3.     ...
  4.     import org.papervision3d.objects.Cylinder;
  5.     import org.papervision3d.materials.WireframeMaterial;
  6.  
  7.         ...
  8.    
  9.     public class VisionneuseCylindre extends Sprite
  10.     {
  11.         ...
  12.        
  13.         private var cylindre:Cylinder;
  14.            
  15.         public function VisionneuseCylindre()
  16.         {
  17.                  ...
  18.            
  19.             cylindre = new Cylinder( new WireframeMaterial(0xFF0000), 100, 200, 16, 1);
  20.             scene.addChild( cylindre );
  21.  
  22.             stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
  23.            
  24.         }
  25.        
  26.         private function onEnterFrame( event: Event ): void
  27.         {
  28.            
  29.             scene.renderCamera( camera );
  30.  
  31.         }
  32.  
  33.  
  34.     }
  35.    
  36. }

Le premier paramètre permet de spécifier la texture a appliquer à l'objet. J'ai pris ici un rendu en fil de fer (Wireframe). Les 2 paramètres suivants représentent le rayon et la hauteur du cylindre. Les 2 derniers sont le nombre de segments horizontaux et verticaux.

Par défaut l'objet est placé aux coordonnées (0,0,0). Comme nous avons spécifié que la scène 3D soit centrée au milieu du stage :

Actionscript:
  1. container.x = stage.stageWidth/2;
  2. container.y = stage.stageHeight/2;

Le cylindre est alors visible au centre du document. Comme la caméra est placée à une distance de -500 , vous devriez apercevoir le cylindre comme sur cet exemple (j'ai ajouté volontairement un légère rotation)

Exemple

  1. Ajout de l'image panoramique au cylindre

Pour appliquer une image comme texture à un objet 3D, il existe 3 solutions qui sont la classe BitmapMaterial, BitmapFileMaterial, BitmapAssetMaterial.

- BitmapMaterial permet d'utiliser une image embarquée dans le swf.
- BitmapFileMaterial permet de charger un fichier externe.
- BitmapAssetMaterial permet d'utiliser une image de la librairie Flash.

Nous allons utiliser ici BitmapFileMaterial pour que les personnes qui n'utilisent pas l'IDE Flash ne soient pas lesés ;)

Créez un dossier 'assets' à la racine de votre projet et déposez l'image panoramique cylindrique.

Actionscript:
  1. import org.papervision3d.materials.BitmapFileMaterial;
  2. ...
  3. private static const CHEMIN_IMAGES:String = "../assets/";
  4. ...
  5. var texture:BitmapFileMaterial = new BitmapFileMaterial(VisionneuseCylindre.CHEMIN_IMAGES+"rendu_cylindrique.jpg");
  6. texture.doubleSided = true;
  7. texture.smooth = true;
  8. ....

Par défaut la texture est placée à l'exterieur de l'objet, c'est pourquoi on utilise la variable doubleSided pour que cette texture soit visible aussi depuis l'interieur du cylindre. Il ne faut pas oublier de placer maintenant la caméra au centre du cylindre:

Actionscript:
  1. camera.z = 0;

D'agrandir le zoom :

Actionscript:
  1. camera.zoom = 8;

Et d'ajouter une simple rotation pour le moment :

Actionscript:
  1. private function onEnterFrame( event: Event ): void
  2. {
  3.     camera.rotationY += 1;
  4.     scene.renderCamera( camera );
  5. }

On peut alors constater que l'image est inversée par rapport à la réalité. C'est normal puisqu'elle est appliquée sur l'exterieur du cylindre. Pour rétablir la vue originale, il suffit de faire un mirroir sur l'image grâce à un logiciel de retouche photo.

Notons aussi qu'il est aussi possible de placer la caméra juste devant le cylindre et de faire tourner le cylindre plutôt que la caméra. Dans ce cas là, il n'est pas nécessaire de retoucher à l'image panoramique et d'utiliser le mode doubleSided.

  1. Ajout des comportements de la souris

Rien de trop compliqué ici. On fait tourner la caméra selon la position du curseur de la souris dans le container.

Le code source final:

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.     import flash.events.Event;
  5.     import flash.utils.getTimer;
  6.    
  7.     import org.papervision3d.cameras.FreeCamera3D;
  8.     import org.papervision3d.scenes.MovieScene3D;
  9.    
  10.     import org.papervision3d.objects.Cylinder;
  11.     import org.papervision3d.materials.BitmapFileMaterial;
  12.  
  13.  
  14.     [SWF(width='800',height='512',backgroundColor='0x000000',frameRate='30')]
  15.    
  16.     public class VisionneuseCylindre extends Sprite
  17.     {
  18.        
  19.         private static const CHEMIN_IMAGES:String = "../assets/";
  20.        
  21.         private var container :Sprite;
  22.         private var scene :MovieScene3D;
  23.         private var camera :FreeCamera3D;
  24.         private var cylindre:Cylinder;
  25.  
  26.  
  27.         public function VisionneuseCylindre()
  28.         {
  29.             container = new Sprite();
  30.            
  31.             container.x = stage.stageWidth/2;
  32.             container.y = stage.stageHeight/2;
  33.            
  34.             addChild( container );
  35.                                    
  36.  
  37.             scene = new MovieScene3D( container );
  38.            
  39.  
  40.             camera = new FreeCamera3D();
  41.             camera.z = 0;
  42.             camera.zoom = 8;
  43.  
  44.            
  45.             var texture:BitmapFileMaterial = new BitmapFileMaterial(VisionneuseCylindre.CHEMIN_IMAGES+"rendu_cylindrique.jpg");
  46.             texture.doubleSided = true;
  47.             texture.smooth = true;
  48.            
  49.            
  50.             cylindre = new Cylinder( texture , 100, 200, 16, 16);
  51.             scene.addChild( cylindre );
  52.  
  53.             stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
  54.            
  55.         }
  56.        
  57.         private function onEnterFrame( event: Event ): void
  58.         {
  59.             camera.rotationY += (container.mouseX)/60;
  60.             scene.renderCamera( camera );
  61.         }
  62.  
  63.  
  64.     }
  65.    
  66. }

Le résultat final

Visionneuse pour les image de type sphériques

Une grande partie du code reste identique. Il suffit de remplacer le cylindre par une sphère, d'appliquer la bonne texture et de rajouter une rotation de la caméra sur la hauteur.

En jouant sur la taille de la sphère, le focus et le zoom de la caméra, on arrive à obtenir un panoramique sans trop de déformation.

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.     import flash.events.Event;
  5.    
  6.     import org.papervision3d.cameras.FreeCamera3D;
  7.     import org.papervision3d.scenes.MovieScene3D;
  8.    
  9.     import org.papervision3d.objects.Sphere;
  10.     import org.papervision3d.materials.BitmapFileMaterial;
  11.  
  12.  
  13.     [SWF(width='800',height='512',backgroundColor='0x000000',frameRate='30')]
  14.    
  15.     public class VisionneuseSphere extends Sprite
  16.     {
  17.        
  18.         private static const CHEMIN_IMAGES:String = "../assets/";
  19.         private static var MAX_X_ROTATION:int = 50;
  20.        
  21.         private var container :Sprite;
  22.         private var scene :MovieScene3D;
  23.         private var camera :FreeCamera3D;
  24.        
  25.         private var sphere:Sphere;
  26.        
  27.        
  28.            
  29.         public function VisionneuseSphere()
  30.         {
  31.             container = new Sprite();
  32.            
  33.             container.x = stage.stageWidth/2;
  34.             container.y = stage.stageHeight/2;
  35.            
  36.             addChild( container );
  37.                                    
  38.  
  39.             scene = new MovieScene3D( container );
  40.            
  41.  
  42.             camera = new FreeCamera3D();
  43.             camera.z = 0;
  44.             camera.zoom = 5;
  45.             camera.focus = 80;
  46.  
  47.            
  48.             var texture:BitmapFileMaterial = new BitmapFileMaterial(VisionneuseSphere.CHEMIN_IMAGES+"rendu_spherique.jpg");
  49.             texture.doubleSided = true;
  50.             texture.smooth = true;
  51.            
  52.            
  53.             sphere = new Sphere( texture , 1000, 16, 16);
  54.             scene.addChild( sphere );
  55.  
  56.             stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
  57.            
  58.         }
  59.        
  60.         private function onEnterFrame( event: Event ): void
  61.         {
  62.            
  63.             camera.rotationY += (container.mouseX)/60;
  64.             camera.rotationX -= (container.mouseY)/60;
  65.            
  66.            
  67.             //on limite la rotation vertivale
  68.             if(camera.rotationX> VisionneuseSphere.MAX_X_ROTATION)
  69.             {
  70.                 camera.rotationX = VisionneuseSphere.MAX_X_ROTATION;
  71.             }
  72.             if(camera.rotationX <-VisionneuseSphere.MAX_X_ROTATION)
  73.             {
  74.                 camera.rotationX = -VisionneuseSphere.MAX_X_ROTATION;
  75.             }
  76.                    
  77.                    
  78.             scene.renderCamera( camera );
  79.            
  80.         }
  81.  
  82.  
  83.     }
  84.    
  85. }

Le résultat

Visionneuse pour les images de type cubiques

Pour cette visionneuse, il faut créer 6 plans qu'on dispose de telle sorte qu'ils forment un cube. On applique ensuite la texture correspondante à chacune des faces du cube.

Actionscript:
  1. package
  2. {
  3.     import flash.display.Sprite;
  4.     import flash.events.Event;
  5.    
  6.     import org.papervision3d.cameras.FreeCamera3D;
  7.     import org.papervision3d.scenes.MovieScene3D;
  8.    
  9.     import org.papervision3d.objects.Plane;
  10.     import org.papervision3d.materials.BitmapFileMaterial;
  11.  
  12.  
  13.     [SWF(width='800',height='512',backgroundColor='0x000000',frameRate='30')]
  14.    
  15.     public class VisionneuseCube extends Sprite
  16.     {
  17.        
  18.         private static const CHEMIN_IMAGES:String = "../assets/";
  19.         private static var MAX_X_ROTATION:int = 50;
  20.         private static var taille:Number = 5000;
  21.        
  22.         private var container :Sprite;
  23.         private var scene :MovieScene3D;
  24.         private var camera :FreeCamera3D;
  25.        
  26.         private var plan:Plane;
  27.        
  28.        
  29.            
  30.         public function VisionneuseCube()
  31.         {
  32.             container = new Sprite();
  33.            
  34.             container.x = stage.stageWidth/2;
  35.             container.y = stage.stageHeight/2;
  36.            
  37.             addChild( container );
  38.                                    
  39.  
  40.             scene = new MovieScene3D( container );
  41.            
  42.  
  43.             camera = new FreeCamera3D();
  44.             camera.zoom = 1;
  45.             camera.focus = 400;
  46.             camera.z = 0;
  47.            
  48.             var centre:Number = taille/2;
  49.  
  50.             //Face Avant
  51.             var texture:BitmapFileMaterial = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_FR.jpg");
  52.            
  53.             plan = new Plane( texture , taille, taille, 16, 16);
  54.             plan.z = centre;
  55.             scene.addChild( plan );
  56.            
  57.             //Face Droite
  58.             texture = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_RT.jpg");
  59.            
  60.             plan = new Plane( texture , taille, taille, 16, 16);
  61.             plan.x = -centre;
  62.             plan.rotationY = -90;
  63.             scene.addChild( plan );
  64.            
  65.             //Face Gauche
  66.             texture = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_LF.jpg");
  67.            
  68.             plan = new Plane( texture , taille, taille, 16, 16);
  69.             plan.x = centre;
  70.             plan.rotationY = 90;
  71.             scene.addChild( plan );
  72.            
  73.             //Face Bas
  74.             texture = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_DN.jpg");
  75.            
  76.             plan = new Plane( texture , taille, taille, 16, 16);
  77.             plan.y = -centre;
  78.             plan.rotationX = -90;
  79.             scene.addChild( plan );
  80.            
  81.             //Face Haut
  82.             texture = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_UP.jpg");
  83.            
  84.             plan = new Plane( texture , taille, taille, 16, 16);
  85.             plan.y = centre;
  86.             plan.rotationX = 90;
  87.             scene.addChild( plan )
  88.            
  89.             //Face Arriere
  90.             texture = new BitmapFileMaterial(VisionneuseCube.CHEMIN_IMAGES+"rendu_cubique_BK.jpg");
  91.            
  92.             plan = new Plane( texture , taille, taille, 16, 16);
  93.             plan.z = -centre;
  94.             plan.rotationY = -180;
  95.            
  96.             scene.addChild( plan );   
  97.  
  98.             stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
  99.            
  100.         }