RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


WPF Wonders: 3D Drawing : Page 5

WPF makes drawing three-dimensional scenes quick and easy.


Marvelous Meshes

Now that you've mastered (or at least glimpsed) cameras, lights, and materials, you're ready to actually build something.

One of the WPF's 3D workhorses is GeometryModel3D. The members that class exposes represent the geometric data that makes up three-dimensional objects. A GeometryModel3D object's Geometry property stores the actual geometric data. A MeshGeometry3D object can serve nicely for the Geometry property.

The MeshGeometry3D object has two key properties that determine exactly where the object sits.

The Positions property is a list of three-dimensional points giving the vertices of the triangles that make up the object. For example, the values -1,0,-1 -1,0,1 1,0,1 1,0,-1 specify four points in the Y = 0 plane with X = +/-1 and Z = +/- 1.

The TriangleIndices property is a list of indexes into the Positions list telling which triples of points make up triangles. For example, the values 0,1,2 0,2,3 means use points 0, 1, and 2 to make up one triangle and use points 0, 2, and 3 to make up another.

The order of the indices is important because Direct3D uses it to determine whether a triangle is visible from the camera. To build an object properly, the points that make up a triangle must be outwardly oriented according to the right-hand rule.

In the right-hand rule, imagine placing your wrist at the first point and pointing your fingers toward the second point. Now curl your finger tips towards the third point. If you can't do that without curling your fingers backwards, flip your hand over so it's upside down. Now your thumb points in the triangle's outward direction. A vector that points perpendicularly away from the triangle in this direction is called a normal.

For example, consider the first triangle described in the previous paragraphs with points (-1, 0, -1), (-1, 0, 1), and (1, 0, 1). Place your wrist at (-1, 0, -1), point your fingers toward (-1, 0, 1) and curl your fingertips toward (1, 0, 1). (Drawing a picture often helps.) If you did it right, your thumb points up.

Before it draws a triangle, Direct3D checks its orientation. If a triangle's normal points toward the camera, then the triangle is visible and Direct3D draws it. If the normal points away from the camera, then the triangle is on the back side of an object and isn't visible, so Direct3D doesn't draw it.

For example, if the triangles make up a cube, then each should be oriented so their normals point out away from the cube and not into it. Then, as you rotate the cube, the faces that are visible to the camera have normals pointing more or less toward the camera. Faces that are on the far side of the cube have normals pointing away from the camera and Direct3D doesn't need to draw them.

The following code defines an almost complete three-dimensional scene.

<Viewport3D Camera="{DynamicResource Camera}">
            <!-- Lights -->
            <AmbientLight Color="Gray"/>
            <DirectionalLight Color="Gray" Direction="-1,-3,-2"/>
            <DirectionalLight Color="Gray" Direction="1,-2,3"/>
                  <!-- Square -->
                   Positions="-1,0,-1 -1,0,1 1,0,1 1,0,-1"
                   TriangleIndices="0,1,2 0,2,3"/>
                  <DiffuseMaterial Brush="Blue"/>
Figure 7. Square in the Air: The sample program Square uses a camera, lights, a material, and a mesh to draw a floating square.

A Viewport3D object holds a ModelVisual3D object. That in turn contains a Model3DGroup, which holds lights and a GeometryModel3D. In this case, the GeometryModel3D's Geometry property defines a square. The GeometryModel3D's Material property gives the square a blue diffuse material.

The only piece missing from the preceding code is a definition of the Viewport3D object's camera. I'm not including it here because this example uses a transformation bound to the program's scroll bars, so it's kind of messy.

Figure 7 shows the result.

Program Square uses a very simple mesh that defines only four points and two triangles, but you can easily make meshes that use hundreds of points and triangles. Example program Cube (see Figure 8) is almost exactly the same as program Square except that it uses the following code to define its mesh.

     -1,-1,-1   1,-1,-1   1,-1, 1  -1,-1, 1
     -1,-1, 1   1,-1, 1   1, 1, 1  -1, 1, 1
      1,-1, 1   1,-1,-1   1, 1,-1   1, 1, 1
      1, 1, 1   1, 1,-1  -1, 1,-1  -1, 1, 1
     -1,-1, 1  -1, 1, 1  -1, 1,-1  -1,-1,-1
     -1,-1,-1  -1, 1,-1   1, 1,-1   1,-1,-1"
      0  1  2     2  3  0
      4  5  6     6  7  4
      8  9 10    10 11  8
     12 13 14    14 15 12
     16 17 18    18 19 16
     20 21 22    22 23 20"/>
Figure 8. Colored Cube: Example program Cube uses a more complicated mesh than program Square does to make a cube.

If you look closely at the mesh used to draw the cube in Figure 8, you'll see that each point is listed three times in the Positions list. Each triangle defined in the TriangleIndices list uses a different version of each point even though there are many duplicates and you may wonder why there's so much duplication.

When Direct3D draws a mesh, it considers all of the triangles that share a given point. It calculates the triangles' normals and uses their average to determine the color at the shared point. This causes adjacent triangles to blend smoothly together and makes curved surfaces look better.

For example, the triangles that make up the spheres shown in Figure 6 share points so that Direct3D draws them smoothly.

However, for a shape such as a cube, you don't want adjacent sides to blend smoothly together. If they did, you wouldn't be able to tell where one face ended and the next began. The result would a bit like the left cube in Figure 5.

Author's Note: I've left code to draw this sort of cube commented out in the Cube example program so you can experiment if you like.

One solution is to use duplicate points as program Cube does. Another is to use a separate mesh for each of the cube's faces. The first solution is a bit more efficient but for small examples such as this one (only 24 points and 12 triangles), the difference isn't noticeable.

The next article continues this exploration with a discussion of Textures, Transformations, and a dip into more realistic 3D rendering.

Editor's Note: The downloadable code for this article contains the sample code for both the WPF 3D articles in this series, not just this article.

Rod Stephens is a consultant and author who has written more than a dozen books and two hundred magazine articles, mostly about Visual Basic. During his career he has worked on an eclectic assortment of applications for repair dispatch, fuel tax tracking, professional football training, wastewater treatment, geographic mapping, and ticket sales. His VB Helper web site receives more than 7 million hits per month and provides three newsletters and thousands of tips, tricks, and examples for Visual Basic programmers.
Email AuthorEmail Author
Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date