-
Notifications
You must be signed in to change notification settings - Fork 511
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, 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.
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
#include <Model.h>
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).
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.
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);
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);
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.
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.
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.
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 );
});
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 );
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);
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.
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 theD3D11_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.
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.
See Geometry formats for more information.
The .SDKMESH
Samples Content Exporter uses Autodesk FBX 2013.3.1 or later.
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
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
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 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.
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.
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
- Windows 8.1
- Windows 7 Service Pack 1
- Xbox One
- x86
- x64
- ARM64
- Visual Studio 2022
- Visual Studio 2019 (16.11)
- clang/LLVM v12 - v18
- MinGW 12.2, 13.2
- CMake 3.20