Was this page helpful?
Your feedback about this content is important. Let us know what you think.
Additional feedback?
1500 characters remaining
Export (0) Print
Expand All

Walkthrough: Creating and Animating a 3-D Textured Cube in Silverlight

This walkthrough describes how to create and animate a 3D textured cube using the Silverlight 5 tools and runtime. In this walkthrough, you will create a Silverlight 5 application that controls the yaw, pitch, and roll (orientation) of this cube using Slider elements, as shown in the following illustration.

Screenshot of Textured 3D Cube

This topic contains the following sections.

You need the following components to complete this walkthrough:


To download the complete source code for this sample, click the following link:

Download Controllable Textured 3D Cube Sample

This section describes how to create the project for this sample.

  1. Start Visual Studio and create a new Silverlight Application project in C# named SilverlightApplication3DTest.

  2. In the New Silverlight Application dialog box, specify the following settings:

    • Host the Silverlight Application in a new Web site

    • ASP.NET Web Application Project

    • Silverlight 5

  3. Save the following image to your SilverlightApplication3DTest project folder and name it SLXNA.png. You can also use your own .png image that has a 2:1 ratio (where the width of the image in pixels is double the height). This image will be used as the texture for the cube.

    Texture File for 3D Cube
  4. In Visual Studio, add the SLXNA.png file to the SilverlightApplication3DTest project.

  5. In the SilverlightApplication3DTest project, add references to the following assemblies:

    • Microsoft.Xna.Framework

    • Microsoft.Xna.Framework.Graphics

    • Microsoft.Xna.Framework.Graphics.Extensions

    • Microsoft.Xna.Framework.Graphics.Shaders

    • Microsoft.Xna.Framework.Math

    • System.Windows.Xna

  6. In the SilverlightApplication3DTest.Web project, open SilverlightApplication3DTestTestPage.aspx and SilverlightApplication3DTestTestPage.html.

  7. In the <object> tag add a <param> value named EnableGPUAcceleration to enable the hardware capabilities of your computer’s graphics processing unit (GPU). The EnableGPUAcceleration parameter is shown in bold the following code.

    <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
       <param name="source" value="ClientBin/<MAIN_PROJECT_NAME>.xap"/>
       <param name="EnableGPUAcceleration" value="true" />
       <param name="onError" value="onSilverlightError" />
       <param name="background" value="white" />
       <param name="minRuntimeVersion" value="<RUNTIME_VERSION>" />
       <param name="autoUpgrade" value="true" />
          <a href=”<RUNTIME_LINK>” style="text-decoration:none"><img 
          src="<IMG_LINK>" alt="Get Microsoft Silverlight" style="border-style:none"/>

In Silverlight 5, shader effects can be applied to 3D objects by importing compiled shaders written in High-Level Shading Language (HLSL). Compilation of these files is possible using the Effect-Compiler Tool (FXC) that is provided as part of the DirectX SDK, or by using DirectX APIs. This section describes how to create and compile vertex and pixel shaders using the FXC tool.


To complete this section, you must have the DirectX SDK installed.

  1. Open Windows Explorer to the project folder that contains MainPage.xaml.

  2. Create a new text file named Cube.vs.hlsl.

  3. Add the following HLSL code.

    This code provides the ability to for the cube to have its position and world view modified by the Silverlight application. The output from this vertex shader is passed on to the pixel shader and also to the geometry processor, which changes the viewing angle of the cube when new input data is received.

    // transformation matrix provided by the application
    float4x4 WorldViewProj : register(c0);
    // vertex input to the shader
    struct VertexData
      float3 Position : POSITION;
      float2 UV : TEXCOORD;
    // vertex shader output passed through to geometry 
    // processing and a pixel shader
    struct VertexShaderOutput
      float4 Position : POSITION;
      float2 UV : TEXCOORD;
    // main shader function
    VertexShaderOutput main(VertexData vertex)
      VertexShaderOutput output;
      // apply standard transformation for rendering
      output.Position = mul(float4(vertex.Position,1), WorldViewProj);
      // pass the UV coordinates through to the next stage
      output.UV = vertex.UV;
      return output;
  4. Create a new text file named Cube.ps.hlsl.

  5. Add the following HLSL code.

    This code provides the ability to for the cube to have its texture modified by the Silverlight application.

    texture cubeTexture : register(t0);
    sampler cubeSampler = sampler_state
    texture = <cubeTexture>;    
    // output from the vertex shader
    struct VertexShaderOutput
      float4 Position : POSITION;
      float2 UV : TEXCOORD;
    // main shader function
    float4 main(VertexShaderOutput vertex) : COLOR
      //return float4(1.0f, 1.0f, 1.0f, 1.0f);
      return tex2D(cubeSampler, vertex.UV).rgba;
  6. Depending on your architecture, create a .bat named CompileShaders_x86.bat or CompileShaders_x64.bat.

    To compile these HLSL files, use the FXC tool in the DirectX SDK.

  7. If you are using an x86 architecture, add the following code to CompileShaders_x86.bat.

    for /f "tokens=*" %%a in ('dir /b *.vs.hlsl') do (
    call "%%DXSDK_DIR%%Utilities\bin\x86\fxc.exe" /T vs_2_0 /O3 /Zpr /Fo %%~na %%a >> hlslcomplog.txt
    for /f "tokens=*" %%a in ('dir /b *.ps.hlsl') do (
    call "%%DXSDK_DIR%%Utilities\bin\x86\fxc.exe" /T ps_2_0 /O3 /Zpr /Fo %%~na %%a >> hlslcomplog.txt
  8. If you are using an x64 architecture, add the following code CompileShaders_x64.bat.

    for /f "tokens=*" %%a in ('dir /b *.vs.hlsl') do (
    call "%%DXSDK_DIR%%Utilities\bin\x64\fxc.exe" /T vs_2_0 /O3 /Zpr /Fo %%~na %%a >> hlslcomplog.txt
    for /f "tokens=*" %%a in ('dir /b *.ps.hlsl') do (
    call "%%DXSDK_DIR%%Utilities\bin\x64\fxc.exe" /T ps_2_0 /O3 /Zpr /Fo %%~na %%a >> hlslcomplog.txt
  9. Double-click the .bat file to compile the two HLSL files.

    Three files should be created: a text file logging the results of the calls to FXC (hlslcomplog.txt), the compiled pixel shader (Cube.ps), and the compiled vertex shader (Cube.vs).

  10. In Visual Studio, add Cube.ps and Cube.vs to the SilverlightApplication3DTest project.

  11. In the Properties window, set the Build Action for Cube.ps and Cube.vs to Resource.

To create a cube, you use 2D vectors (X and Y coordinates) and 3D vectors (X, Y, and Z coordinates) to create a 3D representation. You use the image to create a texture for the cube. To compute the position, scale, and rotation of the cube, you use matrices. You use the vertex and pixel shaders to draw the cube. This section describes how to create and texture the cube.

  1. In the SilverlightApplication3DTest project, add a new class named Cube.

  2. Add the following code to the class.

    using System;
    using System.Net;
    using System.Windows;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using System.IO;
    using System.Windows.Media.Imaging;
    using System.Windows.Graphics;
    namespace SilverlightApplication3DTest
        public struct VertexPositionTexture
            public Vector3 Position;
            public Vector2 UV;
            public VertexPositionTexture(Vector3 position, Vector2 uv)
                Position = position;
                UV = uv;
            public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
                new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                new VertexElement(12, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
        public class Cube
            static readonly GraphicsDevice resourceDevice = GraphicsDeviceManager.Current.GraphicsDevice;
            VertexBuffer vertexBuffer;
            VertexShader vertexShader;
            PixelShader pixelShader;
            Texture2D texture;
            public Cube()
                // Initialize resources required to draw the Cube
                // Create geometry and store in vertex buffer
                vertexBuffer = CreateCube();
                // Load vertex shader
                Stream shaderStream = Application.GetResourceStream(new Uri(@"SilverlightApplication3DTest;component/Cube.vs", 
                vertexShader = VertexShader.FromStream(resourceDevice, shaderStream);
                // Load pixel shader
                shaderStream = Application.GetResourceStream(new Uri(@"SilverlightApplication3DTest;component/Cube.ps", 
                pixelShader = PixelShader.FromStream(resourceDevice, shaderStream);
                // Load image for texture
                Stream imageStream = Application.GetResourceStream(new Uri(@"SilverlightApplication3DTest;component/SLXNA.png", 
                var image = new BitmapImage();
                // Create texture           
                texture = new Texture2D(resourceDevice, image.PixelWidth, image.PixelHeight, false, SurfaceFormat.Color);
                // Copy image to texture
            /// <summary>
            /// Creates a vertex buffer containing a single Cube primitive
            /// </summary>
            VertexBuffer CreateCube()
                var cube = new VertexPositionTexture[36];
                Vector3 topLeftFront = new Vector3(-1.0f, 1.0f, 1.0f);
                Vector3 bottomLeftFront = new Vector3(-1.0f, -1.0f, 1.0f);
                Vector3 topRightFront = new Vector3(1.0f, 1.0f, 1.0f);
                Vector3 bottomRightFront = new Vector3(1.0f, -1.0f, 1.0f);
                Vector3 topLeftBack = new Vector3(-1.0f, 1.0f, -1.0f);
                Vector3 topRightBack = new Vector3(1.0f, 1.0f, -1.0f);
                Vector3 bottomLeftBack = new Vector3(-1.0f, -1.0f, -1.0f);
                Vector3 bottomRightBack = new Vector3(1.0f, -1.0f, -1.0f);
                Vector2 topLeftUVXNA = new Vector2(0.0f, 0.0f);
                Vector2 topRightUVXNA = new Vector2(0.5f, 0.0f);
                Vector2 bottomLeftUVXNA = new Vector2(0.0f, 1.0f);
                Vector2 bottomRightUVXNA = new Vector2(0.5f, 1.0f);
                Vector2 topLeftUVSL = new Vector2(0.5f, 0.0f);
                Vector2 topRightUVSL = new Vector2(1.0f, 0.0f);
                Vector2 bottomLeftUVSL = new Vector2(0.5f, 1.0f);
                Vector2 bottomRightUVSL = new Vector2(1.0f, 1.0f);
                // Front face
                cube[0] = new VertexPositionTexture(topRightFront, topRightUVXNA);
                cube[1] = new VertexPositionTexture(bottomLeftFront, bottomLeftUVXNA);
                cube[2] = new VertexPositionTexture(topLeftFront, topLeftUVXNA);
                cube[3] = new VertexPositionTexture(topRightFront, topRightUVXNA);
                cube[4] = new VertexPositionTexture(bottomRightFront, bottomRightUVXNA);
                cube[5] = new VertexPositionTexture(bottomLeftFront, bottomLeftUVXNA);
                // Back face 
                cube[6] = new VertexPositionTexture(bottomLeftBack, bottomRightUVXNA);
                cube[7] = new VertexPositionTexture(topRightBack, topLeftUVXNA);
                cube[8] = new VertexPositionTexture(topLeftBack, topRightUVXNA);
                cube[9] = new VertexPositionTexture(bottomRightBack, bottomLeftUVXNA);
                cube[10] = new VertexPositionTexture(topRightBack, topLeftUVXNA);
                cube[11] = new VertexPositionTexture(bottomLeftBack, bottomRightUVXNA);
                // Top face
                cube[12] = new VertexPositionTexture(topLeftBack, topLeftUVSL);
                cube[13] = new VertexPositionTexture(topRightBack, topRightUVSL);
                cube[14] = new VertexPositionTexture(topLeftFront, bottomLeftUVSL);
                cube[15] = new VertexPositionTexture(topRightBack, topRightUVSL);
                cube[16] = new VertexPositionTexture(topRightFront, bottomRightUVSL);
                cube[17] = new VertexPositionTexture(topLeftFront, bottomLeftUVSL);
                // Bottom face 
                cube[18] = new VertexPositionTexture(bottomRightBack, topLeftUVSL);
                cube[19] = new VertexPositionTexture(bottomLeftBack, topRightUVSL);
                cube[20] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);
                cube[21] = new VertexPositionTexture(bottomRightFront, bottomLeftUVSL);
                cube[22] = new VertexPositionTexture(bottomRightBack, topLeftUVSL);
                cube[23] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);
                // Left face
                cube[24] = new VertexPositionTexture(bottomLeftFront, bottomRightUVSL);
                cube[25] = new VertexPositionTexture(bottomLeftBack, bottomLeftUVSL);
                cube[26] = new VertexPositionTexture(topLeftFront, topRightUVSL);
                cube[27] = new VertexPositionTexture(topLeftFront, topRightUVSL);
                cube[28] = new VertexPositionTexture(bottomLeftBack, bottomLeftUVSL);
                cube[29] = new VertexPositionTexture(topLeftBack, topLeftUVSL);
                // Right face 
                cube[30] = new VertexPositionTexture(bottomRightBack, bottomRightUVXNA);
                cube[31] = new VertexPositionTexture(bottomRightFront, bottomLeftUVXNA);
                cube[32] = new VertexPositionTexture(topRightFront, topLeftUVXNA);
                cube[33] = new VertexPositionTexture(bottomRightBack, bottomRightUVXNA);
                cube[34] = new VertexPositionTexture(topRightFront, topLeftUVXNA);
                cube[35] = new VertexPositionTexture(topRightBack, topRightUVXNA);
                var vb = new VertexBuffer(resourceDevice, VertexPositionTexture.VertexDeclaration, cube.Length, BufferUsage.WriteOnly);
                vb.SetData(0, cube, 0, cube.Length, 0);
                return vb;
            public void Draw(GraphicsDevice graphicsDevice, TimeSpan totalTime, Matrix viewProjection)
                // update cube transform
                Matrix position = Matrix.Identity; // origin
                Matrix scale = Matrix.CreateScale(1.0f); // no scale modifier
                // create a continuously advancing rotation
                Matrix rotation = Matrix.CreateFromYawPitchRoll(State.sliderYawVal, State.sliderPitchVal, State.sliderRollVal);
                // the world transform for the cube leaves it centered
                // at the origin but with the rotation applied
                Matrix world = scale * rotation * position;
                // calculate the final transform to pass to the shader
                Matrix worldViewProjection = world * viewProjection;
                // setup vertex pipeline
                graphicsDevice.SetVertexShaderConstantFloat4(0, ref worldViewProjection); // pass the transform to the shader
                // setup pixel pipeline
                graphicsDevice.Textures[0] = texture;
                // draw
                graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 12);

In the Cube constructor, the .png texture file is loaded, as well as the compiled pixel and vertex shaders, and stored in properties of the Cube class for use in other methods.

The VertexPositionTexture structure contains a 3D vector and a 2D vector as its properties. This structure maps the 3D coordinates of the cube’s vertices to 2D coordinates on the cube’s six different surface planes. There is a one-to-many relationship between the 3D vertices and 2D coordinates. Groups of the 2D coordinates represent a shape in which graphical information from the texture file is rendered.

These associations are assigned in a member method for the Cube class named CreateCube. The 2D coordinates, positioned at their associated 3D vertices, are grouped in square areas comprised of six individual 2D coordinates, and each group represents a face on the cube. Six coordinates are used per face because the square area is actually comprised of two interlocking right triangles, representing polygons. With six sides on the cube, and six coordinates comprising each plane, 36 total associations are entered, mapping triangular areas of the .png graphic file to be used as the texture for polygons comprising the cube.

In the Cube class’s Draw method, which is called once per frame update, the cube is textured and oriented in the 3D scene it inhabits. The orientation of the cube is set using the CreateFromYawPitchRoll method, and the orientation used is derived from the values stored in static fields that are tracking state, which will be covered later in this walkthrough. For now, note that a class named State will provide the cube’s orientation values.

Caution noteCaution:

When making use of 3D in Silverlight, the rendering of the 3D scene takes place in a graphics thread that is separate from the application’s main UI thread, because this rendering is occurring on a completely different processor (the GPU) than is used by the rest of the application. For performance reasons, it is best that the thread that handles 3D rendering is passively “pulling” state information as shown in this sample and not actively “pushing” instructions into another thread (e.g. using Dispatcher.BeginInvoke), and vice versa.

A cube can now be generated and oriented, but it still must exist somewhere in a three-dimensional space, and to be seen it must be presented in front of a virtual camera that is also positioned in this space, and is oriented to be pointing at the cube. This section describes how to create a 3D scene for the cube.

  1. In the SilverlightApplication3DTest project, add a new class named Scene.

  2. Add the following code to the class.

    using System;
    using System.Net;
    using System.Windows;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    namespace SilverlightApplication3DTest
        public class Scene
            static readonly Color transparent = new Color(0, 0, 0, 0); // avoid allocating in draw
            Matrix view; // The view or camera transform
            Matrix projection; // The projection transform to convert 3D space to 2D screen space
            // The single Cube at the root of the scene
            Cube Cube = new Cube();
            public Scene()
                Vector3 cameraPosition = new Vector3(0, 0, 4.0f); // the camera's position
                Vector3 cameraTarget = Vector3.Zero; // the place the camera is looking
                // the transform representing a camera at a position looking at a target
                view = Matrix.CreateLookAt(cameraPosition, cameraTarget, Vector3.Up);
            public float AspectRatio
                    // update the screen space transform every time the aspect ratio changes
                    projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, value, 1.0f, 100.0f);
            public void Draw(GraphicsDevice graphicsDevice, TimeSpan totalTime)
                // clear the existing render target
                graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, transparent, 1.0f, 0);
                // draw the Cube
                Cube.Draw(graphicsDevice, totalTime, view * projection);

The cube will be rendered at the origin point in the scene (at the 3D coordinate 0, 0, 0), you can point the camera at the origin and know that the cube should be visible as long as the camera is at an appropriate distance. In this sample, the distance is set so that the cube is dead-centered in the camera’s view (meaning that the X and Y coordinates are zero) and the camera is backed away somewhat from the cube (in this case, at a Z coordinate value of 4.0).

The Scene class has a property whose set method handles the SizeChanged event, which fires when the application’s viewable area is changed (usually by the user resizing the client window). This method ensures that rendering of the scene retains the correct aspect ratio, so that the cube will not be distorted that is out of proportion when the client area changes dimensions.

The Scene class has its own Draw method, which simply clears the graphics device’s resource buffers, and calls Cube’s Draw method, giving it a chance to update its orientation according to the latest information in the static state fields.

This section describes how to create the user interface and drawing surface for the cube. It also describes how to implement the event handling.

  1. Open MainPage.xaml.

  2. Replace the existing Grid with the following XAML.

    <Grid x:Name="LayoutRoot" Background="#FF57AECE">
        <DrawingSurface Draw="OnDraw" SizeChanged="DrawingSurface_SizeChanged" />
        <Slider Height="23" HorizontalAlignment="Left" Margin="50,10,0,0" Name="slider1" 
            VerticalAlignment="Top" Width="100" ValueChanged="slider1_ValueChanged" />
        <Slider Height="23" HorizontalAlignment="Left" Margin="50,30,0,0" Name="slider2" 
            VerticalAlignment="Top" Width="100" ValueChanged="slider2_ValueChanged" />
        <Slider Height="23" HorizontalAlignment="Left" Margin="50,50,0,0" Name="slider3" 
            VerticalAlignment="Top" Width="100" ValueChanged="slider3_ValueChanged" />
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="21,14,0,0" Name="label1" Text="Yaw:" 
            VerticalAlignment="Top" Foreground="White" />
        <TextBlock HorizontalAlignment="Left" Margin="17,34,0,243" Name="label2" Text="Pitch:" 
            Foreground="White" />
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="23,54,0,0" Name="label3" Text="Roll:" 
            VerticalAlignment="Top" Foreground="White" />

    In addition to some labeled sliders that will be used to control the yaw, pitch, and roll of the cube, this XAML creates a DrawingSurface element onto which the 3D scene is portrayed. The DrawingSurface element has two primary events. The first one is SizeChanged, which fires whenever the viewable client area changes. This event was discussed earlier when examining the maintenance of the scene’s aspect ratio.

    The other important event is Draw, which fires when one of three things happens: 1) the application is first executed, 2) the primary graphics device changes states from Not Available to Available, or 3) the DrawEventArgs method InvalidateSurface (or DrawingSurface method Invalidate) is called, scheduling a callback that is automatically wired to fire the Draw event again. Although Silverlight 5’s 3D functionality is provided by XNA’s core graphics libraries, there is no 3D render loop that is constantly “ticking” as there is in XNA. Instead, the cycle of InvalidateSurface firing the Draw event is what creates a constantly updating display of the 3D scene.

  3. Open MainPage.xaml.cs.

  4. Add the following code to implement event handling.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Graphics;
    namespace SilverlightApplication3DTest
        public static class State
            public static float sliderYawVal = new float();
            public static float sliderPitchVal = new float();
            public static float sliderRollVal = new float();
        public partial class MainPage : UserControl
            // init the 3D scene
            Scene scene = new Scene();
            public MainPage()
            void OnDraw(object sender, DrawEventArgs args)
                // draw 3D scene
                scene.Draw(GraphicsDeviceManager.Current.GraphicsDevice, args.TotalTime);
                // invalidate to get a callback next frame
            // update the aspect ratio of the scene based on the
            // dimensinos of the surface
            private void DrawingSurface_SizeChanged(object sender, SizeChangedEventArgs e)
                DrawingSurface surface = sender as DrawingSurface;
                scene.AspectRatio = (float)surface.ActualWidth / (float)surface.ActualHeight;
            private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
                State.sliderYawVal = (float)e.NewValue;
            private void slider2_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
                State.sliderPitchVal = (float)e.NewValue;
            private void slider3_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
                State.sliderRollVal = (float)e.NewValue;

    This code includes a class named State that receives updated values from the sliders, making them available to any thread that can process them without requiring a local instance of the data be created first. The reading of these values was shown earlier in the Cube.cs file.

    The OnDraw method handles the Draw event, which sets up the InvalidateSurface “loop.” An initial firing of the InvalidateSurface event ensures that the loop gets started. As shown earlier, a call to Scene’s Draw method will fire the Cube’s Draw method.

    Lastly, the handler function for SizeChanged shows the AspectRatio property is being set, and the code from Scene shown earlier reveals that the custom set method ensures an appropriate scaling of the visual assets.

This section describes how to build and test the sample.

  1. Build the solution.

  2. Make sure the SilverlightApplication3DTest.Web is set as the startup project.

  3. Run the sample.

    You should see an image with three sliders.

  4. Change the Yaw, Pitch, and Roll sliders to animate the cube.

© 2015 Microsoft