Skip to content
Chuck Walbourn edited this page Apr 21, 2023 · 63 revisions
DirectXTK

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

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

A Model can optionally have an array of ModelBone data. This data can be used for rigid-body animation of meshes, skinned animations, and/or for runtime metadata.

Screenshot

See also EffectFactory

Related tutorials: Rendering a model, Animating using model bones, Using skinned models

classDiagram
direction LR
class Model{
    +name
    +Copy*BoneTransforms*()
    +Draw()
    +DrawSkinned()
    +UpdateEffects()
}
class ModelBone
class ModelMesh{
    +boundingSphere
    +boundingBox
    +boneIndex
    +name
    +PrepareForRendering()
    +Draw()
    +DrawSkinned()
}
class ModelMeshPart{
    +primitiveType
    +indexFormat
    +vertexStride
    +indexBuffer
    +vertexBuffer
    +effect
    +Draw()
    +DrawInstanced()
    +CreateInputLayout()
    +ModifyEffect()
}
Model --o ModelBone : bones
Model --o ModelMesh : meshes
ModelMesh --o ModelMeshPart : meshParts
Loading

Header

#include <Model.h>

Initialization

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

Visual Studio 2012 or later 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 (Windows 8.1, Windows 8.0).

m_fx = std::make_unique<DGSLEffectFactory>(device);
// Can also use EffectFactory, but will ignore pixel shader material settings

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

For exception safety, the loaders return a std::unique_ptr.

The Samples Content Exporter will generate .SDKMESH files from an Autodesk .FBX.

m_fx = std::make_unique<EffectFactory>(device);
// Can also use DGSLEffectFactory, but will always use default materials

auto tiny = Model::CreateFromSDKMESH( device, L"tiny.sdkmesh", *m_fx );

The .VBO file format is a very simple geometry format containing a index buffer and a vertex buffer. It was originally introduced in the Windows 8.0 ResourceLoading sample, but can be generated by DirectXMesh's meshconvert utility.

auto ship = Model::CreateFromVBO( device, L"ship.vbo" );

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.

m_states = std::make_unique<CommonStates>(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.

Rigid-body animation

There is an overload of Draw which takes an array of transformation matrices. The boneIndex in each ModelMesh is used to index into this array to combine with the world matrix for positioning. This is typically used for rigid-body animation using ModelBone data.

auto tank = Model::CreateFromSDKMESH(device, L"tank.sdkmesh", *m_fx, ModelLoader_IncludeBones);

// Find bone index for the turret mesh and set a local rotation into
// matching the boneMatrices array.
uint32_t index = 0;
for(auto it : tank->bones)
{
    if (_wcsicmp(L"turret", it.name.c_str()) == 0)
    {
        tank->boneMatrices[index] = ...;
        break;
    }

    ++index;
}

size_t nbones = tank->bones.size();
auto bones = ModelBone::MakeArray(nbones);
tank->CopyAbsoluteBoneTransformsTo(nbones, bones.get());

tank->Draw(context, states, nbones, bones.get(), world, view, projection);

You can directly modify the boneMatrices in the Model instance, or you can create a distinct transformation array to work with:

size_t nbones = tank->bones.size();

auto animBones = ModelBone::MakeArray(nbones);
tank->CopyBoneTransformsTo(nbones, animBones.get();

// Modify the appropriate local transforms in animBones

auto bones = ModelBone::MakeArray(nbones);
tank->CopyAbsoluteBoneTransforms(nbones, animBones.get(), bones.get());

tank->Draw(context, states, nbones, bones.get(), world, view, projection);

Skinned animation drawing

The DrawSkinned method is used to draw with skinned effects--i.e. with effects that support the IEffectSkinning interface. This is typically used for skinned animation using ModelBone data.

auto soldier = Model::CreateFromSDKMESH(device, L"soldier.sdkmesh", *m_fx, ModelLoader_IncludeBones);

size_t nbones = soldier->bones.size();
auto animBones = ModelBone::MakeArray(nbones);
soldier->CopyBoneTransformsTo(nbones, animBones.get());

// Apply local animation for given time to animBones transforms array

auto bones = ModelBone::MakeArray(nbones);
soldier->CopyAbsoluteBoneTransforms(nbones, animBones.get(), bones.get());

for (size_t j = 0; j < nbones; ++j)
{
    bones[j] = XMMatrixMultiply(soldier->invBindPoseMatrices[j], bones[j]);
}

soldier->DrawSkinned(context, states, nbones, bones.get(), world, view, projection);

Advanced drawing

Rather than using the standard 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.

ModelMesh::PrepareForRendering sets the blend state, depth stencil state, raster state, and sets a pixel shader sampler.

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.

See also Depth sorting alpha blended objects.

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 model loader flags default to ModelLoader_CounterClockwise. If using a .CMO with left-handed coordinates, you should pass ModelLoader_Clockwise instead which will use clockwise winding. 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, ModelLoader_Clockwise );

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 model loader flags default to ModelLoader_Clockwise 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 ModelLoader_CounterClockwise instead.

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

Depth buffer

The rendering setup assumes you are using a standard z-buffer. If have set up your pipeline for reverse zbuffer rendering, be sure to set this class property on ModelMesh:

ModelMesh::SetDepthBufferMode(true);

Metadata

A Model instance 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).

If the Model contains ModelBone data, then it will have a non-empty collection called bones. The boneMatrices array will be the same length and contain the default local transformation matrix for that bone. The invBindPoseMatrices array will contain the inverse bind-pose transformation used for animation. Note that the ModelMesh class also contains optional boneIndex and boneInfluences related to this information.

Model Loader Flags

The various CreateFrom* methods have a defaulted parameter to provide additional model loader control.

  • ModelLoader_Clockwise: Should use a clockwise winding for backface-culling.

  • ModelLoader_CounterClockwise: Should use counter-clockwise winding for backface-culling.

  • ModelLoader_PremultipledAlpha: Should use premultipled alpha blending instead of 'straight' Alpha

  • ModelLoader_MaterialColorsSRGB: Material colors specified in the model file should be converted from sRGB to Linear colorspace.

  • ModelLoader_AllowLargeModels: Allows models with VBs/IBs that exceed the required resource size support for all Direct3D devices as indicated by the D3D11_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_x_TERM constants.

  • ModelLoader_IncludeBones: Indicates that any frames (for SDKMESHes) or bones (for CMOs) should be loaded as ModelBone data. This includes bones, boneMatrices, and invBindPoseMatrices.

  • ModelLoader_DisableSkinning: Normally the presence of bone indices in the model file indicate that skinning effects should be used. If this flag is set, non-skinning effects are always used. Some legacy SDKMESH models contain more bone influences than IEffectSkinning::MaxBones (72) can support, and these models render incorrectly. The use of this flag can at least render those as rigid models correctly.

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.

Direct3D feature levels

Content Notes

See Geometry formats for more information.

SDKMESH

The .SDKMESH Samples Content Exporter uses Autodesk FBX 2013.3.1 or later.

CMO

The VS 2012 and 2013 .CMO exporter uses Autodesk FBX 2013.1. VS 2015 uses Autodesk FBX 2015.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

VBO

A .VBO file does not contain any material or attribute information. The loader will create a default untextured BasicEffect when loading the model, or you can provide your own instance to use:

ComPtr<ID3D11ShaderResourceView> defaultTex;
ComPtr<ID3D11ShaderResourceView> cubeMap;
// ...
auto effect = std::make_shared<EnvironmentMapEffect>(device);
effect->EnableDefaultLighting();
effect->SetTexture(defaultTex.Get());
effect->SetEnvironmentMap(cubeMap.Get());

auto ship = Model::CreateFromVBO( device, L"ship.vbo", effect );

CMO, SDKMESH and VBO are 'uncompressed' formats meaning that all the vertex buffer and index buffer data is the same size on disk as it is in memory. For simple applications, samples, and demos this is perfectly acceptable. For very large models, however, the disk-space usage becomes a concern. For more, see Compressing assets.

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.

State management

When Draw is called, it will set the states needed to render with the Model's effects. Existing state is not save or restored. For efficiency, it simply sets the state it requires to render and assumes that any subsequent rendering will overwrite state that it needs.

Model makes use of the following states:

  • BlendState
  • Constant buffer (Vertex Shader and Pixel Shader stages, slot 0)
  • DepthStencilState
  • Index buffer
  • Input layout
  • Pixel shader
  • Primitive topology
  • RasterizerState
  • SamplerState (Pixel Shader stage, slots 0 and 1)
  • Shader resources (Pixel Shader stage, slots 0 and 1)
  • Vertex buffer (slot 0)
  • Vertex shader

If you used DGSLEffectFactory for the model then Draw will make use of additional state as outlined in Effects

The Model class assumes you've already set the Render Target view, Depth Stencil view, and Viewport.

Be sure that if you set any of the following shaders prior to using built-in effects for your Model instances that you clear them: Geometry Shader, Hull Shader, Domain Shader, Compute Shader.

Further reading

Models, meshes, parts, and bones

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Windows 8.1
  • Windows 7 Service Pack 1
  • Xbox One

For Development

  • Visual Studio 2022
  • Visual Studio 2019 (16.11)
  • clang/LLVM v12 - v18
  • MinGW 12.2, 13.2
  • CMake 3.20

Related Projects

DirectX Tool Kit for DirectX 12

DirectXMesh

DirectXTex

DirectXMath

Win2D

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

Clone this wiki locally