Babylon Toolkit : a toolbox for developing 3D applications with Silverlight 5

[05/10/2011 : Adding StatesManager]

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

https://babylontoolkit.codeplex.com/

You can also grab it using NuGet :

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

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)LibsShaderBuildTaskSetup.msi. Once the task is installed, you can set PixelShader or VertexShader build action to your shaders:

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)
{

}

}




<p>
  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.
</p>

<p>
  You can affect effect’s parameters by using direct affectation or by using EffectParameter class:
</p>

<div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
  <pre style="background-color: white; width: 789px; height: 366px; overflow: auto"><div>

public class BasicEffect : Effect
{
readonly EffectParameter worldViewProjectionParameter;
readonly EffectParameter worldParameter;

</span><span style="color: #0000ff">public</span><span style="color: #000000"> Matrix World { </span><span style="color: #0000ff">get</span><span style="color: #000000">; </span><span style="color: #0000ff">set</span><span style="color: #000000">; }
</span><span style="color: #0000ff">public</span><span style="color: #000000"> Matrix View { </span><span style="color: #0000ff">get</span><span style="color: #000000">; </span><span style="color: #0000ff">set</span><span style="color: #000000">; }
</span><span style="color: #0000ff">public</span><span style="color: #000000"> Matrix Projection { </span><span style="color: #0000ff">get</span><span style="color: #000000">; </span><span style="color: #0000ff">set</span><span style="color: #000000">; }

</span><span style="color: #0000ff">public</span><span style="color: #000000"> BasicEffect(GraphicsDevice device)
    : </span><span style="color: #0000ff">base</span><span style="color: #000000">(device, </span><span style="color: #800000">"</span><span style="color: #800000">Babylon.Toolbox</span><span style="color: #800000">"</span><span style="color: #000000">, </span><span style="color: #800000">"</span><span style="color: #800000">BasicEffect/BasicEffect</span><span style="color: #800000">"</span><span style="color: #000000">)
{
    worldViewProjectionParameter </span><span style="color: #000000">=</span><span style="color: #000000"> GetParameter(</span><span style="color: #800000">"</span><span style="color: #800000">WorldViewProjection</span><span style="color: #800000">"</span><span style="color: #000000">);
    worldParameter </span><span style="color: #000000">=</span><span style="color: #000000"> GetParameter(</span><span style="color: #800000">"</span><span style="color: #800000">World</span><span style="color: #800000">"</span><span style="color: #000000">);
}

</span><span style="color: #0000ff">public</span><span style="color: #000000"> </span><span style="color: #0000ff">override</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> Apply()
{         
    worldViewProjectionParameter.SetValue(World </span><span style="color: #000000">*</span><span style="color: #000000"> View </span><span style="color: #000000">*</span><span style="color: #000000"> Projection);
    worldParameter.SetValue(World);

    </span><span style="color: #0000ff">base</span><span style="color: #000000">.Apply();
}

}

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <p>
    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:
  </p>

  <ul>
    <li>
      Ambient color
    </li>
    <li>
      Emissive color
    </li>
    <li>
      Diffuse color and texture
    </li>
    <li>
      Alpha transparency
    </li>
    <li>
      One point light
    </li>
  </ul>

  <h1>
    Camera classes
  </h1>

  <p>
    Babylon.Toolkit provides 2 cameras classes:
  </p>

  <ul>
    <li>
      Camera : a standard camera defined by a position and a target
    </li>
    <li>
      OrbitCamera : an orbital camera defined by a target and two angles representing rotation angles around the target
    </li>
  </ul>

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

  <p>
    For instance, here is a sample code of an application using OrbitCamera:
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
    <pre style="background-color: white; width: 789px; height: 1060px; overflow: auto"><div>

public partial class MainPage
{
bool mouseLeftDown;
Point startPosition;
readonly OrbitCamera camera = new OrbitCamera{Radius = 15};

</span><span style="color: #0000ff">public</span><span style="color: #000000"> MainPage()
{
    InitializeComponent();
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> DrawingSurface_Draw(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, DrawEventArgs e)
{
    camera.ApplyInertia();

    </span><span style="color: #008000">//</span><span style="color: #008000"> Draw

    </span><span style="color: #008000">//</span><span style="color: #008000"> Invalidate</span><span style="color: #008000">

e.InvalidateSurface();
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> drawingSurface_SizeChanged(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, SizeChangedEventArgs e)
{
    ComputeAspectRatio();
}

</span><span style="color: #0000ff">void</span><span style="color: #000000"> ComputeAspectRatio()
{
    camera.AspectRatio </span><span style="color: #000000">=</span><span style="color: #000000"> (</span><span style="color: #0000ff">float</span><span style="color: #000000">)(drawingSurface.ActualWidth </span><span style="color: #000000">/</span><span style="color: #000000"> drawingSurface.ActualHeight);
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> UserControl_Loaded(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, RoutedEventArgs e)
{
    ComputeAspectRatio();
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> drawingSurface_MouseMove(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, System.Windows.Input.MouseEventArgs e)
{
    </span><span style="color: #0000ff">if</span><span style="color: #000000"> (</span><span style="color: #000000">!</span><span style="color: #000000">mouseLeftDown)
        </span><span style="color: #0000ff">return</span><span style="color: #000000">;

    Point currentPosition </span><span style="color: #000000">=</span><span style="color: #000000"> e.GetPosition(drawingSurface);
    camera.InertialAlpha </span><span style="color: #000000">+=</span><span style="color: #000000"> (</span><span style="color: #0000ff">float</span><span style="color: #000000">)(currentPosition.X </span><span style="color: #000000">-</span><span style="color: #000000"> startPosition.X) </span><span style="color: #000000">*</span><span style="color: #000000"> camera.AngularSpeed;
    camera.InertialBeta </span><span style="color: #000000">-=</span><span style="color: #000000"> (</span><span style="color: #0000ff">float</span><span style="color: #000000">)(currentPosition.Y </span><span style="color: #000000">-</span><span style="color: #000000"> startPosition.Y) </span><span style="color: #000000">*</span><span style="color: #000000"> camera.AngularSpeed;


    startPosition </span><span style="color: #000000">=</span><span style="color: #000000"> currentPosition;
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> LayoutRoot_MouseLeftButtonDown(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, System.Windows.Input.MouseButtonEventArgs e)
{
    mouseLeftDown </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #0000ff">true</span><span style="color: #000000">;
    startPosition </span><span style="color: #000000">=</span><span style="color: #000000"> e.GetPosition(drawingSurface);
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> LayoutRoot_MouseLeftButtonUp(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, System.Windows.Input.MouseButtonEventArgs e)
{
    mouseLeftDown </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #0000ff">false</span><span style="color: #000000">;
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> LayoutRoot_MouseEnter(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, System.Windows.Input.MouseEventArgs e)
{
    mouseLeftDown </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #0000ff">false</span><span style="color: #000000">;
}

</span><span style="color: #0000ff">private</span><span style="color: #000000"> </span><span style="color: #0000ff">void</span><span style="color: #000000"> LayoutRoot_MouseWheel(</span><span style="color: #0000ff">object</span><span style="color: #000000"> sender, System.Windows.Input.MouseWheelEventArgs e)
{
    camera.Radius </span><span style="color: #000000">-=</span><span style="color: #000000"> e.Delta </span><span style="color: #000000">*</span><span style="color: #000000"> camera.Radius </span><span style="color: #000000">/</span><span style="color: #000000"> </span><span style="color: #800080">1000.0f</span><span style="color: #000000">;
}

}

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <h1>
    Model/ModelMesh/ModelMeshPart classes
  </h1>

  <p>
    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.
  </p>

  <p>
    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:
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
    <pre style="background-color: white; width: 789px; height: 304px; overflow: auto"><div>

e.GraphicsDevice.BlendState = BlendState.AlphaBlend;
e.GraphicsDevice.DepthStencilState
= DepthStencilState.Default;
e.GraphicsDevice.RasterizerState
= RasterizerState.CullNone;

e.GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, new Color(, , , ), 1.0f, );

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

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <p>
    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 :
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
    <pre style="background-color: white; width: 789px; height: 436px; overflow: auto"><div>

// 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();


<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <h1>
    Importation system
  </h1>

  <p>
    The importation system define IModelImporter:
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
    <pre style="background-color: white; width: 789px; height: 131px; overflow: auto"><div>

public interface IModelImporter
{
event Action<Model> OnImportCompleted;
event Action<int> OnImportProgressChanged;

</span><span style="color: #0000ff">void</span><span style="color: #000000"> ImportAsync(Stream source, Func</span><span style="color: #000000">&lt;</span><span style="color: #0000ff">string</span><span style="color: #000000">, Stream</span><span style="color: #000000">&gt;</span><span style="color: #000000"> resourceLocator, GraphicsDevice device, 
     ImportationOptions importationOptions </span><span style="color: #000000">=</span><span style="color: #000000"> ImportationOptions.None);

}

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <p>
    An importer must implement the previous interface.
  </p>

  <p>
    Babylon.Toolkit provides a sample Wavefront OBJ importer :
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" class="wlWriterSmartContent">
    <pre style="background-color: white; width: 789px; height: 255px; overflow: auto"><div>

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

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <h1>
    States Manager
  </h1>

  <p>
    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 :
  </p>

  <div style="padding-bottom: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; float: none; padding-top: 0px" id="scid:9D7513F9-C04C-4721-824A-2B34F0212519:f9179a28-64a3-4238-aab9-b9d5ad6a4b5c" class="wlWriterEditableSmartContent">
    <pre style=" width: 789px; height: 116px;background-color:White;overflow: auto;"><div>

statesManager = new StatesManager(e.GraphicsDevice);

// States
statesManager.DepthBufferEnable = true;
statesManager.CullMode
= CullMode.None;
statesManager.ApplyDepthStencilState();
statesManager.ApplyRasterizerState();

<p>
  <!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin.  https://dunnhq.com --></div> 

  <p>
    States are built on demand when you call ApplyXXXX().
  </p>

  <h1>
    Conclusion
  </h1>

  <p>
    Feel free to use Babylon.Toolkit in your own project. And do not hesitate to contact me if you have any questions <img style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" class="wlEmoticon wlEmoticon-smile" alt="Sourire" src="https://msdnshared.blob.core.windows.net/media/MSDNBlogsFS/prod.evol.blogs.msdn.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/01/44/73/metablogapi/0410.wlEmoticon-smile_72ABC529.png" original-url="https://blogs.msdn.com/cfs-file.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-44-73-metablogapi/0410.wlEmoticon_2D00_smile_5F00_72ABC529.png" />
  </p>