Title: Study WPF 3D performance in C#
The example Make a stellate geodesic sphere with WPF and C# makes a pretty complicated shape so I was wondering about the performance provided by WPF 3D programs. This example makes a group of stellate spheres using WPF 3D.
See the earlier post for information about how to make stellate spheres. Here I'm only going to talk about the more interesting changes and the results.
Unlike the previous examples, this one has few controls. It lets you use scroll bars to zoom in and out and to rotate the image. It doesn't include a wireframe option or coordinate axes. Just a bunch of spheres.
The program uses the following code to create the spheres.
// Make some spheres.
const int max = 5;
const int xmin = -max;
const int xmax = max;
const int ymin = -max;
const int ymax = max;
const int zmin = -max;
const int zmax = max;
double dr = 255 / (xmax - xmin);
double dg = 255 / (ymax - ymin);
double db = 255 / (zmax - zmin);
for (int x = xmin; x <= xmax; x++)
{
// Make sure the program doesn't think it's stuck.
Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Background,
new Action(delegate { }));
for (int y = ymin; y <= ymax; y++)
{
for (int z = zmin; z <= zmax; z++)
{
// Get the stellate sphere's triangles.
Triangle[] triangles = MakeTriangles(1);
// Create the WPF triangles for the stellate sphere.
MeshGeometry3D solid_mesh = new MeshGeometry3D();
foreach (Triangle triangle in triangles)
{
AddTriangle(solid_mesh, triangle);
}
// Make the sphere's material.
byte r = (byte)((x - xmin) * dr);
byte g = (byte)((y - ymin) * dg);
byte b = (byte)((z - zmin) * db);
Color color = Color.FromArgb(255, r, g, b);
SolidColorBrush solid_brush =
new SolidColorBrush(color);
DiffuseMaterial solid_material =
new DiffuseMaterial(solid_brush);
GeometryModel3D solid_model =
new GeometryModel3D(solid_mesh, solid_material);
// Scale to make it smaller.
const double scale = 0.4;
ScaleTransform3D scale_transform =
new ScaleTransform3D(
scale, scale, scale,
0, 0, 0);
// Translate to center at (x, y, z).
TranslateTransform3D translate_transform =
new TranslateTransform3D(x, y, z);
// Transform the model.
Transform3DGroup transform_group =
new Transform3DGroup();
transform_group.Children.Add(scale_transform);
transform_group.Children.Add(translate_transform);
solid_model.Transform = transform_group;
MainModelGroup.Children.Add(solid_model);
}
}
}
The code starts by defining some constants that it will use to position spheres. It then enters a series of nested loops to create the spheres. Each loop represents a coordinate (X, Y, or Z) that runs over a range. The result is a collection of spheres that lie on an integer grid in three dimensions.
For very large grids (like a 15×15×15 = 3,375 spheres), the program spent so long initializing that the CLR thought it was stuck and raised an error. Unfortunately WPF doesn't have Application.DoEvents, which would satisfy the CLR that the program was getting something done.
As a workaround, I added the call to Dispatcher.Invoke. That allows the program to run and complete an empty action so the CLR is happy.
Next the code enters its three nested loops. For each X, Y, and Z value it creates a stellate sphere. It then calculates a color based on the sphere's coordinates and uses it to make a material for the sphere.
The original method that creates a stellate sphere makes a fairly large sphere centered at the origin. To make it possible to see all of the spheres, this example scales the spheres so they don't overlap and translates them to their proper locations.
Finally the code adds the spheres to the main model's Children collection.
The program has a few other smaller changes such as using a Stopwatch to determine how long it takes the program to build its model.
The following table shows the time it took to build the model and the number of triangles used by the model for different arrangements of spheres.
Arrangement | # Spheres | # Triangles | Time to Load |
5×5×5 | 125 | 718,740 | 3 sec |
6×6×6 | 216 | 1,186,380 | 5 sec |
7×7×7 | 343 | 1,822,500 | 7 sec |
8×8×8 | 512 | 2,653,020 | 12 sec |
9×9×9 | 729 | 3,703,860 | 13 sec |
10×10×10 | 1,000 | 5,000,940 | 17 sec |
15×15×15 | 3,375 | 16,087,140 | 73 sec |
If you grab one of the scroll bars and whip it back an forth, there is a barely noticeable lag with the 5×5×5 models and the lag grows larger with the larger models. The delay with the 15×15×15 model is quite annoying.
Performance with the 5×5×5 model is probably acceptable for animation and real-time games similar to Warcraft or Guild Wars. You might even be able to get away with a few more triangles depending on how quickly the scene needs to change. It seems likely that you could get away with a million or so triangles.
For larger models, the delay is larger and would be very annoying in a POV-style game. It would be acceptable, however, in a business presentation or some sort of visualization program where you needed to look at the model from different angles. You could probably get away with around 2 million triangles, perhaps more if you're patient.
With even larger models, such as the 15×15×15 model, the lag is really annoying. You might still be able to do some visualization, but you won't be able to manipulate the model smoothly.
Still I'm impressed that WPF 3D can display a model containing 16 million triangles at all. (When I was in grad school, the Media Lab had a 3D processor that could draw 3,000 triangles in real-time and we were very impressed. Times have definitely changed.) WPF 3D is not a particularly easy environment for building 3D applications, but it has some definite possibilities.
Download the example to experiment with it and to see additional details.
|