Charles Petzold

Animated “3D” Text in Silverlight 3

July 23, 2009
Roscoe, N.Y.

OK, I probably shouldn't have called this program HelloSilverlight3D because people might get the idea that Silverlight 3 has 3D support, and it really doesn't. So when I call this program HelloSilverlight3D I'm really being somewhat whimsical.


What Silverlight 3 does have is the ability to apply a standard 3D transform on a 2D element for perspective effects. Actual 3D support requires at least Z-buffering so that objects in the foreground obscure objects in the background, and shading, which makes surfaces look like they're reflecting light. To achieve any kind of decent performance, these aspects of 3D graphics should be done on the graphics board, and it's problematic accessing that hardware through a platform-independent browser plug-in like Silverlight, so we probably won't see real 3D support in Silverlight for awhile.

For this program I used the new PlaneProjection class. Despite the existence of 12 settable properties, this class is likely to be popular for simple 3D-like rotations, such as those used in flip-panels and page-turners. For a flip-panel, for example, you'll probably just need to animate RotationY (to spin around the Y-axis) or RotationX (the X-axis). For a page-turner, you'll want to change the rotation origin from the center of the element to one side. The CenterOfRotationX and CenterOfRotationY properties are both relative to the element, much like the RenderTransformOrigin property. The default values are both 0.5 — the center of the element.

As I played around with the PlaneProjection class and tried to relate it to what I knew about 3D camera transforms — a subject I was able to refresh myself on by digging into Chapter 7 of my book 3D Programming for Windows — it became obvious that the class contained an internal constant that governed the perspective effect. If you assume that a Silverlight 2D element is sitting on the XY plane, then the imaginary camera is somewhere on the positive Z axis aimed towards the origin. Where that camera is on the Z axis governs how much of a perspective effect you'll see when something spins around the X or Y axis. Experimentation demonstrated to my satisfaction that the magic number is 1000. The camera is assumed to be 1000 pixels from the screen, or — assuming 96 pixels to the inch — a little over 10 inches. If the PlaneProjection transform results in a transformed element having a Z value above 998 or so, it will be clipped.

Unlike the relative coordinates of the CenterOfRotationX and CenterOfRotationY properties, the CenterOfRotationZ property is in units of pixels and has a default value of 0.

It was important for me to figure this out because I wanted to create a Silverlight animation much like the one in my blog entry 3D Text Animation (dot xaml) from three summers ago. I wanted the text to swing around and smack the user's nose as it goes by.

I won't give you the whole source code package for this one because I only edited the MainPage.xaml file like so:

    <Grid x:Name="LayoutRoot">
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        <Canvas Grid.Column="1"
            <TextBlock Text="Hello Silverlight 3D"
                       FontFamily="Times New Roman"
                        CenterOfRotationX="0" />
                        From="-90" To="270"
                        RepeatBehavior="Forever" />

The screen shot shown above was done with a smaller font size. This text string in a font size of 144 pixels is obviously close to 1000 pixels in length. It's positioned on the right half of a two-column Grid with the CenterOfRotationX property set to 0 so it rotates around the left edge. The entire string won't be fully visible unless that web browser is over 2000 pixels wide, but I didn't really want it to be visible all the time.

Consequently, I had some trouble getting this to work right. If I just put the TextBlock in a Grid cell, the text would be truncated depending on how much of the unrotated text fit in the cell at the particular browser window size. I dropped a Canvas in there to avoid that clipping. (Before that, I tried using a FontSize of 12 and a RenderTransform that scaled by a factor of 12, but that revealed a little too much of what's going on internally: It looks as if the presence of a non-default Projection property will cause the element to be converted to a bitmap, which is then subjected to the RenderTransform. Try it!)