Effects System
Implementation status: All five XNA 4.0 stock effects — BasicEffect, AlphaTestEffect, DualTextureEffect, EnvironmentMapEffect and SkinnedEffect — are implemented and pixel-tested on all three 3D backends (EasyGL, Vulkan and bgfx). Two known gaps remain: EnvironmentMapEffect and SkinnedEffect only forward DirectionalLight0 to the GPU (lights 1 and 2 are dropped), and SkinnedEffect has no specular term. Custom shaders are a different story — CNA cannot load compiled .fx bytecode, so they must be hand-written; see Shader Effects.
Overview
CNA implements the full XNA 4.0 effects pipeline inside the Microsoft::Xna::Framework::Graphics namespace. An effect is a shader program combined with its parameter set and render state. You apply an effect by setting its parameters, then calling effect.CurrentTechnique.Passes[0].Apply() before drawing primitives.
All stock effects implement one or more effect interfaces (IEffectMatrices, IEffectFog, IEffectLights) so that higher-level drawing code can treat effects polymorphically. Parameters can also be accessed directly through EffectParameter objects exposed on the effect.
Custom shaders are supported via the ShaderEffect class, which accepts user-supplied GLSL source (EasyGL backend) or SPIR-V bytecode (Vulkan backend), loaded through the ContentManager pipeline using a .shader.json descriptor.
Stock effects at a glance
| Effect | Key capability | Implements | Status |
|---|---|---|---|
BasicEffect |
Ambient/diffuse/specular lighting, up to 3 directional lights, texture mapping, fog | IEffectMatrices, IEffectFog, IEffectLights | Implemented |
AlphaTestEffect |
Alpha threshold discard (greater/less/equal/etc.), single texture | IEffectMatrices, IEffectFog | Implemented |
DualTextureEffect |
Blends two textures with fog | IEffectMatrices, IEffectFog | Implemented |
EnvironmentMapEffect |
Cube-map environment reflection and Fresnel, fog | IEffectMatrices, IEffectFog, IEffectLights | Implemented |
SkinnedEffect |
Skeletal animation, up to 72 bones via UBO, 1–4 skinning weights, fog | IEffectMatrices, IEffectFog, IEffectLights | Implemented |
SpriteEffect |
Internal 2D effect used by SpriteBatch; not typically used directly | IEffectMatrices | Implemented |
BasicEffect
BasicEffect is the general-purpose 3D effect. It supports per-vertex and per-pixel lighting with up to three independent directional lights, a single diffuse texture, per-vertex color, and distance fog. Lighting is controlled via the IEffectLights interface — call EnableDefaultLighting() to activate a standard three-point light rig in a single call.
// C++23 example
auto effect = std::make_shared<BasicEffect>(graphicsDevice);
effect->SetWorld(Matrix::Identity);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->EnableDefaultLighting();
effect->SetTextureEnabled(true);
effect->SetTexture(myTexture);
for (auto& pass : effect->CurrentTechnique->Passes) {
pass->Apply();
graphicsDevice->DrawIndexedPrimitives(
PrimitiveType::TriangleList, 0, 0, indexCount / 3);
}
AlphaTestEffect
AlphaTestEffect renders a single textured surface and discards fragments whose alpha does not satisfy a configurable comparison against a reference value. Supported comparisons are Always, Never, Equal, NotEqual, Less, LessEqual, Greater, and GreaterEqual. This effect is useful for vegetation, fences, and other cut-out geometry where alpha blending would cause sorting problems.
auto effect = std::make_shared<AlphaTestEffect>(graphicsDevice);
effect->SetWorld(transform);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->SetTexture(treeBarkTexture);
effect->SetAlphaFunction(CompareFunction::Greater);
effect->SetReferenceAlpha(128); // discard alpha <= 0.5
effect->CurrentTechnique->Passes[0]->Apply();
DualTextureEffect
DualTextureEffect samples two textures in the same draw call and blends them together. A common use case is lightmap baking: the first texture holds the base colour and the second holds a pre-baked lightmap. Both textures share the same set of texture coordinates unless the shader variant with independent UV channels is selected.
auto effect = std::make_shared<DualTextureEffect>(graphicsDevice);
effect->SetWorld(Matrix::Identity);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->SetTexture(diffuseTexture);
effect->SetTexture2(lightmapTexture);
effect->CurrentTechnique->Passes[0]->Apply();
EnvironmentMapEffect
EnvironmentMapEffect adds a cube-map environment reflection on top of a diffuse texture. The amount of reflection versus base colour is controlled by the environment map amount and an optional Fresnel factor, which makes reflections stronger at glancing angles. A single directional light contributes specular highlights in addition to the environment map. Fog is also supported.
auto effect = std::make_shared<EnvironmentMapEffect>(graphicsDevice);
effect->SetWorld(sphereTransform);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->SetTexture(diffuseTexture);
effect->SetEnvironmentMap(skyboxCube);
effect->SetEnvironmentMapAmount(0.8f);
effect->SetFresnelFactor(1.0f);
effect->CurrentTechnique->Passes[0]->Apply();
SkinnedEffect
SkinnedEffect is the standard effect for skeletal (skinned) mesh animation. Bone matrices are uploaded to a Uniform Buffer Object (UBO), supporting up to 72 bones per draw call. Each vertex can reference 1, 2, or 4 bones through the WeightsPerVertex property. The effect also supports the full lighting model (three directional lights) and fog.
auto effect = std::make_shared<SkinnedEffect>(graphicsDevice);
effect->SetWorld(Matrix::Identity);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->EnableDefaultLighting();
effect->SetWeightsPerVertex(4);
effect->SetBoneTransforms(boneMatrices); // std::span<Matrix>, max 72
effect->CurrentTechnique->Passes[0]->Apply();
SpriteEffect
SpriteEffect is an internal 2D effect consumed by SpriteBatch. It sets up an orthographic projection over the back-buffer dimensions and routes through a simple textured-quad shader. Application code does not normally instantiate SpriteEffect directly — SpriteBatch manages it internally, but it is exposed in the public API for completeness and for XNA compatibility.
Backend parity
The table below shows which effect is available on each rendering backend. pixel-tested means rendered output has been verified; compiles/links means the code is present but no pixel-level test has been run yet; not implemented means the effect is absent from that backend; limited 2D only means only the 2D SpriteBatch path works.
| Effect | EasyGL | Vulkan | bgfx | SDL_Renderer |
|---|---|---|---|---|
BasicEffect |
pixel-tested | compiles/links | compiles/links | limited 2D only |
AlphaTestEffect |
pixel-tested | compiles/links | compiles/links | – |
DualTextureEffect |
pixel-tested | pixel-tested | compiles/links | – |
EnvironmentMapEffect |
pixel-tested | pixel-tested | not implemented | – |
SkinnedEffect |
pixel-tested | compiles/links | compiles/links | – |
ShaderEffect (custom GLSL/SPIR-V) |
pixel-tested | pixel-tested | not implemented | – |
Effect interfaces
IEffectMatrices
Implemented by every stock 3D effect. Provides the three standard transformation matrices used to project geometry from object space to clip space.
| Property | Type | Description |
|---|---|---|
World | Matrix | Object-to-world transform (model matrix) |
View | Matrix | World-to-camera transform |
Projection | Matrix | Camera-to-clip transform (perspective or orthographic) |
IEffectFog
Implemented by all stock effects that render geometry in world space. Distance fog is blended linearly between FogStart and FogEnd. Set FogEnabled = false to disable the computation entirely.
| Property | Type | Description |
|---|---|---|
FogEnabled | bool | Enables or disables distance fog |
FogStart | float | Camera distance at which fog begins |
FogEnd | float | Camera distance at which fog is fully opaque |
FogColor | Vector3 | RGB colour of the fog |
IEffectLights
Implemented by BasicEffect, EnvironmentMapEffect, and SkinnedEffect. Exposes an ambient light and three independent directional lights. Call EnableDefaultLighting() to configure a standard three-point lighting setup in one call.
| Member | Type | Description |
|---|---|---|
AmbientLightColor | Vector3 | RGB colour of the scene ambient light |
DirectionalLight0 | DirectionalLight | First directional light |
DirectionalLight1 | DirectionalLight | Second directional light |
DirectionalLight2 | DirectionalLight | Third directional light |
EnableDefaultLighting() | void | Configures a standard three-point lighting rig |
Each DirectionalLight exposes:
Direction—Vector3pointing toward the light source (normalized)DiffuseColor—Vector3RGB diffuse contributionSpecularColor—Vector3RGB specular contributionEnabled—boolenables or disables this light
EffectParameter
All effect parameters are accessible through the Effect::Parameters collection as EffectParameter objects. Parameters can be read and written using typed GetValue<T>() and SetValue() overloads. This mirrors the XNA 4.0 API exactly.
Supported types for Get/Set:
float— scalar floating-point valueVector2,Vector3,Vector4— float vectorsMatrix— 4×4 column-major matrixMatrix[]— array of matrices (used bySkinnedEffectbone palette)Texture2D— 2D texture sampler bindingbool— boolean flag
// Look up a parameter by name and set it directly
auto param = effect->Parameters["DiffuseColor"];
param->SetValue(Vector4(1.0f, 0.5f, 0.0f, 1.0f));
// Read back a matrix
Matrix world = effect->Parameters["World"]->GetValue<Matrix>();
Prefer the typed properties on stock effects (e.g. effect->SetWorld(...)) over raw EffectParameter access where possible. Typed setters validate input and update dependent cached state such as the combined WorldViewProjection matrix.
Custom ShaderEffect
When the stock effects do not meet your needs, ShaderEffect lets you supply your own shader source. The shader is loaded through the ContentManager using a .shader.json descriptor that references the shader source files.
Backend requirement: Custom shaders use GLSL source on the EasyGL backend and SPIR-V bytecode on the Vulkan backend. The bgfx and SDL_Renderer backends do not currently support custom ShaderEffect.
Example .shader.json descriptor:
{
"vertex": "shaders/my_effect.vert.glsl",
"fragment": "shaders/my_effect.frag.glsl"
}
Loading and using a custom effect:
// Load via ContentManager
auto myEffect = content.Load<ShaderEffect>("shaders/my_effect");
// Set parameters by name
myEffect->Parameters["WorldViewProj"]->SetValue(wvp);
myEffect->Parameters["Tint"]->SetValue(Vector4(1, 0.8f, 0.6f, 1));
// Apply and draw
myEffect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice->DrawPrimitives(PrimitiveType::TriangleList, 0, triangleCount);
For the Vulkan backend, replace the GLSL source references in the descriptor with pre-compiled SPIR-V files (.spv). The push constant block is limited to 128 bytes, and uniform buffer layout follows std140 rules, matching standard Vulkan conventions.