Title: Draw a smooth 3D surface with WPF, XAML, and C#
My post Draw a 3D surface with WPF, XAML, and C# explains how to use WPF, XAML, and C# to draw a three-dimensional surface. The following list recaps the main steps.
- Place a Viewport3D object on a WPF program's window. Give it a name so you can refer to it in code.
- Add startup code to do the following.
- Set the viewport's Camera property to a camera.
- Create a Model3DGroup to hold information about the three-dimensional scene.
- Create lights and add them to the group's Children collection.
- Use the following steps to create the geometry model.
- Make one or more MeshGeometry3D objects to represent three-dimensional objects.
- Add points to the mesh's Positions collection to define points used by the object.
- Add indexes to the mesh's TriangleIndices collection to define triangles.
- Optionally add normal vectors to the mesh's Normals collection to determine the points' normal directions. (The previous example didn't do this. I'll cover this in a later post.)
- Optionally add values to the mesh's TextureCoordinates collection to indicate how points are mapped to the object's texture. (I'll also cover this in a later post.)
- Create a material to represent the mesh's object.
- Use the mesh and the material to create a new GeometryModel3D object.
- Optionally set the model object's BackMaterial property.
- Add the model to the group's Children collection.
Simple, right? Actually it's not too bad if you use the previous example as a template for new programs.
One problem with the previous example is that it doesn't draw a smooth surface. If you look at that program's result, you can see the triangles that make up the surface. The reason you see each triangle is that the program doesn't supply vertex normal information. The rendering engine calculates each triangle's normal separately and uses those calculated normals to shade the triangles.
You could define normals for each of the surface's points, but that would be a lot of work, particularly because you'd have to find the derivative of the messy function that generates the surface.
Fortunately there's an easier approach. If two triangles share a vertex (i.e. use the same index in the TriangleIndices collection), then by default the rendering engine uses an average of their normals for the normal at that point. The previous example didn't get this benefit because it added each triangle's vertices to the mesh's Positions collection separately so the triangles didn't share vertices. To fix this, the new example reuses any vertices that are already in the Positions collection.
The following code shows how the program adds a new triangle to its mesh.
// Add a triangle to the indicated mesh.
// If the triangle's points already exist, reuse them.
private void AddTriangle(MeshGeometry3D mesh,
Point3D point1, Point3D point2, Point3D point3)
// Get the points' indices.
int index1 = AddPoint(mesh.Positions, point1);
int index2 = AddPoint(mesh.Positions, point2);
int index3 = AddPoint(mesh.Positions, point3);
// Create the triangle.
This method calls the AddPoint method (described next) to add the triangle's vertices to the mesh's Positions collection. The AddPoint method returns the index of a point in that collection. The code then adds the vertices' indices to the mesh's TriangleIndices collection to define the triangle.
The following code shows the AddPoint method.
// If the point already exists, return its index.
// Otherwise create the point and return its new index.
private int AddPoint(Point3DCollection points, Point3D point)
// See if the point exists.
for (int i = 0; i < points.Count; i++)
if ((point.X == points[i].X) &&
(point.Y == points[i].Y) &&
(point.Z == points[i].Z))
// We didn't find the point. Create it.
return points.Count - 1;
This code loops through the mesh's Positions collection to see if the desired point is already there. If it is, the method returns the point's index in the collection. If the point isn't there, the method adds it and returns the new point's index.
Because the triangles reuse existing vertices, they share any vertices they can. The result is the smooth surface you see at the top of the post.
Download the example to experiment with it and to see additional details.