Perspective : la programmation 3D facile avec Silverlight 5

Perspective : la programmation 3D facile avec Silverlight 5

La version 5 de Silverlight permet de réaliser des scènes 3D qui exploitent l'accélération matérielle (c'est à dire qui délèguent les calculs d'affichage au processeur graphique ou GPU). Il s'agit en fait d'une intégration de XNA au sein du runtime Silverlight. Comme le souligne Don Burnett dans son article XNA for Web Browsers or Silverlight 5 3D from Mix11 , l'accent est mis sur la portabilité du code XNA et non sur la compatibilité avec l'API 3D haut-niveau de WPF. XNA est une API bas-niveau, destinée au programmeur, et à priori inadaptée à un langage déclaratif comme XAML et aux outils associés. Alors, quid de la collaboration designer / développeur ?

Fort de mon expérience d'encapsulation de l'API 3D de WPF, j'ai créé au sein de la bibliothèque Perspective un jeu de classes pour amener la programmation 3D avec Silverlight 5 à un niveau proche de celui de l'API 3D de WPF et de Perspective pour WPF .

Ce jeu de classes est fourni dans l'assembly Perspective.Wpf3DX.dll pour Silverlight 5, avec son code source.

Téléchargement

Introduction à la 3D

La 3D est une technique qui permet de représenter une scène offrant profondeur, relief et perspective.

Le contenu de la scène est défini au moyen de modèles 3D, dont les faces sont composées de triangles disposant d'une texture (couleur, motif) et d'une matière (qui définit la réaction à l'éclairage). La surface la plus simple représentable en 3D est en effet un triangle.

La position du point de vue et la direction du regard déterminent l'aspect d'une scène 3D. Ces éléments sont gérés par un objet PerspectiveCamera, qui représente une caméra de prise de vue.

Une scène doit contenir un éclairage pour montrer son contenu. Des objets spécifiques permettent de définir la nature de l'éclairage : éclairage ambiant multidirectionnel, éclairage unidirectionnel (similaire aux rayons solaires), éclairage ponctuel, etc.

Le principe d'affichage d'une scène consiste à fournir les données des modèles au GPU puis à appeler une fonction de dessin. Ces opérations doivent ête répétées à chaque rafraîchissement de l'écran (soit en général 30 à 60 fois par seconde). Les informations de dessin sont constituées du tableau des sommets des triangles (vertex buffer), des matrices de projection, des informations sur l'éclairage, des algorithmes de gestion des sommets et des pixels (programmes spécifiques appelés shaders ), etc. Le développement 3D est par essence bas-niveau.

Scène 3D

Le système de coordonnées à trois dimensions de Silverlight présente les caractéristiques suivantes, depuis un point d’observation dont les coordonnées sont positives sur les trois axes :

  • Abscisse X, valeurs positives vers la droite.
  • Ordonnée Y, valeurs positives vers le haut.
  • Profondeur Z, valeurs positives vers l’avant.

Système de coordonnées Silverlight

Il s'agit, comme pour WPF, d'un système "main droite".

Les axes d'un système de coordonnées 3D peuvent en effet être représentés au moyen des doigts d'une main : le pouce représente l'axe des X, l'index l'axe des Y et le majeur l'axe des Z. L'axe des X est généralement dirigé vers la droite et l'axe des Y vers le haut. La direction de l'axe des Z différencie deux systèmes : le système "main droite" et le système "main gauche".

La taille physique de l'unité et la position à l'écran du point d'origine (0, 0, 0) dépendent des paramètres de calcul de la projection à l’écran.

Dans XNA (et donc dans Silverlight), toutes les coordonnées 3D sont définies en type float (contrairement à WPF qui utilise le type double).

La structure XNA Vector3 permet de stocker les coordonnées d’un point, qu’elle expose dans ses propriétés X, Y et Z.

Workshop3DX : un atelier pour découvrir la 3D

Workshop3DX est un contrôle Silverlight 2D de la bibliothèque Perspective qui permet d'afficher facilement une scène 3D et de la manipuler au clavier. Il encapsule un élément DrawingSurface (élément de base de Silverlight 5 pour afficher une scène 3D) et un objet Perspective Scene, qui permet de configurer la caméra, l'éclairage et le modèle 3D. Une caméra et un éclairage sont intégrés par défaut.

Une scène 3D Perspective peut être définie en XAML (objet du présent article) ou en code .NET (C#, Visual Basic, etc.) .

L'exemple XAML suivant affiche un cube au moyen des classes Workshop3DX, Scene et Box de la bibliothèque Perspective :

<UserControl 
  x:Class="PerspectiveDemo.Wpf3DX.DemoPage"
...
  xmlns:p="http://www.codeplex.com/perspective">
  <Grid x:Name="LayoutRoot" Background="#333333">
    <p:Workshop3DX 
      Name="workshop3DX">
      <p:Scene>
        <p:Box />
      </p:Scene>
    </p:Workshop3DX>
  </Grid>
</UserControl>

Scène 3D Perspective

Les différents types de modèles prédéfinis de Perspective sont présentés dans cet article .

Interactivité

Quand le Workshop3DX a le focus, le clavier permet de faire varier le facteur de zoom de la caméra, sa position et sa direction :

  • Les touches Plus et Moins agissent sur le zoom (angle de vue).
  • Les touches fléchées du pavé numérique déplacent la caméra sur un plan horizontal X-Z.
  • Quand la touche [Ctrl] est enfoncée, elles déplacent l’orientation de la caméra selon un plan vertical (flèche haut, flèche bas) ou horizontal (flèche gauche, flèche droite). Quand la touche [Shift] est enfoncée, elles déplacent la caméra en orbite autour du point d'origine sur un plan vertical (flèche haut, flèche bas) ou horizontal (flèche gauche, flèche droite).
  • Les touches 5 ou [Ctrl] Plus du pavé numérique élèvent la position de la caméra. Les touches [Ctrl] 5 ou [Ctrl] Moins la redescendent.

Le clavier est pour le moment la seule interface interactive supportée par le Workshop3DX.

Matérialisation du repère

L'élément Perspective XyzAxis matérialise le repère sur le point d’origine, ce qui facilite la mise au point des modèles. La propriété Length indique la taille de ses axes (en unités 3D).

<p:Workshop3DX 
  Name="workshop3DX">
  <p:Scene>
    <p:XyzAxis Length="3"/>
  </p:Scene>
</p:Workshop3DX>

Repère à 3 unités

La propriété Signed (false par défaut) indique si les axes doivent aussi être affichés sur les valeurs négatives.

Position et taille des modèles

Si nous décrivons une scène au moyen d'un modèle XyzAxis et d'un modèle Box, nous constatons que le cube est positionné sur le point d’origine, et que les dimensions de ses côtés sont de 1 unité 3D.

<p:Scene>
  <p:XyzAxis Length="3"/>
  <p:Box />
</p:Scene>

Position et taille

Tous les modèles 3D de la bibliothèque Perspective sont ainsi par défaut positionnés sur l’origine, avec des dimensions généralement de 1 unité. Ils n’exposent pas de propriété permettant de modifier position et dimensions.

En effet, dans la mesure du possible, le positionnement et le dimensionnement des modèles sont confiés à des transformations 3D de la bibliothèque Perspective qui exploitent le GPU.

Ces transformations 3D permettent de déplacer, réduire, agrandir ou faire pivoter un modèle, voire de lui appliquer une combinaison de ces effets. Elles sont spécifiées dans la propriété Transform du modèle, au moyen notamment des classes Perspective Translation, Scaling ou Rotation. La classe ModelTransformGroup permet quant à elle de regrouper des transformations.

<p:Box>
  <p:Box.Transform>
    <p:ModelTransformGroup>
      <p:Translation OffsetX="-1" OffsetY="0.5" />
      <p:Scaling ScaleZ="2" />

      <p:Rotation Angle="10" Direction="Clockwise" Axis="Z"/>

      <p:Rotation Angle="15" Direction="CounterClockwise"  Axis="Y"/>
    </p:ModelTransformGroup>
  </p:Box.Transform>
</p:Box>

Transformations

Texture

La couleur ou le motif des modèles Perspective sont définis dans leur propriété Texture au moyen des classes ColorTexture ou BitmapTexture. En interne, ces classes exploitent des textures XNA. L'exemple suivant permet de modifier la couleur d'un modèle (jaune par défaut) :

<p:Box>
  <p:Box.Texture>
    <p:ColorTexture R="0.55" G="0.15" B="0.87" A="1"/>
  </p:Box.Texture>
</p:Box>

Couleur

La gestion des textures diffère de celle de WPF qui utilise des pinceaux.

La classe BitmapTexture permet d'utiliser comme texture une image, spécifiée au moyen d'un objet BitmapSource qui aura été préalablement initialisé. Dans la version actuelle de Perspective, il est nécessaire de procéder à cette initialisation par le code .NET.

Par ailleurs, en comparaison avec WPF, l'extensibilité de Silverlight s'avère limitée. Ainsi, il n'est pas possible (à ma connaissance) de récupérer dans le code .NET une référence sur un modèle Perspective nommé au moyen de l'attribut x:Name coté XAML. Une référence doit donc être créée manuellement dans le code .NET pour que l'objet puisse par exemple être manipulé avec une portée globale sur les différents événements de la page. Cette référence peut être initialisée sur l'événement Initialized du modèle.

Dans l'exemple suivant, lors de l'événement Initialized, une image est chargée à partir des ressources embarquées dans l'assembly pour être utilisée comme texture via un BitmapImage (hérité de BitmapSource).

<p:Box Initialized="Box_Initialized" />
// Code-behind :
private void Box_Initialized(object sender, EventArgs e)
{
  Uri uri = new Uri("/Perspective.Demo3D;component/BoxTexture.png", UriKind.Relative);
  StreamResourceInfo sri = Application.GetResourceStream(uri);
  BitmapImage bitmapImage = new BitmapImage();
  bitmapImage.SetSource(sri.Stream);
  var box = (Box)sender;
  box.Texture = new BitmapTexture(bitmapImage);
  box.InvalidateTexture();
}

La méthode InvalidateTexture prévient le moteur d'affichage de Perspective du changement de texture.

Texture image

Matière

La matière d'un modèle définit sa réaction à l'éclairage. Elle se définit dans la propriété Material du modèle au moyen d'un objet ModelMaterial qui dispose des propriétés suivantes (dont les valeurs doivent se situer entre 0 et 1) :

  • Ambientness : facteur de réponse à la lumière ambiante.
  • Diffuseness : facteur de réponse à la lumière directionnelle.
  • Specularness : facteur de brillance (l'intensité du reflet étant réglable au moyen de la propriété Shininess).
  • Emissiveness : facteur d'émission de lumière (un modèle pouvant ainsi se passer d'éclairage).

L'exemple suivant montre comment définir une sphère brillante (au moyen du modèle Perspective Spherical), qui présente des reflets de l'éclairage :

<p:Spherical
  ParallelCount="80">
  <p:Spherical.Texture>
    <p:ColorTexture R="0.55" G="0.15" B="0.87" A="1"/>
  </p:Spherical.Texture>
  <p:Spherical.Material>
    <p:ModelMaterial 
      Diffuseness="1.0"
      Specularness="1" 
      Shininess="0.8" />
  </p:Spherical.Material>
</p:Spherical>

Matière

Animations

Bien que les scènes 3D Perspective puissent être définies avec XAML, les propriétés des classes 3D Perspective ne sont pas des Dependency Properties et ne peuvent pas être animées au moyen du mécanisme classique d'animation de Silverlight. Ceci est dû à l'utilisation sous-jacente de XNA, qui impose un mécanisme d'animation basé l'utilisation de code .NET.

Typiquement, l'animation sera appliquée sur une propriété d'une transformation lors de l'événement Render (émis lors de l'affichage de chaque frame ).

L'exemple suivant anime la rotation d'une sphère autour du point d'origine. Une première transformation de type translation décale la sphère de 4 unités. Une rotation est ajoutée et sa propriété Angle est incrémentée à chaque frame lors de l'événement Render. Au préalable, le modèle est référencé lors de l'événement Initialized.

<p:Spherical
  Initialized="Spherical_Initialized"
  Render="Spherical_Render"
  ParallelCount="80">
  ...
  <p:Spherical.Transform>
    <p:ModelTransformGroup>
      <p:Translation OffsetX="4"/>
    </p:ModelTransformGroup>
  </p:Spherical.Transform>
</p:Spherical>
// Code-behind :
private Spherical _spherical;
private Rotation _sphericalRotation = new Rotation();
private void Spherical_Initialized(object sender, EventArgs e)
{
  _spherical = (Spherical)sender;
  _sphericalRotation.Axis = AxisDirection.Y;
  (_spherical.Transform as ModelTransformGroup).Children.Add(_sphericalRotation);
}
private void Spherical_Render(object sender, Wpf3DX.RenderEventArgs e)
{
  if (_spherical != null)
  {
    _sphericalRotation.Angle++;
    _spherical.InvalidateTransform();
  }
}

La méthode InvalidateTransform prévient le moteur d'affichage de Perspective du changement dans la transformation.

A propos de cet article

Auteur : Olivier Dewit

Historique :

  • 27 octobre 2012 : correction code testure, antialiasing, lien sur les scènes dynamiques .
  • 6 février 2012 : correction code animation.
  • 28 décembre 2011 : validation pour Silverlight 5 RTM.
  • 15 août 2011 : retouches mineures dans le texte.
  • 29 juin 2011 : première publication pour Silverlight 5 bêta.