3D Rendering
Implementation status: CNA exposes 50 renderer identities across 46 implementation families. A normal build compiles one identity selected with CNA_GRAPHICS_RENDERER; alpha.1 can instead compile a compatible list with CNA_GRAPHICS_RENDERERS and select one before the first graphics device is created. 3D depth varies by renderer, so use the capability table and the complete renderer reference rather than assuming every selectable identity implements the full pipeline.
Surface formats are renderer-qualified. Most families defer to the framework's SurfaceFormat::Color-only rule. Skia promotes a broad set and IGL promotes verified Rg32/Single textures and render targets; Texture3D and TextureCube remain Color-only. See Surface format boundaries.
Overview
CNA exposes XNA 4.0’s full 3D pipeline through the Microsoft::Xna::Framework::Graphics namespace. The pipeline follows the same pattern as the original API: you upload geometry into VertexBuffer and IndexBuffer objects, configure a GraphicsDevice render state, apply an Effect, and issue a draw call.
The five EasyGL identities share an implementation but select distinct OpenGL ES, desktop OpenGL or WebGL profiles. Vulkan is a separate native renderer and is not an Emscripten identity. Shader dialect and 3D capability follow the active renderer, so use GraphicsDevice capability queries and the renderer reference rather than translating one GL example mechanically to every family.
Thirteen identities are deliberately 2D-only, while HEADLESS and STUB produce no presented pixels. Their exact exception and capability behavior is not interchangeable. The renderer reference is the canonical inventory; this page concentrates on the game-facing 3D API.
Headless and Software sit outside the usual comparison. Headless deliberately renders nothing (its ReadBackbuffer returns the last clear colour) and exists for fast CI of game logic. Software is a genuine CPU rasterizer — real edge functions, a real Z-buffer and perspective-correct 1/w interpolation — but it is TriangleList-only and intended for deterministic pixel tests rather than real-time gameplay. It does, however, need no GPU at all.
| Renderer | 3D support | Notes |
|---|---|---|
| EasyGL | Production-ready | The most mature renderer and the 3D reference. 13 GLSL programs, all 9 effect families, per-vertex and per-pixel lighting, instancing, occlusion queries, anisotropy, fog, and GL context-loss recovery. Also the Emscripten default. |
| Vulkan | Functional (near production) | 35 SPIR-V entry points, 8 effect paths, MRT proxy, device-queried depth formats, real stencil PSO, MSAA resolve, mip blit cascades. |
| DIRECTX9 | Functional | Windows-only. 72 verified SM2/SM3 shader blobs, full device-lost and D3DPOOL_DEFAULT recovery, caps-validated MRT. Uniquely targets pixel-exact XNA 4.0 authenticity. |
| DIRECTX11 | Functional | Windows-only. Five state-object caches, MRT with deferred resolve, instancing, runtime D3DCompile. Note: startIndex/baseVertex draw offsets are ignored. |
| SDL_GPU | Functional | Deferred/retained design, 23 SPIR-V blobs, 9 effect families, stencil and MSAA resolve. Vulkan-only device today. |
| Software | Functional (narrow) | Genuine CPU rasterizer with real Z-buffer and perspective-correct interpolation. TriangleList only. Needs no GPU. |
| DIRECTX12 | Partial | Windows-only. Strong core (PSO/root-signature caches, barrier tracker) but no scissor, no viewport, no stencil and no blend factor. MRT clears but never draws. |
| bgfx | Partial | 108 shader blobs, but GL/GLES/Vulkan/WebGPU only — no D3D or Metal variants, so 3D silently draws nothing on bgfx's default Windows/macOS renderer. DrawUserIndexedPrimitives corrupts geometry (see caveats below). |
| WebGPU | Partial / experimental | Backbuffer-only forward renderer: no render targets at all, no MSAA, no cube or 3D textures, no queries, no instancing, non-functional stencil. |
| SDL_Renderer, Direct2D, Canvas, HTML_DOM, Skia, Blend2D, FreeDirect, GDI, SVG_DOM and OpenVG | 2D only | No programmable 3D pipeline. SDL_Renderer throws on 3D calls by design and is the only renderer whose capability reporting is fully truthful. |
| Headless | Renders nothing | A test harness, not a renderer. For fast CI of game logic. |
Cross-renderer caveats
Three verified issues cut across renderers and are worth knowing before you debug something that looks like your own bug.
| Caveat | Detail |
|---|---|
SupportsCapability() is unreliable |
The base explicitly returns false for multi-stream input and compiled effects and delegates stencil, but returns true for its other entries. DIRECTX9, DIRECTX11, DIRECTX12 and SDL_GPU never override it at all. Several claims therefore fail open; do not use a bare true as execution proof. |
| Draw offsets ignored | startIndex and baseVertex are ignored on DIRECTX11, and on bgfx outside the wireframe path. |
| bgfx 32-bit index corruption | DrawUserIndexedPrimitives corrupts geometry on bgfx: the renderer never overrides CreateIndexBuffer32, so 32-bit indices are uploaded into a 16-bit buffer. Every other 3D renderer implements this correctly. |
Surface format boundaries
The tag no longer has one framework-wide answer. Texture2D asks the active renderer for a three-way Supported/Unsupported/Defer verdict. Skia promotes its broad native set, and IGL promotes the two end-to-end verified formats Rg32 and Single. Every other family defers to Texture::ValidateFormat, whose public rule accepts SurfaceFormat::Color only. Texture3D and TextureCube still use that framework rule directly.
The practical consequences:
- Do not infer public support from a renderer's internal mapping. Without a promoted verdict, construction still reaches the framework's
Color-only gate. - Skia is the broad format route. It accepts packed, float, normalized and DXT/BC7 texture formats, with a narrower verified render-target subset.
- IGL's exception is intentionally small. Only
Rg32andSinglehave the end-to-end public promotion in alpha.1.
Vertex types
CNA provides the same four built-in vertex types as XNA 4.0. Every vertex type carries a static VertexDeclaration that describes the layout of its fields to the GPU. You can also define custom vertex structs and supply a matching VertexDeclaration.
| Type | Fields | Typical use |
|---|---|---|
VertexPositionColor |
Position (Vector3), Color (Color) |
Debug geometry, wireframes, simple colored primitives |
VertexPositionTexture |
Position (Vector3), TextureCoordinate (Vector2) |
Textured quads and simple textured meshes without lighting |
VertexPositionColorTexture |
Position (Vector3), Color (Color), TextureCoordinate (Vector2) |
Sprites in 3D space, tinted textured geometry |
VertexPositionNormalTexture |
Position (Vector3), Normal (Vector3), TextureCoordinate (Vector2) |
Lit 3D meshes with BasicEffect, EnvironmentMapEffect, SkinnedEffect |
Each vertex type exposes a static VertexDeclaration member that enumerates the VertexElement entries — their semantic (VertexElementUsage), format (VertexElementFormat), and byte offset within the struct. The GraphicsDevice reads this declaration when a vertex buffer is bound to know how to interpret the raw bytes.
// Access the declaration for a built-in type
const VertexDeclaration& decl = VertexPositionNormalTexture::VertexDeclaration;
// Inspect the stride (bytes per vertex)
int stride = decl.VertexStride; // 32 bytes for VertexPositionNormalTexture
Buffer management
VertexBuffer
A VertexBuffer stores a fixed array of vertices in GPU memory. Pass the VertexDeclaration describing the vertex layout, the number of vertices, and a BufferUsage hint when constructing.
// Allocate a static vertex buffer for 3 vertices
auto vb = std::make_shared<VertexBuffer>(
graphicsDevice,
VertexPositionColor::VertexDeclaration,
3,
BufferUsage::None);
// Upload vertex data
std::vector<VertexPositionColor> vertices = {
{ Vector3(0.0f, 0.5f, 0.0f), Color::Red },
{ Vector3(0.5f, -0.5f, 0.0f), Color::Green },
{ Vector3(-0.5f,-0.5f, 0.0f), Color::Blue },
};
vb->SetData(vertices.data(), static_cast<int>(vertices.size()));
// Read back data if needed
vb->GetData(vertices.data(), static_cast<int>(vertices.size()));
Use BufferUsage::None for static geometry that will not change after upload, and BufferUsage::WriteOnly when you intend to upload data once and never read it back (allows driver optimization).
DynamicVertexBuffer
DynamicVertexBuffer is optimised for geometry that changes every frame, such as particle systems or procedurally animated meshes. It uses a different allocation path that avoids GPU pipeline stalls on repeated SetData calls.
auto dvb = std::make_shared<DynamicVertexBuffer>(
graphicsDevice,
VertexPositionColor::VertexDeclaration,
maxParticles,
BufferUsage::WriteOnly);
// Each frame: discard old data and upload new
dvb->SetData(particleVerts.data(), static_cast<int>(liveCount),
SetDataOptions::Discard);
IndexBuffer
An IndexBuffer stores integer indices that reference vertices in a VertexBuffer, enabling vertex reuse. Choose IndexElementSize::SixteenBits (up to 65 535 unique vertices per draw) or IndexElementSize::ThirtyTwoBits (up to ~4 billion).
auto ib = std::make_shared<IndexBuffer>(
graphicsDevice,
IndexElementSize::SixteenBits,
36, // 12 triangles * 3 indices = 36 for a cube
BufferUsage::None);
std::vector<uint16_t> indices = { 0, 1, 2, 0, 2, 3, /* ... */ };
ib->SetData(indices.data(), static_cast<int>(indices.size()));
Binding and draw calls
Before issuing a draw call you must bind at least one vertex buffer to the GraphicsDevice. Index buffers and the active effect are set in the same way.
graphicsDevice->SetVertexBuffer(vb);
graphicsDevice->Indices = ib; // optional; required for indexed draws
effect->CurrentTechnique->Passes[0]->Apply(); // upload shader constants
Draw call variants
| Method | Description | Renderers |
|---|---|---|
DrawPrimitives(type, startVertex, primitiveCount) |
Non-indexed draw from the bound vertex buffer | All |
DrawIndexedPrimitives(type, baseVertex, startIndex, primitiveCount) |
Indexed draw; combines vertices with index buffer | All |
DrawInstancedPrimitives(type, baseVertex, startIndex, primitiveCount, instanceCount) |
GPU instancing: draw instanceCount copies in a single call | EasyGL, Vulkan |
DrawUserPrimitives<T>(type, vertices[], vertexOffset, primitiveCount) |
Immediate-mode draw; no VertexBuffer object needed |
All |
DrawUserPrimitives uploads vertex data to a transient buffer on every call. It is convenient for small one-off shapes but inefficient for large or frequently drawn geometry. Prefer VertexBuffer for anything drawn more than once per frame.
PrimitiveType
All draw calls accept a PrimitiveType that tells the GPU how to interpret the vertex stream.
| Value | Vertices per primitive | Description |
|---|---|---|
TriangleList |
3 per triangle | Each group of three vertices forms an independent triangle. Most common for solid meshes. |
TriangleStrip |
1 per additional triangle | First triangle needs 3 vertices; each subsequent triangle reuses the last two. Reduces index count for sequential strips. |
LineList |
2 per line | Each pair of vertices forms an independent line segment. Useful for debug overlays. |
LineStrip |
1 per additional segment | Vertices form a continuous polyline. Efficient for curves and paths. |
BasicEffect 3D setup
BasicEffect is the standard effect for solid 3D geometry. It supports per-vertex and per-pixel lighting with up to three independent directional lights, a single diffuse texture, per-vertex color tinting, and distance fog. See the Effects System page for the full API reference; the sections below focus on the typical 3D setup pattern.
The minimal setup for a lit, textured mesh:
- Set the three transformation matrices:
World,View,Projection. - Enable lighting with
EnableDefaultLighting()or configureDirectionalLight0manually. - Enable and assign a texture via
TextureEnabled = trueandTexture = myTex. - Call
CurrentTechnique->Passes[0]->Apply()to upload all constants to the GPU. - Issue the draw call.
auto effect = std::make_shared<BasicEffect>(graphicsDevice);
// Matrices
effect->SetWorld(Matrix::Identity);
effect->SetView(Matrix::CreateLookAt(
Vector3(0, 2, 5), // camera position
Vector3::Zero, // look-at target
Vector3::Up));
effect->SetProjection(Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4, // 45 degrees
graphicsDevice->Viewport.AspectRatio(),
0.1f, 100.0f));
// Lighting
effect->EnableDefaultLighting();
// Texture
effect->SetTextureEnabled(true);
effect->SetTexture(wallTexture);
// Apply and draw
effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawIndexedPrimitives(
PrimitiveType::TriangleList, 0, 0, indexCount / 3);
Camera setup
CNA reproduces the XNA 4.0 Matrix factory methods for building camera and projection matrices. The view matrix transforms world-space coordinates into camera space; the projection matrix further transforms camera-space coordinates into clip space.
// View matrix: camera at (3, 4, 5) looking at the origin, Y is up
Matrix view = Matrix::CreateLookAt(
Vector3(3.0f, 4.0f, 5.0f), // eye position
Vector3::Zero, // target
Vector3::Up); // up direction
// Perspective projection: 60-degree vertical FOV, standard near/far
float aspectRatio = graphicsDevice->Viewport.AspectRatio();
Matrix projection = Matrix::CreatePerspectiveFieldOfView(
MathHelper::ToRadians(60.0f),
aspectRatio,
0.1f, // near plane
500.0f); // far plane
// Orthographic projection (for UI or isometric views)
Matrix ortho = Matrix::CreateOrthographic(
graphicsDevice->Viewport.Width,
graphicsDevice->Viewport.Height,
-1.0f, 1.0f);
CNA uses a right-handed coordinate system with Y-up, matching XNA 4.0. The near and far planes are in view-space distance units; keep the ratio of farPlane / nearPlane as small as practical to maintain depth buffer precision.
Depth buffer and blend state
Depth buffer
The depth buffer prevents back faces of opaque geometry from overwriting front faces. CNA initialises the GraphicsDevice with depth testing enabled by default. You can switch states explicitly when mixing 3D and 2D content in the same frame.
| State preset | Depth test | Depth write | Typical use |
|---|---|---|---|
DepthStencilState::Default |
Less | Yes | Standard opaque 3D geometry |
DepthStencilState::DepthRead |
Less | No | Transparent or decal geometry that tests but does not write depth |
DepthStencilState::None |
Off | No | SpriteBatch, HUD, and 2D overlays drawn after 3D geometry |
// 3D pass: depth enabled
graphicsDevice->DepthStencilState = DepthStencilState::Default;
Draw3DScene();
// 2D overlay pass: depth off so sprites always appear on top
graphicsDevice->DepthStencilState = DepthStencilState::None;
spriteBatch->Begin();
spriteBatch->DrawString(font, L"Score: 100", Vector2(10, 10), Color::White);
spriteBatch->End();
Blend state
Transparent 3D geometry requires an additive or alpha-blend state and should be drawn back-to-front after all opaque geometry.
// Opaque geometry first (depth writes on)
graphicsDevice->BlendState = BlendState::Opaque;
graphicsDevice->DepthStencilState = DepthStencilState::Default;
DrawOpaqueMeshes();
// Transparent geometry last (depth writes off, alpha blend)
graphicsDevice->BlendState = BlendState::AlphaBlend;
graphicsDevice->DepthStencilState = DepthStencilState::DepthRead;
DrawTransparentMeshes(); // caller responsible for back-to-front sorting
RenderTarget2D
Off-screen rendering lets you draw a scene into a texture and then use that texture in a subsequent draw call — for example for shadow maps, reflections, or post-process effects. SurfaceFormat::Color is the portable common denominator; Skia and IGL have the renderer-qualified exceptions described above.
// Create a 512x512 off-screen target
auto rt = std::make_shared<RenderTarget2D>(
graphicsDevice, 512, 512,
false, // no mipmaps
SurfaceFormat::Color,
DepthFormat::Depth24);
// Render the scene into the target
graphicsDevice->SetRenderTarget(rt);
graphicsDevice->Clear(Color::Black);
DrawMirroredScene();
// Restore the back buffer
graphicsDevice->SetRenderTarget(nullptr);
// Use the render target as a texture in the next pass
effect->SetTextureEnabled(true);
effect->SetTexture(rt); // RenderTarget2D is-a Texture2D
effect->CurrentTechnique->Passes[0]->Apply();
DrawFullScreenQuad();
Model rendering
The Model class represents a complete 3D asset loaded by the ContentManager. It handles mesh hierarchy, bone transforms, and effect binding internally. The minimal draw call is a single line:
model->Draw(worldMatrix, viewMatrix, projectionMatrix);
Model::Draw iterates over all ModelMesh objects within the model, applies each mesh’s accumulated bone transform to the supplied world matrix, and calls the appropriate effect. Each mesh carries its own MeshPart list and associated Effect. For most assets this is a BasicEffect configured with the texture and lighting settings baked in at export time.
To override the effect on all meshes (for example to apply a custom tint or swap to a SkinnedEffect for animation):
// Replace the effect on every mesh part
for (auto& mesh : model->Meshes) {
for (auto& part : mesh->MeshParts) {
auto be = std::dynamic_pointer_cast<BasicEffect>(part->Effect);
if (be) {
be->SetEmissiveColor(Vector3(0.1f, 0.0f, 0.0f)); // subtle red tint
}
}
}
model->Draw(world, view, projection);
Skeletal animation drives the ModelBone hierarchy. Compute the final bone-pose matrices with your animation system and pass them to the effect before drawing:
// For a skinned model: update bone matrices each frame
std::vector<Matrix> boneTransforms(model->Bones.Count());
model->CopyAbsoluteBoneTransformsTo(boneTransforms);
for (auto& mesh : model->Meshes) {
for (auto& part : mesh->MeshParts) {
auto se = std::dynamic_pointer_cast<SkinnedEffect>(part->Effect);
if (se) {
se->SetBoneTransforms(boneTransforms);
}
}
mesh->Draw();
}
Texture3D and TextureCube
Texture3D (volumetric) and TextureCube (six-face cube map) are available on the 3D-capable renderers; WebGPU supports neither. TextureCube is used directly by EnvironmentMapEffect for reflection mapping. Texture3D is useful for volumetric fog, 3D lookup tables (LUTs), and density fields. Unlike Texture2D's renderer-qualified format classification, both call the framework's Texture::ValidateFormat rule directly and therefore remain SurfaceFormat::Color-only — a float LUT is not constructible.
// Load a cube map through ContentManager
auto skybox = content.Load<TextureCube>("textures/skybox");
auto envEffect = std::make_shared<EnvironmentMapEffect>(graphicsDevice);
envEffect->SetEnvironmentMap(skybox);
envEffect->SetEnvironmentMapAmount(1.0f);
envEffect->SetFresnelFactor(0.5f);
Advanced topics
GPU instancing
Drawing many identical meshes (trees, rocks, crowd members) can be done in a single GPU call using DrawInstancedPrimitives. The per-instance data (world matrix, colour, etc.) is uploaded in a second vertex buffer bound to stream index 1 with a step frequency of 1 instance rather than 1 vertex.
// Second vertex buffer holds one matrix per instance
auto instanceVB = std::make_shared<VertexBuffer>(
graphicsDevice, InstanceData::VertexDeclaration,
instanceCount, BufferUsage::WriteOnly);
instanceVB->SetData(instanceMatrices.data(), instanceCount);
// Bind both streams
graphicsDevice->SetVertexBuffers({
VertexBufferBinding(meshVB, 0, 0), // stream 0: mesh geometry
VertexBufferBinding(instanceVB, 0, 1), // stream 1: per-instance data
});
graphicsDevice->Indices = meshIB;
effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawInstancedPrimitives(
PrimitiveType::TriangleList,
0, 0, triangleCount, instanceCount);
Instancing is confirmed on EasyGL and DIRECTX11. It is not available on WebGPU, and the 13 2D-only renderers (SDL_Renderer, Direct2D, Canvas, HTML_DOM, Skia, Blend2D, FreeDirect, DirectX1, GDI, SVG_DOM, OpenVG, NanoVG and PixiJS) have no instancing path at all. Verify against your chosen renderer before relying on it — and note that SupportsCapability() cannot answer this question reliably (see Cross-renderer caveats).
Multiple render targets (MRT)
Binding multiple RenderTarget2D objects simultaneously enables G-buffer rendering for deferred shading. Support varies sharply: Vulkan has an MRT proxy, DIRECTX11 has MRT with deferred resolve, and DIRECTX9 has caps-validated MRT. On DIRECTX12, MRT clears but never draws. WebGPU has no render targets at all — it is a backbuffer-only forward renderer. On the common Color-only families a G-buffer must pack into RGBA8; Skia and IGL have the qualified exceptions above. Pass an array of render targets to SetRenderTargets:
graphicsDevice->SetRenderTargets({
RenderTargetBinding(albedoRT),
RenderTargetBinding(normalRT),
RenderTargetBinding(depthRT),
});
DrawGeometryPass();
// Restore back buffer
graphicsDevice->SetRenderTarget(nullptr);
OcclusionQuery
OcclusionQuery asks the GPU how many fragments passed the depth test for a given draw. This is used for techniques like hardware-accelerated lens flares or conditional rendering of expensive effects. Occlusion queries are confirmed on EasyGL; WebGPU has no queries at all.
auto query = std::make_shared<OcclusionQuery>(graphicsDevice);
query->Begin();
DrawProxyGeometry(); // small bounding box or billboard
query->End();
// On a later frame (avoid stalling the GPU by checking immediately)
if (query->IsComplete) {
bool visible = query->PixelCount > 0;
}
Code examples
Minimal 3D triangle
The smallest complete example: a coloured triangle rendered with BasicEffect and no index buffer.
// --- Setup (LoadContent) ---
std::vector<VertexPositionColor> verts = {
{ Vector3( 0.0f, 0.5f, 0.0f), Color::Red },
{ Vector3( 0.5f, -0.5f, 0.0f), Color::Green },
{ Vector3(-0.5f, -0.5f, 0.0f), Color::Blue },
};
auto vb = std::make_shared<VertexBuffer>(
graphicsDevice,
VertexPositionColor::VertexDeclaration,
3, BufferUsage::None);
vb->SetData(verts.data(), 3);
auto effect = std::make_shared<BasicEffect>(graphicsDevice);
effect->SetVertexColorEnabled(true);
effect->SetWorld(Matrix::Identity);
effect->SetView(Matrix::CreateLookAt(
Vector3(0, 0, 3), Vector3::Zero, Vector3::Up));
effect->SetProjection(Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4,
graphicsDevice->Viewport.AspectRatio(),
0.1f, 100.0f));
// --- Draw (Draw) ---
graphicsDevice->Clear(Color::CornflowerBlue);
graphicsDevice->SetVertexBuffer(vb);
effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawPrimitives(PrimitiveType::TriangleList, 0, 1);
Textured cube with camera
A complete rotating textured cube using an index buffer and a perspective camera.
// 8 unique vertices for a unit cube
std::vector<VertexPositionTexture> cubeVerts = {
// front
{ Vector3(-0.5f, 0.5f, 0.5f), Vector2(0, 0) },
{ Vector3( 0.5f, 0.5f, 0.5f), Vector2(1, 0) },
{ Vector3( 0.5f, -0.5f, 0.5f), Vector2(1, 1) },
{ Vector3(-0.5f, -0.5f, 0.5f), Vector2(0, 1) },
// back
{ Vector3( 0.5f, 0.5f, -0.5f), Vector2(0, 0) },
{ Vector3(-0.5f, 0.5f, -0.5f), Vector2(1, 0) },
{ Vector3(-0.5f, -0.5f, -0.5f), Vector2(1, 1) },
{ Vector3( 0.5f, -0.5f, -0.5f), Vector2(0, 1) },
// ... (left, right, top, bottom faces omitted for brevity)
};
std::vector<uint16_t> cubeIdx = {
0,1,2, 0,2,3, // front
4,5,6, 4,6,7, // back
// ...
};
auto cubeVB = std::make_shared<VertexBuffer>(graphicsDevice,
VertexPositionTexture::VertexDeclaration,
static_cast<int>(cubeVerts.size()), BufferUsage::None);
cubeVB->SetData(cubeVerts.data(), static_cast<int>(cubeVerts.size()));
auto cubeIB = std::make_shared<IndexBuffer>(graphicsDevice,
IndexElementSize::SixteenBits,
static_cast<int>(cubeIdx.size()), BufferUsage::None);
cubeIB->SetData(cubeIdx.data(), static_cast<int>(cubeIdx.size()));
auto cubeEffect = std::make_shared<BasicEffect>(graphicsDevice);
cubeEffect->SetTextureEnabled(true);
cubeEffect->SetTexture(crateTexture);
// In Update(): accumulate rotation angle
float angle += (float)gameTime.getElapsedGameTimeProperty().getTotalSecondsProperty();
// In Draw():
graphicsDevice->Clear(Color::CornflowerBlue);
graphicsDevice->DepthStencilState = DepthStencilState::Default;
cubeEffect->SetWorld(Matrix::CreateRotationY(angle));
cubeEffect->SetView(Matrix::CreateLookAt(
Vector3(0, 1.5f, 3), Vector3::Zero, Vector3::Up));
cubeEffect->SetProjection(Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4,
graphicsDevice->Viewport.AspectRatio(),
0.1f, 100.0f));
graphicsDevice->SetVertexBuffer(cubeVB);
graphicsDevice->Indices = cubeIB;
cubeEffect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawIndexedPrimitives(
PrimitiveType::TriangleList, 0, 0,
static_cast<int>(cubeIdx.size()) / 3);
Model::Draw usage
Load a .cnamodel asset through the ContentManager and draw it with a single call.
// LoadContent
auto houseModel = content.Load<Model>("models/house");
// Update: animate position or rotation
Matrix houseWorld = Matrix::CreateTranslation(housePos);
// Draw
Matrix view = Matrix::CreateLookAt(cameraPos, cameraTarget, Vector3::Up);
Matrix proj = Matrix::CreatePerspectiveFieldOfView(
MathHelper::PiOver4,
graphicsDevice->Viewport.AspectRatio(),
0.5f, 1000.0f);
graphicsDevice->DepthStencilState = DepthStencilState::Default;
graphicsDevice->BlendState = BlendState::Opaque;
houseModel->Draw(houseWorld, view, proj);
Wireframe rendering
Wireframe rendering requires RasterizerState::WireFrame, which sets FillMode::WireFrame. This fill mode is not available on the EasyGL (OpenGL ES 3.0) renderer because OpenGL ES does not expose a polygon fill-mode toggle. Vulkan supports it; bgfx has a dedicated wireframe path (and is, incidentally, the only bgfx path where draw offsets are honoured).
// Switch to wireframe (Vulkan renderer required)
graphicsDevice->RasterizerState = RasterizerState::WireFrame;
effect->SetVertexColorEnabled(true);
effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawIndexedPrimitives(
PrimitiveType::TriangleList, 0, 0, triangleCount);
// Restore solid fill for the next draw
graphicsDevice->RasterizerState = RasterizerState::CullCounterClockwise;
FillMode::WireFrame is not available on the EasyGL renderer (OpenGL ES 3.0 does not support polygon mode). An alternative on EasyGL is to draw your geometry with PrimitiveType::LineList or LineStrip, or to generate wireframe edge geometry explicitly in the vertex buffer.