Silverlight 5–What’s New #4

April 13, 2011

one comment

This post is dedicated to 3D graphics in Silverlight 5. The graphics engine is pretty similar to XNA and for better understanding I suggest to read some 3D graphics theory and some XNA documentation.

To display the 3D graphics Silverlight 5 adds a new FrameworkElement called “DrawingSurface”. To use it, first you application must have GPU acceleration enabled at the Silverlight plug-in level:

<param name="EnableGPUAcceleration" value="true" />

Once your plug-in set, the new element could be added and used from XAML:

<Grid x:Name="LayoutRoot" Background="LightGoldenrodYellow">
        <DrawingSurface x:Name="surface" Draw="DrawingSurface_Draw" 
                        SizeChanged="DrawingSurface_SizeChanged"/>
</Grid>

The 3D graphics works in immediate graphics mode and draws directly on the screen surface. In order to draw the application should subscribe to the Draw event; this event will be called every time the application should draw a new frame.

In order to draw at the 3D model (even the simplest one I’ll use for this sample) we still need to initialize pretty much things. The minimum initialization set includes the model vertices and the “camera”. In my case I’ll also initialize the texture to paint on the model. I’ll use pixel & vertex shaders to apply texture on the model. The shaders should be compiled and added to the solution as a resources:

image

To create a shaders you have to use a High Level Shader Language (HLSL) and compile into binary form to be used in the application. Compilation cold be done with DirectX SDK command line tool called “fxc.exe”. Once compiled and added as resources to the project they could be loaded at the runtime and initialized as a managed class instance:

VertexShader vertexShader;
PixelShader pixelShader;
Stream shaderStream = Application.GetResourceStream(new Uri(@"3DSample;component/OurHLSLfile.vs", UriKind.Relative)).Stream;
vertexShader = VertexShader.FromStream(device, shaderStream);

shaderStream = Application.GetResourceStream(new Uri(@"3DSample;component/OurHLSLfile.ps", UriKind.Relative)).Stream;
pixelShader = PixelShader.FromStream(device, shaderStream);

For shader initialization the application should provide an instance of GraphicsDevice which could be obtained from GraphicsDeviceManager:

GraphicsDeviceManager graphics;
GraphicsDevice device;
graphics = GraphicsDeviceManager.Current;
device = graphics.GraphicsDevice;

The Texture2D instance from resource is according to the following:

Texture2D streetTexture;
// Load image for texture
Stream imageStream = Application.GetResourceStream(new Uri(@"3DSample;component/streettexture.png", UriKind.Relative)).Stream;
var image = new BitmapImage();
image.SetSource(imageStream);

// Create texture           
streetTexture = new Texture2D(graphics.GraphicsDevice, image.PixelWidth, image.PixelHeight, false, SurfaceFormat.Color);

// Copy image to texture
image.CopyTo(streetTexture);

Now to the camera creation. We have to specify the position of the camera in a 3D space, the view matrix (where camera looks at) and the projection matrix (perspective field of view of the camera):

Microsoft.Xna.Framework.Matrix viewMatrix;
Microsoft.Xna.Framework.Matrix projectionMatrix;
Vector3 cameraPos;
float aspectRatio = (float)surface.ActualWidth / (float)surface.ActualHeight;

cameraPos = new Vector3(-25, 13, 18);
viewMatrix = Microsoft.Xna.Framework.Matrix.CreateLookAt(cameraPos,
    new Vector3(0, 2, -12), new Vector3(0, 1, 0));
projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
    aspectRatio, 1.0f, 200.0f);

When the camera is set (in my case static camera looking to the specific point in space) I’ll setup my model. The model could be specified my model file (like .X or .FBX) or as a set of Vertices.

Note: Unlike XNA, Silverlight 5 doesn’t provide any built-in model importer, which means the model files need to be loaded and parsed by developer at runtime. 

To load information about my very simple model I used the following structure (combines the vector position and corresponding coordinate at the texture):

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));
}

The initialization of vertices is the follows:

VertexBuffer vertexBuffer;
VertexPositionTexture[] vertices = new VertexPositionTexture[12];

vertices[0] = new VertexPositionTexture(new Vector3(-20, 0, 10), new Vector2(-0.25f, 25.0f));
vertices[1] = new VertexPositionTexture(new Vector3(-20, 0, -100), new Vector2(-0.25f, 0.0f));
vertices[2] = new VertexPositionTexture(new Vector3(2, 0, 10), new Vector2(0.25f, 25.0f));
vertices[3] = new VertexPositionTexture(new Vector3(2, 0, -100), new Vector2(0.25f, 0.0f));
vertices[4] = new VertexPositionTexture(new Vector3(2, 1, 10), new Vector2(0.375f, 25.0f));
vertices[5] = new VertexPositionTexture(new Vector3(2, 1, -100), new Vector2(0.375f, 0.0f));
vertices[6] = new VertexPositionTexture(new Vector3(3, 1, 10), new Vector2(0.5f, 25.0f));
vertices[7] = new VertexPositionTexture(new Vector3(3, 1, -100), new Vector2(0.5f, 0.0f));
vertices[8] = new VertexPositionTexture(new Vector3(13, 1, 10), new Vector2(0.75f, 25.0f));
vertices[9] = new VertexPositionTexture(new Vector3(13, 1, -100), new Vector2(0.75f, 0.0f));
vertices[10] = new VertexPositionTexture(new Vector3(13, 21, 10), new Vector2(1.25f, 25.0f));
vertices[11] = new VertexPositionTexture(new Vector3(13, 21, -100), new Vector2(1.25f, 0.0f));

var vb = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration,
    vertices.Length, BufferUsage.WriteOnly);
vb.SetData(0, vertices, 0, vertices.Length, 0);

vertexBuffer = vb;

Now, when the vertices are initialized the model could be presented during the draw call:

device = e.GraphicsDevice;

//Clear the surface
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, theColor, 1.0f, 0);

//Calculate the world view projection
Matrix worldViewProjection = viewMatrix * projectionMatrix;

//Set Vertex Buffer & Vertex shader
device.SetVertexBuffer(vertexBuffer);
device.SetVertexShader(vertexShader);
device.SetVertexShaderConstantFloat4(0, ref worldViewProjection);

//Set Pixel Shader
device.SetPixelShader(pixelShader);
device.Textures[0] = streetTexture;
device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, vertexBuffer.VertexCount);

//Invalidate the surface to make sure it redraws
e.InvalidateSurface();

The result looks like the following:

image

The sources of the project are here.

 

Stay tuned,

Alex

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Ryan WatsonJuly 19, 2012 ב 17:26

    Silverlight is on the way of growth and growing very fast, with the improvement on latest versions.

    Reply