This is a class hierarchy for drawing simple meshes with support for loading models from Visual Studio 3D Starter Kit .CMO files and legacy DirectX SDK .SDKMESH files. It is an implementation of a mesh renderer similar to the XNA Game Studio Model, ModelMesh, ModelMeshPart design.

NOTE: Support for loading keyframe animations is not yet included.

A Model consists of one or more ModelMesh instances. The ModelMesh instances can be shared by multiple instances of Model. A ModelMesh instance consists of one or more ModelMeshPart instances.

Each ModelMeshPart references an index buffer, a vertex buffer, an input layout, an Effects instance, and includes various metadata for drawing the geometry. Each ModelMeshPart represents a single material to be drawn at the same time (i.e. a submesh).

ModelExample.jpg

Header

#include <Model.h>

Initialization

Model instances can be loaded from either .CMO files or .SDKMESH files, or from custom file formats. The Model loaders take an EffectFactory instance to facilitate the sharing of Effects and textures between models. The default EffectFactory always returns built-in BasicEffect instances. The DGSLEffectFactory can be used with .CMO files to load Visual Studio Shader Designer (DGSL) shaders through DGLSEffect instances.

Visual Studio 2012 and 2013 include a built-in content pipeline that can generate .CMO files from an Autodesk FBX, as well as DDS texture files from various bitmap image formats, as part of the build process. See the Visual Studio 3D Starter Kit for details. http://aka.ms/vs3dkitwin for Windows 8.1, http://aka.ms/vs3dkitwin80 for Windows 8.0.

DGSLEffectFactory fx( device );
// Can also use EffectFactory, but will ignore pixel shader material settings

auto teapot = Model::CreateFromCMO( device, L"teapot.cmo", fx );

The legacy DirectX SDK has an exporter that will generate .SDKMESH files from an Autodesk FBX. The latest version of this exporter tool can be obtained from http://go.microsoft.com/fwlink/?LinkId=226208

EffectFactory fx( device );
// Can also use DGSLEffectFactory, but will always use default materials

auto tiny = Model::CreateFromSDKMESH( device, L"tiny.sdkmesh", fx );
A Model instance also contains a name (a wide-character string) for tracking and application logic. Model can be copied to create a new Model instance which will have shared references to the same set of ModelMesh instances (i.e. a 'shallow' copy).

Simple drawing

The Model::Draw functions provides a high-level, easy to use method for drawing models.

CommonStates states(device);

XMMATRIX local = XMMatrixTranslation( 1.f, 1.f, 1.f );
local = XMMatrixMultiply( world, local );
tiny->Draw( context, states, local, view, projection );
There are optional parameters for rendering in wireframe and to provide a custom state override callback.

Advanced drawing

Rather than using the standard Model::Draw, the ModelMesh::Draw method can be used on each mesh in turn listed in the Model::meshes collection. ModelMesh::Draw can be used to draw all the opaque parts or the alpha parts individually. The ModelMesh::PrepareForRendering method can be used as a helper to setup common render state, or the developer can set up the state directly before calling ModelMesh::Draw. See ModelMesh for an example.

More detailed control over rendering can be had by skipping the use of Model::Draw and ModelMesh::Draw in favor of the ModelMeshPart::Draw method. Each Model::meshes collection can be scanned for each ModelMesh::meshParts collection to enumerate all ModelMeshPart instances. For this version of draw, the ModelMeshPart::effect and ModelMeshPart::inputLayout can be used, or a custom effect override can be used instead (be sure to create the appropriate matching input layout for the custom effect beforehand using ModelMeshPart::CreateInputLayout). See ModelMeshPart for an example.

Effects control

The Model loaders create an appropriate Effects instance for each ModelMeshPart in a mesh. Generally all effects in a mesh should use the same lighting and fog settings, which is facilitated by the Model::UpdateEffects method. This calls back for each unique effect in the ModelMesh once.

tiny->UpdateEffects([&](IEffect* effect)
{
    auto lights = dynamic_cast<IEffectLights*>(effect);
    if ( lights )
    {
        XMVECTOR dir = XMVector3Rotate( g_XMOne, quat );
        lights->SetLightDirection( 0, dir );
    }
    auto fog = dynamic_cast<IEffectFog*>(effect);
    if ( fog )
    {
        fog->SetFogEnabled(true);
        fog->SetFogStart(6); // assuming RH coordiantes
        fog->SetFogEnd(8);
        fog->SetFogColor(Colors::CornflowerBlue);
    }
});
It is also possible to change the Effects instance used by a given part (such as when overriding the default effect put in place from a Model loader) by calling ModelMeshPart::ModifyEffect. This will regenerate the ModelMeshPart::inputLayout appropriately.

Be sure to call Model::Modified on all Model instances that reference the impacted ModelMesh instance to ensure the cache used by UpdateEffects is correctly updated. Model::Modified should also be called whenever a Model::meshes or ModelMesh::meshParts collection is modified.

As noted above, it is also possible to render part or all of a model using a custom effect as an override, rather than changing the effect referenced by the ModelMeshPart::effect directly. See ModelMeshPart for an example.

Alpha blending

Proper drawing of alpha-blended models can be a complicated procedure. Each ModelMeshPart has a bool value to indicate if the associated part is fully opaque (isAlpha is false), or has some level of alpha transparency (isAlpha is true). The Model::Draw routine handles some basic logic for the rendering, first rendering the opaque parts, then rendering the alpha parts. More detailed control is provided by the ModelMesh::Draw method which can be used to draw all opaque parts of all meshes first, then go back and draw all alpha parts of all meshes second. See ModelMesh for an example.

To indicate the use of ‘straight’ alpha vs. ‘premultiplied’ alpha blending modes, ModelMesh::pmalpha is set by the various loaders functions controlled by a default parameter (which defaults false to indicate the texture files are using 'straight' alpha). If you make use of DirectXTex's texconv tool with the -pmalpha switch, you should use pmalpha=true instead.

Custom render states

All the various Draw method provide a setCustomState callback which can be used to change the state just before the geometry is drawn.

tiny->Draw( context, states, local, view, projection, false, [&]()
{
    ID3D11ShaderResourceView* srv = nullptr;
    context->PSSetShaderResources( 0, 1, &srv );
});

Coordinate systems

Meshes are authored in a specific winding order, typically using the standard counter-clockwise winding common in graphics. The choice of viewing handedness (right-handed vs. left-handed coordinates) is largely a matter of preference and convenience, but it impacts how the models are built and/or exported.

The Visual Studio 3D Starter Kit’s .CMO files assume the developer is using right-handed coordinates. DirectXTK’s default parameters assume you are using right-handed coordinates as well, so the ‘ccw’ parameter defaults to true. If using a .CMO with left-handed coordinates, you should pass false for the ‘ccw’ parameter which will use clockwise winding instead. This makes the geometry visible, but could make textures on the model appear ‘flipped’ in U.

// When using LH coordinates
auto teapot = Model::CreateFromCMO( device, L"teapot.cmo", fx, false );
The legacy DirectX SDK’s .SDKMESH files assume the developer is using left-handed coordinates. DirectXTK’s default parameters assume you are using right-handed coordinates, so the ‘ccw’ parameter defaults to false which will use clockwise winding and potentially have the ‘flipped in U’ texture problem. If using a .SDKMESH with left-handed coordinates, you should pass true for the ‘ccw’ parameter.

// When using LH coordinates
auto tiny = Model::CreateFromSDKMESH( device, L"tiny.sdkmesh", fx, true );

Feature Level Notes

If any ModelMeshPart makes use of 32-bit indices (i.e. ModelMeshPart:: indexFormat equals DXGI_FORMAT_R32_UINT) rather than 16-bit indices (DXGI_FORMAT_R16_UINT), then that model requires Feature Level 9.2 or greater.

If any ModelMeshPart uses adjacency (i.e. ModelMeshPart::primitiveType equals D3D_PRIMITIVE_TOPOLOGY_*_ADJ), then that model requires Feature Level 10.0 or greater. If using tessellation (i.e. D3D_PRIMITIVE_TOPOLOGY_?_CONTROL_POINT_PATCHLIST), then that model requires Feature Level 11.0 or greater.

Keep in mind that there are maximum primitive count limits per ModelMeshPart based on feature level as well (65535 for Feature Level 9.1, 1048575 or Feature Level 9.2 and 9.3, and 4294967295 for Feature Level 10.0 or greater).

See EffectFactory for more Feature Level notes.

Content Notes

The SDKMESH exporter http://go.microsoft.com/fwlink/?LinkId=226208 uses Autodesk FBX 2011.3.1.

The VS 2012 and 2013 CMO exporter uses Autodesk FBX 2013.1. Recommended settings for exporting an FBX as a CMO include:
  • Geometry: Smoothing Groups, TurboSmooth, Convert Deforming Dummies to Bones, Preserve edge orientation
  • Animation: Bake Animation (Start=0, End=100, Step=1), Deformations, Skins, Morphs
  • Units: Automatic
  • Axis conversion: Y-up
  • FBX File Format: Binary, FBX 2013

Threading model

The ModelMeshPart is tied to a device, but not a device context. This means that Model creation/loading is ‘free threaded’. Drawing can be done on the immediate context or by a deferred context, but keep in mind device contexts are not ‘free threaded’. See EffectFactory for some additional notes.

Further reading

http://msdn.microsoft.com/en-us/library/windows/desktop/ff476891.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ff476876.aspx
http://blogs.msdn.com/b/chuckw/archive/2012/06/20/direct3d-feature-levels.aspx

Last edited Jul 16 at 11:56 PM by walbourn, version 29