This entry is part 3 of a 12-part series on WPF
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();
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
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,
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.Arrange(new Rect(0, 0,
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
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.