Model Loading & Rendering
Implementation status: Model loading and Draw are implemented. Skeletal animation via SkinnedEffect and bone matrices is supported (72-bone UBO). Complex animation systems (animation clips, blending) are not built-in and must be implemented in game code. Implemented
Overview
The Model class lives in the Microsoft::Xna::Framework::Graphics namespace and represents a complete 3D mesh asset ready for rendering. A Model holds a hierarchy of ModelMesh objects; each mesh in turn contains one or more ModelMeshPart entries that map directly to a single draw call. Every part carries its own VertexBuffer, IndexBuffer, and Effect, mirroring the XNA 4.0 design exactly.
Because CNA targets open runtimes that have no XNB pipeline, models are described by .model.json descriptor files rather than compiled XNB binary assets. The descriptor references texture and shader paths that the ContentManager resolves relative to its RootDirectory. At load time the runtime inflates the descriptor into the full Model object graph.
.model.json Descriptor Format
A .model.json file describes all meshes that make up a model. Each mesh entry names itself, specifies a material (currently a diffuse texture path), and provides inline vertex and index data.
{
"meshes": [
{
"name": "Cube",
"material": { "texture": "assets/cube.png" },
"vertices": [...],
"indices": [...]
}
]
}
Asset paths inside the descriptor (e.g. "assets/cube.png") are relative to the ContentManager's RootDirectory. A single descriptor may contain any number of mesh entries; each entry becomes one ModelMesh in the resulting Model object. Additional material fields (shader path, normal map, etc.) can be added as the pipeline evolves without breaking the basic format.
Model API
| Member | Type | Description |
|---|---|---|
Draw(world, view, projection) |
void |
One-call rendering. Binds matrices via IEffectMatrices on every part's effect, then draws all meshes. |
Meshes |
ModelMeshCollection |
Ordered collection of all ModelMesh objects in this model. |
Bones |
ModelBoneCollection |
Flat list of all bones. Each ModelBone exposes a local Transform matrix and an Index. |
Root |
ModelBone* |
The root bone of the skeleton hierarchy. |
CopyAbsoluteBoneTransformsTo(Matrix[]) |
void |
Walks the bone hierarchy and fills the caller-supplied array with world-space (absolute) bone matrices. The array must have at least Bones.size() entries. |
Tag |
std::any |
Arbitrary user data attached to the model. Mirrors the XNA 4.0 Tag property. |
ModelMesh
| Member | Type | Description |
|---|---|---|
Name |
std::string |
The mesh name as declared in the .model.json descriptor. |
MeshParts |
ModelMeshPartCollection |
Collection of ModelMeshPart entries that together define this mesh's geometry. |
Effects |
EffectCollection |
All effects referenced by parts in this mesh. Useful for bulk parameter updates. |
Draw() |
void |
Draws all MeshParts using their currently assigned effects. |
ParentBone |
ModelBone* |
The bone that controls this mesh's transform in the skeleton hierarchy. |
BoundingSphere |
BoundingSphere |
Bounding sphere in local mesh space. Used for frustum culling. |
Tag |
std::any |
Arbitrary user data attached to this mesh. |
ModelMeshPart
| Member | Type | Description |
|---|---|---|
Effect |
std::shared_ptr<Effect> |
The effect used to render this part. Assign a different effect here to override the default material. |
VertexBuffer |
std::shared_ptr<VertexBuffer> |
GPU buffer holding the vertex data for this part. |
IndexBuffer |
std::shared_ptr<IndexBuffer> |
GPU buffer holding the index data for this part. |
PrimitiveCount |
int |
Number of primitives (triangles) to draw. |
StartIndex |
int |
First index in the IndexBuffer for this part. |
VertexOffset |
int |
Offset added to each index when reading from the VertexBuffer. |
NumVertices |
int |
Number of vertices referenced by this part. |
Tag |
std::any |
Arbitrary user data attached to this part. |
Code Examples
1. Simple one-call draw
The simplest way to render a model. Model::Draw iterates every part, sets the world/view/projection matrices on each effect via IEffectMatrices, and issues the draw calls automatically.
model->Draw(world, view, projection);
2. Loading via ContentManager
Pass the asset name without extension. The ContentManager appends .model.json, resolves all referenced textures relative to RootDirectory, and returns a fully initialised Model.
auto model = Content.Load<Model>("models/house");
3. Per-mesh effect override
Override the default effect on each part to apply custom lighting or material properties per mesh.
for (auto& mesh : model->Meshes) {
for (auto& part : mesh.MeshParts) {
auto effect = std::dynamic_pointer_cast<BasicEffect>(part.Effect);
if (effect) {
effect->setWorld(world);
effect->setView(view);
effect->setProjection(projection);
effect->setLightingEnabled(true);
}
}
mesh.Draw();
}
4. Skeletal animation with bone transforms
Call CopyAbsoluteBoneTransformsTo to obtain world-space matrices for every bone. Multiply each mesh's parent-bone transform by the scene world matrix before setting it on the effect. This is the standard pattern for rendering skinned models exported with a bone hierarchy.
std::vector<Matrix> transforms(model->Bones.size());
model->CopyAbsoluteBoneTransformsTo(transforms.data());
for (auto& mesh : model->Meshes) {
Matrix meshWorld = transforms[mesh.ParentBone->Index] * world;
for (auto& part : mesh.MeshParts) {
auto effect = std::dynamic_pointer_cast<BasicEffect>(part.Effect);
if (effect) {
effect->setWorld(meshWorld);
effect->setView(view);
effect->setProjection(projection);
}
}
mesh.Draw();
}
Animation clips and blending are not built into CNA. The bone hierarchy and SkinnedEffect (72-bone UBO) provide the low-level foundation; animation playback, clip sampling, and blend trees must be implemented in game code by writing bone transforms into ModelBone::Transform each frame before calling CopyAbsoluteBoneTransformsTo.
Skeletal Animation Notes
SkinnedEffect accepts up to 72 bones uploaded via a Uniform Buffer Object. Set the bone palette with effect->SetBoneTransforms(span) and configure the number of skinning influences per vertex via effect->SetWeightsPerVertex(n), where n is 1, 2, or 4.
auto skinned = std::dynamic_pointer_cast<SkinnedEffect>(part.Effect);
if (skinned) {
skinned->SetWorld(meshWorld);
skinned->SetView(view);
skinned->SetProjection(projection);
skinned->EnableDefaultLighting();
skinned->SetWeightsPerVertex(4);
skinned->SetBoneTransforms(boneMatrices); // std::span<Matrix>, max 72
}