Namide

Skybox (de Blender à Away3D)

Une Skybox est facile à mettre en place avec les moteurs 3D Flash actuel mais sa génération avec les logiciels 3D me paraissait beaucoup plus floue. Après quelques recherches voici la méthode la plus propre que j'ai pu trouver pour créer une carte d'environnement depuis Blender afin de la visionner sous Flash avec Away3D 4.

Rendu final
Pour visionner le rendu final vous devez posseder la versions 10.0 de FlashPlayer.
Télécharger la dernière version de Flashplayer
Skybox rendue avec Away3D 4, déplacez le curseur sur la zone Flash pour faire tourner l'espace.

Il existe bien des manières de générer des Skybox ainsi que beaucoup de librairies 3D pour les afficher en Flash. Celle que nous allons voir n'est pas forcément la meilleure technique mais elle nous permet de voir cette fonctionnalité avec un logiciel et une librairie Open Source, et ça, c'est la classe !
Petit rappel, la version 4 d'Away 3D nous permet d'utiliser l'accélération matérielle de l'ordinateur donc des rendus 3D beaucoup plus fluides et détaillés. Pour cela il faut au minimum FlashPlayer 11 (SDK Flex 4.5).

De la même manière que pour l'article sur la modélisation 3D & Flash nous n'allons pas détailler chaque étape car des tutoriels le font déjà très bien. Nous nous attarderons par contre sur la partie ActionScript 3 qui décrit l'adaptation de l'image pour l'utiliser avec Away3D 4.

Avantages de cette technique de génération de skybox :
- Cette méthode est très propre, pas de caméras supplémentaires et pas besoin d'adapter l'image générée avec un logiciel de retouche 2D.

Désavantages de cette technique :
- Elle utilise le moteur de rendu natif de Blender, celui-ci n'est pas celui qui offre le meilleur rendu. Yafaray par exemple, ajouté à Blender, produit de meilleurs rendus.
- Certaines fonctionnalités, comme les lumière de type Area, génèrent un rendu qui dépend du point de vue, dans ce cas particulier les bords des faces de la carte d'environnement ne correspondent plus.

I. Générer notre carte d'environnement depuis blender

Ce tutoriel décrit comment faire la Skybox en partant de deux photos. Néanmoins si vous avez déjà conçu votre espace dans Blender (j'utilise la version 2.58a) voici comment procéder pour générer votre carte d’environnement :

- Créez un cube positionné au point de vue que vous souhaitez pour votre Skybox : menu Add > Mesh > Cube.

Interface de Blender : ajout d'un cube
Ajouter un cube sur la scène. Avec cette version de Blender on peut aussi utiliser le raccourcis Shift + A. Sur des versions plus ancienne le racourcis était Ctrl + barre d'espace.

- Sélectionnez votre cube puis dans la fenêtre Properties, onglet Material, créez un nouveau matériau en cochant Shadeless (partie Shading).

Interface de Blender : créer un cube
Ajouter un matériau pour notre cube.
Interface de Blender : désactiver les ombres
Cocher Shadeless permet de désactiver les ombres de notre cube.

- Votre cube sélectionné rendez vous à l'onglet Texture afin de créer une nouvelle texture, change le type pour Environment Map.

Interface de Blender : ajout d'une texture
Créer une texture pour notre cube.
Interface de Blender : ajout d'une carte d'environement
Sélectionner un type Environment Map pour notre texture.

- Dans la partie Environment Map de la texture cochez Static, sélectionnez votre cube dans le Viewpoint Object puis sélectionnez comme résolution une puissance de 2 (seules tailles acceptés par la carte graphique) : 256, 512 ou 1024 par exemple.

Interface de Blender : paramètres de la carte d'environement
Ajustements des paramètres de la carte d'environnement.

- Lancez un rendu (F12) puis sauvegardez votre Environment Map depuis votre texture, partie Environment Map, Save Environment Map.

Interface de Blender : sauvegarder la carte d'environement
Exporter le fichier image représentant notre carte d’environnement.

Nous avons enfin la texture utilisable pour notre skybox !

Skybox générée avec Blender
Une skybox générée avec Blender : correspondances des faces.

II. Analyser notre Skybox dans Flash afin de préparer les faces pour Away3D 4

Away3D 4 a besoin de 6 faces séparées pour générer une Skybox. Chaque face doit avoir comme hauteur et largeur une puissance de deux. Par exemple 128, 256, 512 ou 1024 pixels. Après avoir chargé la texture générée par Blender dans Flash, il faut la découper pour en tirer chaque face. Etant donné que notre texture contient 6 faces, 3 en largeur et 2 en hauteur nous savons que chaque face obtenu mesure 1/3 de la largeur et 1/2 de la hauteur de la texture totale.

La méthode copyPixels([...]):void de la classe BitmapData nous permet de copier des parties d'image, elle va donc nous être utile. Il suffit de changer le rectangle source passé en argument pour chaque nouvelle face. Voici par exemple une methode permettant d'obtenir la face supérieure de la Skybox depuis la texture originale :

// texture originale générée depuis Blender
var _skyBoxBD:BitmapData;

function getFaceTop():BitmapData
{
    // initialisation de la face, attention les dimensions doivent être une puissance de 2
    var faceTop:BitmapData = new BitmapData(	int(_skyBoxBD.width / 3),
                                                int(_skyBoxBD.height / 2),
                                                false,
                                                0x000000 );

    // emplacement et taille de la zone à copier sur la texture originale
    var rect:Rectangle = new Rectangle(rect.width, rect.height, faceTop.width, faceTop.height );

    // copie de la zone de la texture sur la face
    faceTop.copyPixels( _skyBoxBD, rect, new Point(0, 0) );

    return faceTop;
}

III. Une skybox avec Away3D 4

La Skybox sous Away3D 4 contient un objet CubeMap, celui-ci est composé de nos 6 faces. Il faut les passer en argument au format BitmapData dans cet ordre : droite, gauche, dessus, dessous, devant puis derrière.

Ci-dessous le script complet en ActionScript 3, du chargement de la carte d'environnement jusqu'à l'affichage de la Skybox.

package
{
	import away3d.cameras.Camera3D;
	import away3d.containers.Scene3D;
	import away3d.containers.View3D;
	import away3d.materials.utils.CubeMap;
	import away3d.primitives.SkyBox;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.net.URLRequest;

	/**
	 * Skybox rendu par Away 4 depuis une texture d'environment map générée par Blender
	 *
	 * @author Damien
	 */
	public class Main extends Sprite
	{
		// Texture de la skybox
		protected var _skyBoxDiffuseBD:BitmapData;

		// Eléments 3D
		protected var _scene:Scene3D;
		protected var _view:View3D;
		protected var _skybox:SkyBox;
		protected var _camera:Camera3D;

		// Constantes pour repérer les faces de la skybox
		public const LEFT:String = "left";
		public const BACK:String = "back";
		public const RIGHT:String = "right";
		public const BOTTOM:String = "bottom";
		public const TOP:String = "top";
		public const FRONT:String = "front";

		public function Main():void
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(e:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);

			initWorld();

			// Charge la texture
			var loadDiffuse:Loader = new Loader();
			loadDiffuse.contentLoaderInfo.addEventListener( Event.COMPLETE, onSkyboxDiffuseLoaded );
			loadDiffuse.load( new URLRequest("pictures/environment_map.png") );

		}

		private function onSkyboxDiffuseLoaded(e:Event):void
		{
			_skyBoxDiffuseBD = ((e.target as LoaderInfo).content as Bitmap).bitmapData;
			initSkybox();
		}

		// Bases pour initialiser Away3D 4
		protected function initWorld():void
		{
			_view = new View3D();
			_view.backgroundColor = 0x000000;
			_view.antiAlias = 2;
			_view.width = stage.stageWidth;
			_view.height = stage.stageHeight;

			_scene = new Scene3D();
			_view.scene = _scene;

			_camera = new Camera3D();
			_view.camera = _camera;

			addChild(_view);
		}

		// Création de l'objet 3D Skybox
		protected function initSkybox():void
		{
			// Passage des 6 faces dans la CubeMap puis implémentation d'une Skybox depuis la CubeMap
			var cubeMap:CubeMap = new CubeMap( 	getSkyboxFace(RIGHT),
												getSkyboxFace(LEFT),
												getSkyboxFace(TOP),
												getSkyboxFace(BOTTOM),
												getSkyboxFace(FRONT),
												getSkyboxFace(BACK) );
			_skybox = new SkyBox(cubeMap);
			_scene.addChild(_skybox);

			addEventListener(Event.ENTER_FRAME, refresh, false, 0, true);
		}

		// Méthode qui renvoie le Bitmap de la face demandée
		// Cette méthode se base sur un BitmapData généré par Blender :
		// les faces ont des positions constantes
		protected function getSkyboxFace(faceName:String):BitmapData
		{
			var faceBD:BitmapData = new BitmapData( int(_skyBoxDiffuseBD.width / 3),
													int(_skyBoxDiffuseBD.height / 2),
													false,
													0x000000 );
			var rect:Rectangle = new Rectangle(0, 0, faceBD.width, faceBD.height );
			const pt0:Point = new Point(0, 0);

			switch(faceName)
			{
				case LEFT :
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
				case BACK :
					rect.x = rect.width;
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
				case RIGHT :
					rect.x = rect.width * 2;
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
				case BOTTOM :
					rect.y = rect.height;
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
				case TOP :
					rect.x = rect.width;
					rect.y = rect.height;
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
				case FRONT :
					rect.x = rect.width * 2;
					rect.y = rect.height;
					faceBD.copyPixels( _skyBoxDiffuseBD, rect, pt0 );
					break;
			}
			return faceBD;
		}

		// Mouvement de la skybox basé sur la position de la souris
		protected function refresh(e:Event = null):void
		{
			_camera.rotationY += (mouseX - stage.stageWidth * 0.5) * 0.01;
			_camera.rotationX += (mouseY - stage.stageHeight * 0.5) * 0.01;
			_view.render();
		}
	}
}