Perspective : la programmation 3D facile avec WPF

Perspective : la programmation 3D facile avec WPF

Il y a quelques années, j'ai été impressionné par la facilité de développement 3D avec VPython . Cette bibliothèque permettait de construire des en langage Python modèles 3D à partir d'objets géométriques de base (cube, cylindre, sphère, etc.) ou plus évolués (flèche), et de les animer. La fenêtre de visualisation par défaut permettait de déplacer la caméra autour de la scène, et de zoomer. Il était donc facile d'obtenir en quelques lignes de code une application très spectaculaire. VPython était très utilisé pour illustrer des modèles physiques. Exemple . Documentation en français .

WPF exploite la technologie Direct3D pour fournir des services de réalisation d'interface utilisateur en 2D mais aussi de scènes 3D. On trouve de nombreuses références à ce sujet, comme ce tutoriel , qui décrit les différentes étapes de la création d'un cube. Même si WPF s'avère beaucoup plus facile à programmer que Direct3D, on est encore loin de la productivité de VPython. Il manque à WPF des classes de haut-niveau pour la programmation 3D (cube, cylindre, sphère, etc.). Plusieurs auteurs font ce constat et proposent des solutions (dont aucune ne va cependant aussi loin que VPython) :

Depuis 2006, j'ai développé la bibliothèque Perspective qui offre à WPF des fonctionnalités 3D et une productivité proches de celles de VPython, tout en intégrant des innovations techniques telles que les contrôles 3D interactifs.

La suite de l'article s'applique à la version 2.0 de Perspective, qui requiert .NET 4.0 et Visual Studio 2010.

Principes de base

Obtenir et installer Perspective

Les concepts de base du développement 3D avec WPF ne sont pas détaillés ici, mais le texte contient des liens vers la documentation du SDK .

Pour exploiter les classes de Perspective dans un fichier Xaml, il est nécessaire de définir un espace de nom XML référençant les assemblys de Perspective :

<Page x:Class="PerspectiveDemo.UI.Pages.pWpf3D.Box3D"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:p="http://www.codeplex.com/perspective"
...

Basiquement, une scène 3D est affichée dans un élément Viewport3D . Il faut ensuite configurer la caméra et l'éclairage . Afin de pouvoir déplacer le point de vue et tourner autour du modèle, des rotations de la caméra sur les axes X, Y et Z (RotateTransform3D) peuvent être configurées. Pour simuler un effet de zoom, une mise à l'échelle (ScaleTransform3D) peut être appliquée à la caméra. Ces transformations peuvent être liées à des sliders.

A l'usage, cela s'avère fastidieux et répétitif. Perspective est compatible avec cette approche, mais fournit depuis sa version 0.9 l'élément Workshop3D qui regroupe un Viewport3D, une caméra, l'éclairage et un système de zoom et de déplacement du point de vue.

<Page ...>
    <p:Workshop3D>
        ...
    </p:Workshop3D>
</Page>

Nous pouvons alors instancier notre premier objet 3D visuel : le repère (classe XyzAxis3D) qui nous aidera visuellement à positionner les éléments 3D :

<p:Workshop3D>
    <p:XyzAxis3D />
</p:Workshop3D>

Si nous exécutons maintenant l'application, nous visualisons le repère.

Objet XyzAxis3D

Démo en ligne

Le Workshop3D affiche 3 joysticks semi-transparents qui permettent de faire varier à la souris le zoom de la caméra, sa position et sa direction. Quand la touche Maj est enfoncée, le joystick de position déplace la caméra en orbite autour du point d’origine sur un plan vertical (boutons avant / arrière) ou horizontal (boutons gauche / droit).

La caméra peut être controllée à la souris (boutons des joysticks) ou au clavier (quand le Workshop3D a le focus) :

  • Les touches Plus et Moins agissent sur le facteur de zoom.
  • Les touches fléchées du pavé numérique agissent sur la position de la caméra (sur un plan XZ).
  • Quand la touche Ctrl est enfoncée, elles changent l'orientation de la caméra sur un plan vertical (flèche haut, flèche bas) ou horizontal (flèche gauche, flèche droite).
  • Quand la touche Maj est enfoncée, elles font tourner la caméra autour du point d'origine sur un plan vertical (flèche haut, flèche bas ou touches correspondantes du joystick de position) ou horizontal (flèche gauche, flèche droite ou touches correspondantes du joystick de position).
  • 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 rabaissent.

Workshop3D

XyzAxis3D dispose des propriétés Radius, rayon du corps de chaque axe, Length, qui permet d'indiquer la taille du repère, et Signed qui affiche les axes pour les valeurs négatives.

<p:XyzAxis3D Length="5" Signed="True"/>

Objet XyzAxis3D

Nous pouvons maintenant créer nos modèles 3D. Commençons par un cube avec la classe Box3D :

<p:Workshop3D>
    <p:XyzAxis3D Length="3.0"/>
    <p:Box3D />
</p:Workshop3D>

Objet Box3D

Démo en ligne

Tous les objets Wpf3D disposent d'une texture par défaut, modifiable dans la propriété Material.

Notre cube est positionné à l'origine du repère, et la taille des arêtes est de 1 unité. A la lecture de la documentation, il peut paraître surprenant qu'aucune propriété ne nous permette de modifier la position et la taille du cube. Mais c'est tout à fait normal. WPF utilise le processeur graphique qui supporte des instructions pour transformer la position ou l'aspect des modèles. Modifier la position et la taille du cube dans notre code reviendrait à utiliser le processeur central au lieu du processeur graphique. Dans la logique de WPF, nous allons donc utiliser des transformations 3D assignées à la propriété Transform (toujours avec la possibilité de définir la transformation de façon centralisée dans la zone des ressources) :

...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Transform3DGroup x:Key="ModelTransform">

        <TranslateTransform3D OffsetX="1.0" OffsetY="-1.0" OffsetZ="0.5" />

        <ScaleTransform3D ScaleX="1.0" ScaleY="1.0" ScaleZ="3.0" />

    </Transform3DGroup>
    ...
</ResourceDictionary>
...
<Page.Resources>
    <ResourceDictionary Source="Wpf3DResources.xaml" />
</Page.Resources>
<p:Workshop3D>
    <p:XyzAxis3D Length="5.0"/>
    <p:Box3D Transform="{StaticResource ModelTransform}"/>
</p:Workshop3D>
...

Objet Box3D transformé

Démo en ligne

Ce principe s'applique à tous les modèles de base livrés dans Perspective. Leur dimension de base est l'unité, et ils sont basés sur l'origine du repère (le point de coordonnées 0.0, 0.0, 0.0).

Interactivité

Les objets 3D de Perspective héritant de UIElement3D, ils prennent en charge la gestion des interactions clavier et souris, et exposent les événements correspondants. L'exemple suivant montre comment déclencher une rotation du modèle 3D au survol de la souris, au moyen des événements MouseEnter et MouseLeave :

<p:Workshop3D>
    <p:Box3D 
        MouseEnter="Box3D_MouseEnter"
        MouseLeave="Box3D_MouseLeave">
        <p:Box3D.Transform >
            <RotateTransform3D CenterX="0.5" CenterZ="0.5">
                <RotateTransform3D.Rotation>
                    <AxisAngleRotation3D 
                        x:Name="boxRotation"    
                        Axis="0.0, 1.0, 0.0"/>
                </RotateTransform3D.Rotation>
            </RotateTransform3D>
        </p:Box3D.Transform>
    </p:Box3D>
</p:Workshop3D>
private void Box3D_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
    // Initialisation de l'objet animation : sur 360 degrés à partir de l'angle courant
    DoubleAnimation da = new DoubleAnimation(boxRotation.Angle + 360.0, new Duration(TimeSpan.FromSeconds(5.0)));
    da.RepeatBehavior = RepeatBehavior.Forever;
    // Déclenchement de l'animation sur la propriété Angle de l'objet boxRotation
    boxRotation.ApplyAnimationClock(AxisAngleRotation3D.AngleProperty, da.CreateClock());
}
private void Box3D_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
    // mémorisation de l'angle courant
    double currentAngle = boxRotation.Angle;
    // Arrêt de l'animation
    boxRotation.ApplyAnimationClock(AxisAngleRotation3D.AngleProperty, null);
    // Réaffectation de l'angle (qui sinon reprend sa valeur initiale)
    boxRotation.Angle = currentAngle;
}

Démo en ligne

Modèles plats

La classe Square3D définit... un carré 2D !

<p:Square3D />

Objet Square3D

Démo en ligne

La propriété BackMaterial jour le même rôle que Material pour la face arrière.

Sur les modèles en volume (3 dimensions), le BackMaterial est visible quand le Material est translucide (Opacity < 1.0).

Polygon3D définit un polygone plat. La propriété SideCount détermine un polygone régulier d'un nombre de cotés donné.

<p:Polygon3D SideCount="6" />

Objet Polygon3D

Le polygone s'inscrit dans un cercle de rayon 1 dans le plan X,Y. Les sommets du polygone sont calculés en divisant la circonférence par la valeur de SideCount. Par défaut, le 1er point est en 1,0,0. La propriété InitialAngle permet de déplacer ce point selon l'angle du segment qu'il forme avec l'origine par rapport à l'axe des X. Exemple pour un angle de 30 degrés :

<p:Polygon3D SideCount="6" InitialAngle="30.0"/>

Objet Polygon3D avec angle initial de 30 degrés

La propriété RoundingRate permet d'arrondir les sommets du polygone. La valeur doit être comprise entre 0.0 et 0.5. Elle représente la proportion d'un coté soumise à l'arrondi de chaque coté du sommet. Une valeur de 0.5 génère donc un cercle parfait. Exemple avec une valeur de 0.15 :

<p:Polygon3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"/>

Objet Polygon3D arrondi

Démo en ligne

Modèles à base de polygones

La classe Bar3D définit une barre de section polygonale, la section ayant les mêmes caractéristiques qu'un Polygon3D : nombre de cotés, angle initial et arrondi. Pour mieux mettre en valeur l'arrondi, nous avons utilisé un matériau brillant (à l'aide d'un SpecularMaterial) et déplacé le modèle par rapport à l'éclairage.

<ResourceDictionary ...>
    <SpecularMaterial x:Key="Specular" SpecularPower="100.0" Brush="White"/>
    <MaterialGroup x:Key="GlossyMaterial">
        <DiffuseMaterial Brush="Goldenrod"/>
        <StaticResource ResourceKey="Specular"/>
    </MaterialGroup>
</ResourceDictionary>
...
<p:Bar3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Transform="{StaticResource ModelTransform}" 
    Material="{StaticResource GlossyMaterial}" />

Objet Bar3D

Démo en ligne

Pour construire un cylindre de rayon 1.0, il suffit d'augmenter la propriété SideCount :

<p:Bar3D SideCount="100" Material="{StaticResource glossyMaterial}" />

Cylindre

Démo en ligne

Le Conical3D est un cône de base polygonale, et fonctionne selon le même principe que Bar3D..

<p:Conical3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Transform="{StaticResource ModelTransform}" 
    Material="{StaticResource GlossyMaterial}" />

Objet Conical3D

Démo en ligne

Enfin le Ring3D est un anneau de section polygonale. Il dispose de propriétés supplémentaire pour indiquer son rayon (Radius) et le nombre de segments (SegmentCount).

<p:Ring3D 
    Radius="10.0" SegmentCount="100"
    SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
    Material="{StaticResource GlossyMaterial}" />

Objet Ring3D

Démo en ligne

Modèles sphériques

La classe Spherical3D définit une sphère de rayon 1. La finesse de l'aspect sphérique est définie à l'aide de la propriété ParallelCount.

Le nombre de méridiens est 2 fois supérieur au nombre de parallèles.

<p:Spherical3D ParallelCount="100" Material="{StaticResource GlossyMaterial}"/>

Objet Spherical3D

Démo en ligne

Il est facile de transformer la sphère en ellipse à l'aide d'une tranformation.

<p:Spherical3D ParallelCount="100" 
    Material="{StaticResource GlossyMaterial}"
    Transform="{StaticResource ModelTransform}"/>

Ellipse

Démo en ligne

Polyèdres

Pour le fun, la classe Isocahedron3D définit un isocaèdre régulier , c'est à dire un polyèdre à 20 faces.

<p:Isocahedron3D Material="{StaticResource GlossyMaterial}" />      

Objet Isocahedron3D

Démo en ligne

La propriété Truncated permet d'obtenir un isocaèdre tronqué...

<p:Isocahedron3D Truncated="True" Material="{StaticResource GlossyMaterial}" />

Objet Isocahedron3D tronqué

Démo en ligne

Bref, vous me voyez venir avec mes gros crampons... (Oh! comment vais-je traduire cela en anglais ?). La genèse de la classe Football3D remonte à un certain soir de juillet 2006. Je ne suis pourtant pas passionné de foot, mais à force de voir de la 3D animée dans les génériques télévisés, j'ai eu envie de prouver qu'on pouvait le faire avec WPF. Ici, on change la couleur du fond, on enlève le repère orthonormé et on peut agrémenter le tout d'une animation WPF ! Voir la video

<p:Football3D />

Ballon de foot

Démo en ligne

Lors de la mise à jour de cet article en novembre 2007, l'idée de construire un ballon de rugby m'a effleuré l'esprit, et puis je me suis dit que non... :-(

Camemberts 3D

La classe PieSlice3D permet de construire de jolis camemberts. Chaque PieSlice3D représente une portion, et dispose des propriétés initialAngle et AngleValue. La propriété IsExploded permet de mettre en valeur une portion. Par défaut, le camembert est construit autour de l'axe des Z. Pour le présenter "à plat", il faut lui appliquer une transformation (comme dans l'exemple qui suit) :

...
<Transform3DGroup x:Key="PieTransform">
    <RotateTransform3D>
        <RotateTransform3D.Rotation>
            <AxisAngleRotation3D 
            Angle="-90"
            Axis="1,0,0" />
        </RotateTransform3D.Rotation>
    </RotateTransform3D>
    <ScaleTransform3D ScaleY="0.2"/>
</Transform3DGroup>
...
<p:Workshop3D>
    <p:PieSlice3D 
        AngleValue="45" 
        IsExploded="True"
        Material="{StaticResource GlossyMaterial}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="45" AngleValue="135" 
        Material="{StaticResource GlossyRed}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="180" AngleValue="80" 
        Material="{StaticResource GlossyBlue}"
        Transform="{StaticResource PieTransform}"/>
    <p:PieSlice3D 
        InitialAngle="260" AngleValue="100" 
        Material="{StaticResource GlossyGreen}"
        Transform="{StaticResource PieTransform}"/>
</p:Workshop3D>

Gyroscope

Démo en ligne

Merci à Philippe Jovelin pour sa contribution.

Application de texture

Il est possible d'afficher une image sur un Square3D, en utilisant un ImageBrush ou un VisualBrush dans la définition du Material.

<p:Square3D >
    <p:Square3D.Material>
        <DiffuseMaterial>
            <DiffuseMaterial.Brush>
                <ImageBrush 
                    ImageSource="http://www.odewit.net/Perspective/Images/Tree.jpg" 
                    TileMode="None" 
                    Stretch="Fill"/>
            </DiffuseMaterial.Brush>
        </DiffuseMaterial>
    </p:Square3D.Material>
</p:Square3D>

Image sur un Square3D

Démo en ligne

Si la propriété BackMaterial n'est pas spécifiée, la face arrière affiche la texture par défaut.

Les classes Perspective qui supportent les textures (VisualBrush, ImageBrush ou DrawingBrush) sont : Square3D, Polygon3D, Box3D, Bar3D, Conical3D et Spherical3D.

Image sur un Polygon3D - Cliquer pour démo en ligneImage sur un Box3D - Cliquer pour démo en ligne
Image sur un Bar3D - Cliquer pour démo en ligneImage sur un Conical3D - Cliquer pour démo en ligneImage sur un Spherical3D - Cliquer pour démo en ligne

Modèles composite

Les objets Wpf3D peuvent être regroupés en XAML au sein d'un ModelVisual3D ou d'un ContainerUIElement3D pour former un modèle composite, tel que ce gyroscope (animé quand vous cliquez dessus) :

<p:Gyroscope3D />

Gyroscope

Démo en ligne

La bibliothèque Perspective est extensible, et il est facile de définir en code .NET (C# ou Visual Basic) de nouvelles classes de modèles composites. Après XyzAxis3D et Football3D, la flèche Arrow3D en est un autre exemple :

<p:Arrow3D Material="{StaticResource GlossyMaterial}" />

Flèche

Démo en ligne

Contrôles 3D

Dans sa version 0.4, Perspective a introduit la notion de contrôle 3D skinable .

Contrôles 3D Perspective

Démo en ligne

A propos de cet article

Auteur : Olivier Dewit

Historique :

  • 1er juin 2010 : mise à jour pour la version 2.0 de Perspective, .NET 4.0 et Visual Studio 2010.
  • 13 mai 2008 : textures.
  • 12 mai 2008 : mise à jour pour la version 0.9 de Perspective. Article original
  • 7 janvier 2008 : mise à jour pour la version 0.5 de Perspective. Article original
  • 16 décembre 2007 : mise à jour pour la version 0.4 de Perspective. Article original
  • 11 novembre 2007 : chapitre sur l'interactivité.
  • 3 novembre 2007 : mise à jour pour la version 0.3 de Perspective. Article original
  • Octobre 2006 : création, pour la version 0.1 de Perspective (Wpf3D - .NET 3.0 - Visual Studio 2005). Article original