(This will be a little brief. A fuller treatment will be given at some point in the future.)
FbxSharp[nuget] is a pure C# library for loading FBX files. ChamberLib [nuget] is a cross-platform library for making 3d games a little more easily, the beginnings of a Game engine. Naturally, these two ought to be usable together.
FbxSharp's object model is patterned after that of the FBX SDK. It has a much richer object model than what is typically needed for video games and other real-time graphics applications.
ChamberLib has an interface named IModel
, which represents a 3d model. It has methods for drawing the model, setting lighting parameters, texture map, bone transformations for skinned models, and a few other things. Different subsystems will have corresponding classes that implement the interface. (Currently, the OpenTK subsystem is the only one implemented. It has a Model
class that draw using OpenTK/OpenGL. But the details are not important for this tutorial.)
The task at hand is to translate from FbxSharp's expansive object format to ChamberLib's simplified and streamlined format. The ChamberLib.FbxSharp [nuget] library does just that. Just instantiate an FbxModelImporter
and pass the ImportModel
method to your content importer, like so:
var importer =
new BuiltinContentImporter( // resolve builtin resources
new ContentImporter(
new FbxModelImporter().ImportModel, // model importer
new BasicTextureImporter().ImportTexture, // texture importer
new GlslShaderImporter().ImportShaderStage, // shader importer
null, // font importer
null, // song importer
null)); // sound effect importer
Now, pass the importer to the subsystem's constructor:
var subsystem = new OpenTKSubsystem(3, 3,
onLoadMethod: Load
onRenderFrameMethod: Draw,
contentImporter: importer);
Next, create the function that will load the model from the file. This is the same Load
method we just referred to in the creation of the subsystem.
public static void Load()
{
model = subsystem.ContentManager.LoadModel("model.fbx");
model.SetAmbientLightColor(new Vector3(0.3f, 0.3f, 0.3f));
model.SetDirectionalLight(_light, false);
}
We'll also need to define the light source, and provide a field to store the model:
static IModel model;
static ChamberLib.DirectionalLight _light =
new ChamberLib.DirectionalLight(
direction: new Vector3(-1, -1, 0).Normalized(),
diffuseColor: new Vector3(0.9f, 0.8f, 0.7f),
specularColor: Vector3.Zero,
enabled: true);
Next, create the function that will draw the model:
public static void Draw(GameTime gameTime)
{
var id = Matrix.Identity;
subsystem.Renderer.DrawLine (Vector3.UnitX, id, view, projection, Vector3.Zero, Vector3.UnitX);
subsystem.Renderer.DrawLine (Vector3.UnitY, id, view, projection, Vector3.Zero, Vector3.UnitY);
subsystem.Renderer.DrawLine (Vector3.UnitZ, id, view, projection, Vector3.Zero, Vector3.UnitZ);
var world = Matrix.CreateScale (0.005f);
model.Draw (world, view, projection);
}
The version of Blender used for the example seems to have a curious habit of increasing the scale of the model by 100, so it has to be re-scaled by 0.01 when drawing (plus an additional amount just to get it the right size, bringing the final scaling factor to 0.005). If you have similar troubles for a different model, you can adjust the scaling factor here to whatever you need.
Next, we'll need to define a pair of transformations, the view and the projection, that will correctly position the model within the cameras view-space, and then transfer it from 3D coordinates to the 2D screen. These matrices are used by the Draw
method.
// back in the Main method
view = Matrix.CreateLookAt (new Vector3 (2, 1.5f, -4),
Vector3.Zero, Vector3.UnitY);
projection = Matrix.CreateOrthographic (6, 4, 0.25f, 1false);
Finally, we need to tell the subsystem to start running:
subsystem.Run ();
This will start up the game loop. First, it will call our Load
method, and then it will repeatedly call Draw
, until we close the window. This is the minimum needed to load and render a 3D model on the screen. This is just enough to get started with. Additional work will be required to move the, model, animate it, or provide input from the user.
Here's the full source code listing:
using System;
using ChamberLib.OpenTK;
using ChamberLib.Content;
namespace ChamberLib.FbxSharp.Example
{
class MainClass
{
public static void Main (string[] args)
{
importer =
new BuiltinContentImporter( // resolve builtin resources
new ContentImporter(
new FbxModelImporter().ImportModel, // model importer
new BasicTextureImporter().ImportTexture, // texture importer
new GlslShaderImporter().ImportShaderStage, // shader importer
null, // font importer
null, // song importer
null)); // sound effect importer
subsystem = new OpenTKSubsystem(
3, 3,
onLoadMethod: Load,
onRenderFrameMethod: Draw,
contentImporter: importer);
view = Matrix.CreateLookAt (new Vector3 (2, 1.5f, -4),
Vector3.Zero, Vector3.UnitY);
projection = Matrix.CreateOrthographic (6, 4, 0.25f, 1false);
subsystem.Run ();
}
static IContentImporter importer;
static ISubsystem subsystem;
static Matrix view;
static Matrix projection;
static IModel model;
static ChamberLib.DirectionalLight _light =
new ChamberLib.DirectionalLight(
direction: new Vector3(-1, -1, 0).Normalized(),
diffuseColor: new Vector3(0.9f, 0.8f, 0.7f),
specularColor: Vector3.Zero,
enabled: true);
public static void Load()
{
model = subsystem.ContentManager.LoadModel("model.fbx");
model.SetAmbientLightColor(new Vector3(0.3f, 0.3f, 0.3f));
model.SetDirectionalLight(_light, false);
}
public static void Draw(GameTime gameTime)
{
var id = Matrix.Identity;
subsystem.Renderer.DrawLine (Vector3.UnitX, id, view, projection, Vector3.Zero, Vector3.UnitX);
subsystem.Renderer.DrawLine (Vector3.UnitY, id, view, projection, Vector3.Zero, Vector3.UnitY);
subsystem.Renderer.DrawLine (Vector3.UnitZ, id, view, projection, Vector3.Zero, Vector3.UnitZ);
var world = Matrix.CreateScale (0.005f);
model.Draw (world, view, projection);
}
}
}
You can find all the project files here:
https://github.com/izrik/ChamberLib.FbxSharp.Example