This entry is part 3 of a 12-part series on WPF 3D.
Saving a WPF 3D Scene to a Bitmap
WPF has some really handy classes in it. Have you seen RenderTargetBitmap? Basically, it's a simple way to capture a bitmap of any WPF Visual. It works with 3D too:
RenderTargetBitmap bmp = new RenderTargetBitmap(
width, height, 96, 96, PixelFormats.Pbgra32);
Wanna copy that image to the clipboard so you can paste it into some other application?
Wanna save that image to a PNG file?
PngBitmapEncoder png = new PngBitmapEncoder();
using (Stream stm = File.Create(filepath))
Nifty. The pictures in this series of blog entries were rendered in just this fashion. But there are a couple of details worth mentioning.
When using RenderTargetBitmap, you should probably erase it before you draw anything into it:
Rectangle vRect = new Rectangle();
vRect.Width = width;
vRect.Height = height;
vRect.Fill = Brushes.White;
vRect.Arrange(new Rect(0, 0, vRect.Width, vRect.Height));
If your Viewport3D was created offscreen, it's not ready to draw. You need to give it a size (the same as the bitmap you're rendering into) and call Measure() and Arrange() to get it ready:
myViewport3D.Width = width;
myViewport3D.Height = height;
myViewport3D.Measure(new Size(width, height));
myViewport3D.Arrange(new Rect(0, 0, width, height));
Note that none of the above is specific to the 3D features of WPF. RenderTargetBitmap just works with a Viewport3D like any other visual. This is one of the best things about WPF: The 3D features are not special or weird. They're seamlessly integrated into the framework.
If you have experience programming with some other 3D API such as OpenGL or Direct3D (upon which WPF is built, by the way), you are probably accustomed to thinking of 3D stuff as very distinct from other stuff. Simple things like getting a 3D graphic to appear in the same window next to a listbox can require all kinds of gymnastics. WPF doesn't have those sorts of boundaries. If you want to put an animated 3D scene as the graphic for a toolbar button, you can.
That level of integration is deeply neato.
Just one more detail about RenderTargetBitmap, and this one is sort-of 3D-specific:
If your Viewport3D was created offscreen and you're using our friend ScreenSpaceLines3D, you'll need to make sure your lines get scaled at least once. Since I hacked my copy of ScreenSpaceLines3D.cs to remove the use of CompositionTarget.Rendering, I simply call my Rescale() method on every instance just after I call Measure() and Arrange() on the offscreen Viewport3D.