[05/10/2011 : Adding StatesManager]

I just created a new Codeplex project for adding some high level features to Silverlight 5:

http://babylontoolkit.codeplex.com/

You can also grab it using NuGet :

http://www.nuget.org/List/Packages/Babylon.Toolkit

image

Effect class

With Babylon.Toolkit you will be able to create effects around your shaders.

Shaders must be compiled with a custom build task you can install with the ($Project)\Libs\ShaderBuildTaskSetup.msi. Once the task is installed, you can set PixelShader or VertexShader build action to your shaders:

image

To use your shader, just create a class inheriting from Effect class (or encapsulating an effect):

public class BasicEffect : Effect { public BasicEffect(GraphicsDevice device) : base(device, "Babylon.Toolbox", "BasicEffect/BasicEffect") { ... } ... }

Effect instances are optimized by using a shaders and registers cache. So you can have many effects on the same shader as the cache will only instantiate one shader and will reference it on all effects.

You can affect effect’s parameters by using direct affectation or by using EffectParameter class:

public class BasicEffect : Effect { readonly EffectParameter worldViewProjectionParameter; readonly EffectParameter worldParameter; public Matrix World { get; set; } public Matrix View { get; set; } public Matrix Projection { get; set; } public BasicEffect(GraphicsDevice device) : base(device, "Babylon.Toolbox", "BasicEffect/BasicEffect") { worldViewProjectionParameter = GetParameter("WorldViewProjection"); worldParameter = GetParameter("World"); } public override void Apply() { worldViewProjectionParameter.SetValue(World * View * Projection); worldParameter.SetValue(World); base.Apply(); } }

You can find a complete shaders sample in the project Babylon.Toolkit. The class called BasicEffect provides a simple way to do basic rendering with the following features:

  • Ambient color
  • Emissive color
  • Diffuse color and texture
  • Alpha transparency
  • One point light

Camera classes

Babylon.Toolkit provides 2 cameras classes:

  • Camera : a standard camera defined by a position and a target
  • OrbitCamera : an orbital camera defined by a target and two angles representing rotation angles around the target

Camera classes provides View and Projection matrices you can use with your shaders/effects.

For instance, here is a sample code of an application using OrbitCamera:

public partial class MainPage { bool mouseLeftDown; Point startPosition; readonly OrbitCamera camera = new OrbitCamera{Radius = 15}; public MainPage() { InitializeComponent(); } private void DrawingSurface_Draw(object sender, DrawEventArgs e) { camera.ApplyInertia(); // Draw // Invalidate e.InvalidateSurface(); } private void drawingSurface_SizeChanged(object sender, SizeChangedEventArgs e) { ComputeAspectRatio(); } void ComputeAspectRatio() { camera.AspectRatio = (float)(drawingSurface.ActualWidth / drawingSurface.ActualHeight); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { ComputeAspectRatio(); } private void drawingSurface_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) { if (!mouseLeftDown) return; Point currentPosition = e.GetPosition(drawingSurface); camera.InertialAlpha += (float)(currentPosition.X - startPosition.X) * camera.AngularSpeed; camera.InertialBeta -= (float)(currentPosition.Y - startPosition.Y) * camera.AngularSpeed; startPosition = currentPosition; } private void LayoutRoot_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { mouseLeftDown = true; startPosition = e.GetPosition(drawingSurface); } private void LayoutRoot_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { mouseLeftDown = false; } private void LayoutRoot_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) { mouseLeftDown = false; } private void LayoutRoot_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e) { camera.Radius -= e.Delta * camera.Radius / 1000.0f; } }

Model/ModelMesh/ModelMeshPart classes

The Model class represents a set of meshes defined by ModelMesh classes. A ModelMesh is a set of ModelMeshPart where each part has its own Effect instance.

The ModelMesh contains the vertex and index buffer. Each part will be drawn by using a segment of the ModelMesh index buffer. So for rendering a Model, you just have to call the following code:

e.GraphicsDevice.BlendState = BlendState.AlphaBlend; e.GraphicsDevice.DepthStencilState = DepthStencilState.Default; e.GraphicsDevice.RasterizerState = RasterizerState.CullNone; e.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Color(0, 0, 0, 0), 1.0f, 0); foreach (ModelMesh mesh in model.Meshes) { foreach (BasicEffect basicEffect in mesh.Effects) { basicEffect.World = Matrix.Identity; basicEffect.View = camera.View; basicEffect.Projection = camera.Projection; basicEffect.LightPosition = camera.Position; basicEffect.CameraPosition = camera.Position; } } model.Draw();

To create a complete Model, you have to fill the object model with data such as vertices, indices and definitions of each part. When an instance is ready, you have to call Freeze() method to close it. Only frozen objects can be drawn :

// Model Model result = new Model(); // ModelMesh(s) currentMesh = new ModelMesh<VertexPositionNormalTexture>(device, (importationOptions & ImportationOptions.Optimize) == ImportationOptions.Optimize); result.AddMesh(currentMesh); result.AddVertex(..); // Many times result.AddIndex(...); // Many times too // ModelMeshPart(s) currentPart = new ModelMeshPart(device); currentPart.Effect = new BasicEffect(device); currentMesh.AddPart(currentPart); currentPart.PrimitivesCount = ...; currentPart.NumVertices = ...; currentPart.StartIndex = ...; currentPart.MinVertexIndex = ...; currentPart.Freeze(); currentMesh.Freeze(); result.Freeze();

Importation system

The importation system define IModelImporter:

public interface IModelImporter { event Action<Model> OnImportCompleted; event Action<int> OnImportProgressChanged; void ImportAsync(Stream source, Func<string, Stream> resourceLocator, GraphicsDevice device, ImportationOptions importationOptions = ImportationOptions.None); }

An importer must implement the previous interface.

Babylon.Toolkit provides a sample Wavefront OBJ importer :

Stream objStream = Application.GetResourceStream(new Uri("/Babylon.Toolbox.Sample;component/Models/temp.obj", UriKind.Relative)).Stream; ObjImporter importer = new ObjImporter(); importer.OnImportCompleted += m => { ... }; // Report loading progress importer.OnImportProgressChanged += p => { ... }; importer.ImportAsync(objStream, GetMaterialStream, device, ImportationOptions.Optimize);

States Manager

Babylon.Toolkit includes a a class called StatesManager which allows developers to affect GraphicsDevice’s states without creating individual objects. A cache system handles duplicates and provides great performances when setting states :

statesManager = new StatesManager(e.GraphicsDevice); // States statesManager.DepthBufferEnable = true; statesManager.CullMode = CullMode.None; statesManager.ApplyDepthStencilState(); statesManager.ApplyRasterizerState();

States are built on demand when you call ApplyXXXX().

Conclusion

Feel free to use Babylon.Toolkit in your own project. And do not hesitate to contact me if you have any questions Sourire