Wpf3D 0.1 : Easy 3D programming with WPFWpf3D 0.1 : Easy 3D programming with WPF
A few years ago, I was impressed by the ease of 3D programming with VPython . This library makes it possible to build in language Python 3D models starting from basic (cube, cylinder, sphere, etc.) or composite (arrow) geometrical objects, and to anime them. The default visualization window allows to move the camera around the scene, and to zoom. Thus it is easy to get with a few lines of code a very spectacular application. VPython is very much used to illustrate physical models. Example .
More recently, I discovered POV-Ray and its specialized language for creating 3D scenes starting from objects presets. To the ease of implementation is added a good graphic quality and the realism of the resulting images, because POV-Ray exploits the technique of ray tracing .
WPF uses Direct3D technology to provide creation services for 2D user interface but also for 3D scenes. You can find many references about this subject like this tutorial , who describes the different steps of the creation of a cube. Even if WPF is much easier to program than Direct3D, we are still far from the VPython productivity. WPF misses high-level classes for the 3D programming (cube, cylinder, sphere, etc.). Several authors make this constatation and give some solutions (of which however none goes as far as VPython) :
While inspiring me by these articles, in particular those of Daniel Lehenbauer , I thus created a Wpf3D library which offers to WPF some functionalities and a productivity close of those of VPython.
Wpf3D Basics
Getting and installing Wpf3D
To use the Wpf3D classes in a Xaml file, you have to define 2 XML namespaces referring the 2 Wpf3D assemblys :
<Window x:Class="WindowsApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Wpf3D="clr-namespace:ODewit.Wpf3D;assembly=ODewit.Wpf3D"
xmlns:Wpf3D.Tools="clr-namespace:ODewit.Wpf3D.Tools;assembly=ODewit.Wpf3D.Tools"
Title="WindowsApplication1" Height="300" Width="300"
WindowState="Maximized"
>Wpf3D uses the Daniel Lehenbauer's 3DTools library for the following functionalities :
- Camera zooming and rotating with the mouse, by using the Trackball class.
- Cartesian coordinate system representation, by using the ScreenSpaceLines3D class (of which line diameter remains constant independantly of the camera position).
By default, a 3D scene is displayed in a Viewport3D object, from the System.Windows.Controls namespace. Wpf3D provides 2 classes inherited from System.Windows.Controls.Viewport3D, to use accordingly to the needed user experience :
- ODewit.Wpf3D.Viewport3D to display Wpf3D classes without using 3DTools Trackball.
- ODewit.Wpf3D.Tools.Viewport3D to display Wpf3D classes while using 3DTools Trackball. To get the mouse active on all the Viewport3D surface, and not only on the 3D model, you have to plug the Viewport3D in a mouse-enabled control, such as a Border object.
We will carry out our tests in that last configuration :
<DockPanel>
<Border Name="bMain"
Background="White" >
<Wpf3D.Tools:Viewport3D Name="vMain">
</Wpf3D.Tools:Viewport3D>
</Border>
</DockPanel>Then we have to configure the camera and lights :
<DockPanel>
<Border Name="bMain"
Background="White" >
<Wpf3D.Tools:Viewport3D Name="vMain">
<Wpf3D.Tools:Viewport3D.Camera>
<PerspectiveCamera
LookDirection="0, 0, -10"
UpDirection="0, 1, 0"
NearPlaneDistance="1"
FarPlaneDistance="100"
Position="0, 0, 10"
FieldOfView="70" />
</Wpf3D.Tools:Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<DirectionalLight Color="#FFFFFFFF" Direction="-10, 0, -15" />
<DirectionalLight Color="#FFFFFFFF" Direction="10, 0, 15" />
<DirectionalLight Color="#FFFFFFFF" Direction="0, -10, 0" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Wpf3D.Tools:Viewport3D>
</Border>
</DockPanel>We can now instanciate our first visual Wpf3D object : the coordinate system (XyzAxis3D class) which will visually help us to position our 3D elements :
<DockPanel>
<Border Name="bMain"
Background="White" >
<Wpf3D.Tools:Viewport3D Name="vMain">
...
<Wpf3D.Tools:XyzAxis3D Length="15.0" Signed="true" Color="Black"/>
</Wpf3D.Tools:Viewport3D>
</Border>
</DockPanel>If we execute now the application, we can see the coordinate system. We can move the camera and zoom with the mouse.

We can now begin to create our models. Let's start with a cube using the Box3D class:
<Wpf3D.Tools:Viewport3D Name="vMain">
...
<Wpf3D.Tools:XyzAxis3D Length="15.0" Signed="true" Color="Black"/>
<Wpf3D:Box3D>
<Wpf3D:Box3D.Material>
<MaterialGroup >
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="SteelBlue" Opacity="1.0"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup >
</Wpf3D:Box3D.Material>
</Wpf3D:Box3D>
</Wpf3D.Tools:Viewport3D>
The Material property indicates the model surface characteristics. If this property is empty, the model is not visible. Material is the more verbose element of our XAML syntax. As we will use materials common to various models, we can centralize them in resources (at the window, application or resource dictionary level). Example:
<Window x:Class="WindowsApplication1.Window1"
...
>
<Window.Resources>
<MaterialGroup x:Key="SteelBlue">
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="SteelBlue" Opacity="1.0"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
</Window.Resources>
<DockPanel>
...Thus, the XAML code of our cube (as of our future models) is strongly reduced :
<Wpf3D:Box3D Material="{StaticResource SteelBlue}" />Our cube is positioned at the origin of the coordinate system, and the edges size is of 1 unit. While reading the documentation, it may seem surprising that no property enables us to modify the position and the size of the cube. But that is completely normal. WPF uses the graphics processor (GPU) which supports instructions to transform the position or the aspect of the models. Modifying the position and the size of the cube in our code would use the main processor (CPU) instead of the GPU. To respect the WPF logic, we will use 3D transformations and assign them to the Transform property (always with the possibility of defining the transformation in the resources centralized zone) :
<Window x:Class="WindowsApplication1.Window1"
...
>
<Window.Resources>
...
<Transform3DGroup x:Key="modelTransform">
<TranslateTransform3D OffsetX="1" OffsetY="-1" OffsetZ="0.5"/>
<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="3"
CenterX="0" CenterY="0" CenterZ="0" />
</Transform3DGroup> </Window.Resources>
</Window.Resources>
...
<Wpf3D:Box3D Material="{StaticResource SteelBlue}" Transform="{StaticResource modelTransform}" />
...
This principle applies to all the basic models delivered in Wpf3D. Their basic dimension is the unit, and they are based on the origin of the coordinate system (the point of coordinates 0, 0, 0).
Polygon-based models
The Square3D defines... a 2D square !
<Wpf3D:Square3D Material="{StaticResource SteelBlue}"/>
It is possible to display an image on a Square3D, by using an ImageBrush or a VisualBrush in the Material definition.
<MaterialGroup x:Key="ImageMaterial">
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource="C:\Captures\ViewMantle2.bmp" TileMode="None" Stretch="Fill"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</MaterialGroup>
Square3D inherits from Polygon3D, which defines a flat polygon. The SideCount property defines a regular polygon of a given side count.
The BackMaterial property has the same role than the Material property but for the back face. If it is empty, the back face is not visible.
<Wpf3D:Polygon3D SideCount="6" Material="{StaticResource SteelBlue}"
BackMaterial="{StaticResource SteelBlue}"/>
The polygon fits in a circle of radius 1 in the X, Y plan. The polygon vertices are calculated by dividing the circumference by the SideCount value. By default, the 1st point is at 1,0,0. The InitialAngle property makes it possible to move this point according to the angle of the segment that it forms with the origin compared to the X axis. Example for an angle of 30 degrees :
<Wpf3D:Polygon3D SideCount="6" InitialAngle="30.0" Material="{StaticResource SteelBlue}"
BackMaterial="{StaticResource SteelBlue}"/>
The RoundingRate makes it possible to round the polygon vertices. The value must be comprised between 0.0 and 0.5. It represents a side proportion submitted to the rounding for the 2 sides of a vertex. Thus a value of 0.5 generates a perfect circle. Example with a value of 0.15 :
<Wpf3D:Polygon3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
Material="{StaticResource SteelBlue}"
BackMaterial="{StaticResource SteelBlue}"/>
The bar3D class défines a bar with a polygonal section. The section has the same characteristics than a Polygon3D : side count, initial angle and rounding rate. For a better emphasizing of the rounding, we use a glossy material (using SpecularMaterial) and move the model compared to the light position.
...
<MaterialGroup x:Key="GlossySteelBlue">
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="SteelBlue" Opacity="1.0"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
<SpecularMaterial SpecularPower="100.0">
<SpecularMaterial.Brush>
<SolidColorBrush Color="White" Opacity="1.0" />
</SpecularMaterial.Brush>
</SpecularMaterial>
</MaterialGroup>
...
<Wpf3D:Bar3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
Transform="{StaticResource modelTransform2}"
Material="{StaticResource GlossySteelBlue}" />
The principle is the same for Conical3D, which is a cone with a polygonal base.
<Wpf3D:Conical3D SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
Transform="{StaticResource modelTransform2}"
Material="{StaticResource GlossySteelBlue}" />
Finally Ring3D is a ring with a polygonal section. It has additional properties to indicate its radius and its segment count.
<Wpf3D:Ring3D Radius="5" SegmentCount="100"
SideCount="6" InitialAngle="30.0" RoundingRate="0.15"
Material="{StaticResource GlossySteelBlue}" />
Spherical models
The Spherical3D class defines a sphere with a radius of 1 unit. The smoothness of the spherical aspect is defined using the ParallelCount property.
<Wpf3D:Spherical3D ParallelCount="100" Material="{StaticResource GlossySteelBlue}"/>
It is easy to transform the sphere into an ellipsoid using a tranformation.
<Wpf3D:Spherical3D ParallelCount="100" Material="{StaticResource GlossySteelBlue}"
Transform="{StaticResource modelTransform2}"/>
Polyhedrons
Just for fun, the Isocahedron3D class defines a regular isocahedron , that is a 20 faces polyhedron.
<Wpf3D:Isocahedron3D Material="{StaticResource GlossySteelBlue}"/> 
If we truncate its vertices, we get a truncated isocahedron... represented by the TruncatedIsocahedron3D class.
<Wpf3D:TruncatedIsocahedron3D Material="{StaticResource GlossySteelBlue}"/> 
Well, you see what I mean... A very poor translation from my french article ! The FootBall3D class genesis began at a certain evening of July 2006. I am however not impassioned by football (soccer). But after often seeing 3D animated credits at the TV, I wanted to prove that WPF can do that. Here we define a material for the hexagons, another one for the pentagons, a background with a new color and without a displayed coordinate system, and we can animate the whole with a 3D WPF animation ! Play video
<Wpf3D:FootBall3D HexagonMaterial="{StaticResource GlossyWhite}"
PentagonMaterial="{StaticResource GlossyBlack}"/>
Composite models
The Wpf3D objects can be gathered in a ModelVisual3D to form a composite model, such as this animated gyroscope :

The source code of the gyroscope is provided with the Wpf3D library in the form of an UserControl. It consists of a ModelVisual3D for the 2 external rings (Ring3d), overlapped in the basic ModelVisual3D including the central ring, the axis and the wheel. Thus, it is possible to imbricate animations (the one of a ring and the one of a group of rings), resulting in a complex global animation. Play video
The Wpf3D library is extensible, and we will see later that it is easy to define in .NET code (C# or Visual Basic) some new composite model classes. With the football (soccer) balloon, the Arrow3D is another example :
<Wpf3D:Arrow3D Material="{StaticResource GlossySteelBlue}"/>
Conclusion
The architecture of Wpf3D was defined on the basis of Daniel Lehenbauer work. It consists in building classes inherited from ModelVisual3D, rather than to group GeometryModel3D models within Model3DGroup objects. It presents the interest to support model overlapping, hit testing (mouse click detection on the 3D models), as well as DataBinding and animation on DependencyProperties. On the other hand, the code centralization requires more programming than with Model3DGroup. We will study this architecture and these advanced aspects in later articles.