It’s time to draw a simple 3D object using WPF. As a quick introduction to 3D graphics in WPF, let’s just render one of the simplest possible objects—a cube.
In this example, I’ll define everything that we need directly in XAML. As with everything else in WPF, we could do all this directly in code. But defining everything in the XAML is a bit cleaner, in that it makes the object hierarchy more obvious. In a real-world project, you’d obviously do some of this in code, e.g. the creation or loading of the 3D mesh (the object that we want to display).
Let’s start with the final XAML. Here are the full contents of the Window1.xaml file:
<Window x:Class="SimpleCube.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="398" Width="608" <Grid> <Viewport3D Name="viewport3D1"> <Viewport3D.Camera> <PerspectiveCamera x:Name="camMain" Position="6 5 4" LookDirection="-6 -5 -4"> </PerspectiveCamera> </Viewport3D.Camera> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight x:Name="dirLightMain" Direction="-1,-1,-1"> </DirectionalLight> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name="meshMain" Positions="0 0 0 1 0 0 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 1 1" TriangleIndices="2 3 1 2 1 0 7 1 3 7 5 1 6 5 7 6 4 5 6 2 0 2 0 4 2 7 3 2 6 7 0 1 5 0 5 4"> </MeshGeometry3D> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial x:Name="matDiffuseMain"> <DiffuseMaterial.Brush> <SolidColorBrush Color="Red"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D> </Grid> </Window>
The basic idea here is that we need a Viewport3D object that contains everything required to render our cube. The simplified structure, showing the Viewport3D and its child objects, is:
Viewport3D ModelVisual3D (defines lighting) DirectionalLight ModelVisual3D (defines object to render) GeometryModel3D MeshGeometry3D DiffuseMaterial
Here’s what each of these objects is responsible for:
- Viewport3D – A place to render 3D stuff
- ModelVisual3D – A 3D object contained by the viewport, either a light or a geometry
- DirectionalLight – A light shining in a particular direction
- GeometryModel3D – A 3D geometrical object
- MeshGeometry3D – The set of triangles that defines a 3D object
- DiffuseMaterial – Material used to render a 3D object, e.g. a brush
Perhaps the most interesting of these classes is the MeshGeometry3D. A “mesh” basically consists of a series of triangles, typically all connected to form the 3D object that you want to render. The MeshGeometry3D object defines a mesh by specifying a series of points and then a collection of triangles. The collection of points represent all of the vertexes in the mesh and are defined by the Positions property. The triangles, stored in the TriangleIndices property, are defined in terms of the points, using indexes into the Positions collection.
This seems a bit odd at first. Why not just define a collection of triangles, each consisting of three points? Why define the points as a separate collection and then define the triangles by referencing the points? The answer is that this scheme allows reusing a single point in multiple triangles.
In our case, drawing a cube, we define eight points, for the eight vertexes of the cube. The image below shows the points numbered from 0-7, matching the order that we add them to Positions. The back left corner of the cube is located at (0, 0, 0).
After defining the points, we define the 12 triangles that make up the surface cube—two triangles per face. We define each triangle by just listing the indexes of the three points that make up the triangle.
It’s also important to pay attention to the order that we list the indexes for each triangle. The order dictates the direction of a vector normal to the triangle, which indicates which side of the triangle that we can see. The rule is: add vertexes counter-clockwise, as you look at the visible face of the triangle.
In addition to the mesh, we define a material used to render the cube. In this case, it’s a DiffuseMaterial, which allows painting the surface of the cube with a simple brush.
We also need to add a camera to our scene, by specifying where it is located and what direction it looks in. In order to see our cube, we put the camera at (6, 5, 4) and then set its LookDirection, a vector, to (-6, -5, -4) so that it looks back towards the origin.
Finally, in order to see the cube, we need lighting. We define a single light source, which is a DirectionalLight—a light that has no position, but just casts light in a particular direction.
The final result is a simple red cube.