-
Notifications
You must be signed in to change notification settings - Fork 459
Model
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
#include <Model.h>
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" );
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 passnullptr
. All textures must have valid texture descriptors (created byLoadTextures
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());
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();
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:
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);
When using Direct command queues with the ResourceUploadBatch, the static VB/IB resources are in the proper state for drawing.
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.
// If using a copy queue for the upload, both resources are in the
// D3D12_RESOURCE_STATE_COPY_DEST state
for (auto& mit : model->meshes)
{
for (auto& pit : mit->opaqueMeshParts)
{
TransitionResource(commandList, pit->staticIndexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
TransitionResource(commandList, pit->staticVertexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER);
}
for (auto& pit : mit->alphaMeshParts)
{
TransitionResource(commandList, pit->staticIndexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
TransitionResource(commandList, pit->staticVertexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_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
for (auto& mit : model->meshes)
{
for (auto& pit : mit->opaqueMeshParts)
{
TransitionResource(commandList, pit->staticIndexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
}
for (auto& pit : mit->alphaMeshParts)
{
TransitionResource(commandList, pit->staticIndexBuffer.Get(),
D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER);
}
}
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.
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());
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.
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);
}
}
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());
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);
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);
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
.
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 theD3D12_REQ_RESOURCE_SIZE_IN_MEGABYTES_EXPRESSION_x_TERM
constants.
See Geometry formats for more information.
The .SDKMESH
Samples Content Exporter uses Autodesk FBX 2013.3.1 or later.
A .VBO
file does not contain any material or attribute information.
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
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
.
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.
All content and source code for this package are subject to the terms of the MIT License.
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.
- Universal Windows Platform apps
- Windows desktop apps
- Windows 11
- Windows 10
- Xbox One
- Xbox Series X|S
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20