Skip to content
Chuck Walbourn edited this page Apr 21, 2023 · 86 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 description, 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.

See also EffectFactory, EffectTextureFactory

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 .SDKMESH or .VBO files, or from custom file formats. The loaded Model instance includes all the rendering geometry, materials information, and texture filenames.

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

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

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 ResourceLoading sample, but can be generated by DirectXMesh's meshconvert utility.

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

Type aliases

  • Model::EffectCollection is an alias for std::vector<std::shared_ptr<IEffect>>.

  • Model::ModelMaterialInfo is an alias for IEffectFactory::EffectInfo.

  • Model::ModelMaterialInfoCollection is an alias for std::vector<ModelMaterialInfo>.

  • Model::TextureCollection is an alias for std::vector<std::wstring>.

Materials

In order to draw a loaded model, you must provide the effects and textures required to render. First, loading textures described in the model requires the use of ResourceUploadBatch and makes use of either a default or explicit EffectTextureFactory:

ResourceUploadBatch resourceUpload(device);

resourceUpload.Begin();

modelResources = model->LoadTextures(device, resourceUpload);

auto uploadResourcesFinished = resourceUpload.End(m_deviceResources->GetCommandQueue());

uploadResourcesFinished.wait();

Creation of the effects requires providing all state description and either a default or explicit EffectFactory:

DirectX::Model::EffectCollection modelEffects;

std::unique_ptr<DirectX::CommonStates> states = std::make_unique<CommonStates>(device);

RenderTargetState rtState(m_deviceResources->GetBackBufferFormat(),
    m_deviceResources->GetDepthBufferFormat());

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullCounterClockwise,
    rtState);

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthRead,
    CommonStates::CullCounterClockwise,
    rtState);

modelEffects = model->CreateEffects(psd, alphapsd,
    modelResources->Heap(), states->Heap());

The input layout provided in the EffectPipelineStateDescription is ignored since the vertex layout is part of the ModelMeshPart structure, so you can safely pass nullptr. All textures must have valid texture descriptors (created by LoadTextures above) and sampler descriptors (which in this case are provided by CommonStates).

You are free to create more than one set of effects for the same model to support additional rendering state combinations.

If your model will be rendered fully opaque (i.e. no alpha-blending), you can also pass the same pipelineState description for both:

modelEffects = model->CreateEffects(psd, psd,
    modelResources->Heap(), states->Heap());

Because of this multi-phase loading, the LoadTextures method will load all texture files referenced in the material information of the model, even if they are not ultimately used by the effects you create. For example, if the model contains normal map textures, they are always listed in the model and therefore loaded at runtime even if you disable the use of normal map effect for the EffectFactory instance you are using.

VBO effects

Note that VBO files do not contain any material information, just a single index buffer and vertex buffer. Therefore, you cannot call CreateEffects on such models. Instead you create your own using the VertexPositionNormalTexture input layout (you cannot pass nullptr here):

EffectPipelineStateDescription pd(
    &VertexPositionNormalTexture::InputLayout,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    ncull,
    rtState);

shipEffect = std::make_unique<BasicEffect>(device, EffectFlags::Lighting, pd);
shipEffect->EnableDefaultLighting();

Vertex and index buffers

Model loaders place the vertex and index buffer data needed to render into an upload heap managed by GraphicsMemory which can be used to render directly. This is equivalent to having the VBs/IBs in D3D11_USAGE_DYNAMIC memory. For better rendering performance, you can also use this upload heap memory to initialize VB/IB resources that are equivalent to D3D11_USAGE_DEFAULT memory using ResourceUploadBatch via the LoadStaticBuffers method:

resourceUpload.Begin();

model->LoadStaticBuffers(device, resourceUpload);

// Can be combined into a single batch with other uploads of textures
// or resources from other models

auto uploadResourcesFinished = resourceUpload.End(m_deviceResources->GetCommandQueue());

uploadResourcesFinished.wait();

By default LoadStaticBuffers will remove references to the GraphicsMemory copy of vertex buffer and index buffer data as it won't be needed for rendering after the upload. You can keep the references for other purposes (such loading the same data into other kinds of resources or CPU access for processing) by passing true for the keepMemory parameter:

model->LoadStaticBuffers(device, resourceUpload, true);

Resource states

When using Direct command queues with the ResourceUploadBatch, the static VB/IB resources are in the proper state for drawing after calling LoadStaticBuffers.

For Copy and Compute command queues, the static VB/IB resources will be left in other states supported by those command list types. To render they need to be in the proper state. With Windows PC, common state promotion will typically fix this up. For Xbox One where this feature is optional or for other usage scenarios, you need to insert resource barriers for transitioning the resource state of the static VB/IB resources after they have been uploaded.

The Transition method on Model is designed to simplify batch inserting resource barriers for static VB/IB resources.

// If using a copy queue for the upload, both resources are in the
// D3D12_RESOURCE_STATE_COPY_DEST state
model->Transition(commandList,
    D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
    D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
// If using a compute queue for the upload, the VB is in the correct state, but
// the IB is set to D3D12_RESOURCE_STATE_COPY_DEST
model->Transition(commandList,
    D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER,
    D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);

Simple drawing

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

ID3D12DescriptorHeap* heaps[] = { modelResources->Heap(), states->Heap() };
commandList->SetDescriptorHeaps(static_cast<UINT>(std::size(heaps)), heaps);

Model::UpdateEffectMatrices(modelEffects, world, view, projection);

model->Draw(commandList, modelEffects.cbegin());

To provide flexibility, setting the proper descriptor heaps to render with via SetDescriptorHeaps is left to the caller. You can create as many heaps as you wish in your application, but remember that you can have only a single texture descriptor heap and a single sampler descriptor heap active at a given time.

VBO drawing

To draw a VBO where you are required to create your own effect (see above), you can pass a pointer to the single effect to draw:

shipEffect->SetMatrices(world, view, projection);
ship->Draw(commandList, shipEffect.get());

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", ModelLoader_IncludeBones);

//... Load textures, create effects, load static buffers, etc. ...

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

//... Set descriptor heap ...
Model::UpdateEffectMatrices(tankEffects, world, view, projection);
tank->Draw(commandList, nbones, bones.get(), world, tankEffects.cbegin());

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

//... Set descriptor heap ...
Model::UpdateEffectMatrices(tankEffects, world, view, projection);
tank->Draw(commandList, nbones, bones.get(), world, tankEffects.cbegin());

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", ModelLoader_IncludeBones);

//... Load textures, create effects, load static buffers, etc. ...

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

// Apply local animation for given time to animBones transformation 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]);
}

//... Set descriptor heap ...
Model::UpdateEffectMatrices(soldierEffects, world, view, projection);
soldier->DrawSkinned(commandList, nbones, bones.get(), world, soldierEffects.cbegin());

Advanced drawing

The standard Draw method invokes the DrawOpaque and DrawAlpha template function, which in turn calls the DrawOpaque and DrawAlpha methods of ModelMesh for each instance in the meshes collection. One use would be to draw all the opaque parts of models, then draw all the alpha parts to get proper blending between models.

// Rather than draw each model's opaque and then alpha parts in turn, this version
// draws all the models' opaque parts first then all the alpha parts second which
// can be important for some complex scenes.

std::list<std::unique_ptr<Model>> models;

// Draw opaque parts
for(const auto& mit : models)
{
    auto model = mit.get();
    assert( model != 0 );

    model->DrawOpaque(commandList, /*effect array for this model*/);
}

// Draw alpha parts (should really be done in back-to-front sorted order)
for(const auto& mit : models)
{
    auto model = mit.get();
    assert( model != 0 );

    model->DrawAlpha(commandList, /*effect array for this model*/);
}

For more details see ModelMesh.

Effects control

UpdateEffectMatrices is a helper method to simplify the use of Model, but you can modify settings directly on each Effect instead:

for (auto& it : modelEffects)
{
    auto skin = dynamic_cast<IEffectSkinning*>(it.get());
    if (skin)
    {
        skin->SetBoneTransforms(m_bones.get(), SkinnedEffect::MaxBones);
    }
}

Wireframe rendering

To draw the mesh as a wireframe, create your effects with this state description:

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::Wireframe,
    rtState);

modelEffects = model->CreateEffects(psd, psd,
    modelResources->Heap(), states->Heap());

Alpha blending

For alpha blending meshes parts, you typically use CommonStates::DepthRead rather than the normal CommonStates::DepthDefault. To indicate the use of ‘premultiplied’ alpha blending modes, use CommonStates::AlphaBlend.

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthRead,
    CommonStates::CullCounterClockwise,
    rtState);

If using 'straight' alpha for textures, use CommonStates::NonPremultiplied.

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::NonPremultiplied,
    CommonStates::DepthRead,
    CommonStates::CullCounterClockwise,
    rtState);

See also Depth sorting alpha blended objects.

Coordinate systems

Meshes are authored in a specific winding order, typically using the standard counter-clockwise winding common in graphics (i.e. CommonStates::CullCounterClockwise). 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.

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullCounterClockwise,
    rtState);

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthRead,
    CommonStates::CullCounterClockwise,
    rtState);

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 you should use CommonStates::CullClockwise. This will use clockwise winding and potentially have the ‘flipped in U’ texture problem.

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullClockwise,
    rtState);

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthRead,
    CommonStates::CullClockwise,
    rtState);

If using a left-handed .SDKMESH with left-handed viewing coordinates, you should use the normal CommonStates::CullCounterClockwise.

You can also choose to render with back-face culling disabled, but this generally results in less performance.

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthDefault,
    CommonStates::CullNone,
    rtState);

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthRead,
    CommonStates::CullNone,
    rtState);

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 use the appropriate depth/stencil state:

EffectPipelineStateDescription psd(
    nullptr,
    CommonStates::Opaque,
    CommonStates::DepthReverseZ,
    CommonStates::CullCounterClockwise,
    rtState);

EffectPipelineStateDescription alphapsd(
    nullptr,
    CommonStates::AlphaBlend,
    CommonStates::DepthReadReverseZ,
    CommonStates::CullCounterClockwise,
    rtState);

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

The ModelMeshPart class provides a material index for each submesh which describes the effect to render with. This indexes the information in Model for materials which consists of EffectInfo data. The textureNames is a list of the texture filenames indexed by the EffectInfo.

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

  • 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 D3D12_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.

Content Notes

See Geometry formats for more information.

SDKMESH

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

VBO

A .VBO file does not contain any material or attribute information.

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 command-list. This means that Model creation/loading is ‘free threaded’. Drawing can be done on any command-list, but keep in mind command lists are not ‘free threaded’. See EffectTextureFactory for threading restrictions on texture upload.

Work Submission in Direct3D 12

State management

When Draw is called, it will set the states needed to render with the Model's effect. This includes the root signature, the Pipeline State Object (PSO), and Primitive Topology.

The Model class assumes you've already set the Render Target view, Depth Stencil view, Viewport, ScissorRects, and Descriptor Heaps (for textures and samplers) to the command-list provided to Draw.

Remark

DirectX Tool Kit for DirectX 12 does not implement support for Visual Studio Directed Graph Shader Language (DGSL) effects._

The DirectX 12 version of Model explicit separates the creation of the geometry rendering resources from the material setup, and the material setup takes places in two distinct steps. In the DirectX 11 version, this is all done as part of one loading operation. The requirements for the vertex layout were also less strict than in DirectX 12 due to the Pipeline State Object (PSO) design.

Further reading

Models, meshes, parts, and bones

For Use

  • Universal Windows Platform apps
  • Windows desktop apps
  • Windows 11
  • Windows 10
  • Xbox One
  • Xbox Series X|S

Architecture

  • x86
  • x64
  • ARM64

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 11

DirectXMesh

DirectXTex

DirectXMath

Tools

Test Suite

Model Viewer

Content Exporter

DxCapsViewer

See also

DirectX Landing Page

Clone this wiki locally