Model Loading & Rendering

Microsoft::Xna::Framework::Graphics — Model, ModelMesh, ModelMeshPart, and .model.json descriptors

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
}