Skip to content
Chuck Walbourn edited this page Apr 13, 2020 · 86 revisions

This is a class hierarchy for drawing simple meshes with support for loading models from 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 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, 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).

See also EffectFactory, EffectTextureFactory

Related tutorial: Rendering a model

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

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:

std::vector<std::shared_ptr<DirectX::IEffect>> 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());

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(_countof(heaps), heaps);

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

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

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

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( auto mit = models.cbegin(); mit != models.cend(); ++mit )
{
    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( auto mit = models.cbegin(); mit != models.cend(); ++mit )
{
    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);

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

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.

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.

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.

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 the Visual Studio mesh content pipeline CMO models or 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