-
Notifications
You must be signed in to change notification settings - Fork 410
Model
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
#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" );
-
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>
.
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 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());
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.
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 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);
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);
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.
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());
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());
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());
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.
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);
See also Depth sorting alpha blended objects.
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);
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);
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.
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. -
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 thanIEffectSkinning::MaxBones
(72) can support, and these models render incorrectly. The use of this flag can at least render those as rigid models correctly.
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.
CMO
,SDKMESH
, andVBO
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.
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 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.
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 opencode@microsoft.com 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