How To: Advanced Graphics

Covers 3D rendering with BasicEffect, render-to-texture with RenderTarget2D, alpha testing, occlusion queries, and instanced draws.

Reference file: Advanced/SampleGame.Cube.cs


BasicEffect with vertex/index buffers

BasicEffect is MonoGame's built-in shader for unlit or per-vertex-lit geometry. The example below renders a rotating coloured cube:

private BasicEffect?  _basicEffect;
private VertexBuffer? _cubeVb;
private IndexBuffer?  _cubeIb;
private float         _cubeAngle;

// In LoadContent:
var verts = new VertexPositionColor[]
{
    new(new Vector3(-0.5f, -0.5f,  0.5f), Color.DodgerBlue),
    new(new Vector3( 0.5f, -0.5f,  0.5f), Color.DodgerBlue),
    new(new Vector3( 0.5f,  0.5f,  0.5f), Color.DodgerBlue),
    new(new Vector3(-0.5f,  0.5f,  0.5f), Color.DodgerBlue),
    // ... remaining 20 vertices (4 per face × 6 faces)
};

_cubeVb = new VertexBuffer(GraphicsDevice,
    typeof(VertexPositionColor), verts.Length, BufferUsage.WriteOnly);
_cubeVb.SetData(verts);

var indices = new short[36]; // 2 triangles × 6 faces × 3 indices
// ... fill indices ...
_cubeIb = new IndexBuffer(GraphicsDevice,
    IndexElementSize.SixteenBits, indices.Length, BufferUsage.WriteOnly);
_cubeIb.SetData(indices);

_basicEffect = new BasicEffect(GraphicsDevice)
{
    VertexColorEnabled = true,
    LightingEnabled    = false,
    View       = Matrix.CreateLookAt(new Vector3(0, 1.5f, 3), Vector3.Zero, Vector3.Up),
    Projection = Matrix.CreatePerspectiveFieldOfView(
        MathHelper.PiOver4, 160f / 120f, 0.1f, 100f),
};
// In Update:
_cubeAngle += (float)gameTime.ElapsedGameTime.TotalSeconds;

// In Draw:
_basicEffect.World = Matrix.CreateRotationY(_cubeAngle);
GraphicsDevice.SetVertexBuffer(_cubeVb);
GraphicsDevice.Indices = _cubeIb;
foreach (var pass in _basicEffect.CurrentTechnique.Passes)
{
    pass.Apply();
    GraphicsDevice.DrawIndexedPrimitives(
        PrimitiveType.TriangleList, 0, 0, 12); // 12 triangles = 36 indices
}

Vertex winding (MonoGame convention): MonoGame's posFixup vertex shader applies a Y-flip. Use clockwise winding in world space so faces are front-facing after the flip.


RenderTarget2D — render to texture (picture-in-picture)

// In LoadContent:
_rt = new RenderTarget2D(GraphicsDevice, 160, 120);

// In Draw — render the cube into the render target:
GraphicsDevice.SetRenderTarget(_rt);
GraphicsDevice.Clear(Color.Black);
// ... draw cube here ...
GraphicsDevice.SetRenderTarget(null); // restore the default framebuffer

// Blit the render target as a picture-in-picture thumbnail:
_spriteBatch!.Begin();
_spriteBatch.Draw(_rt, new Rectangle(8, 8, 160, 120), Color.White);
_spriteBatch.End();

SetRenderTarget(null) must be called to restore Avalonia's framebuffer before SpriteBatch draws to the screen. The Advanced sample renders the rotating cube into a 160×120 render target and blits it as a thumbnail in the corner.


AlphaTestEffect — per-pixel alpha discard

AlphaTestEffect discards pixels whose alpha is below a threshold rather than blending them. This avoids transparency sorting issues for textures with hard-edged transparency (e.g. sprite sheets, UI icons):

_alphaTestEffect = new AlphaTestEffect(GraphicsDevice)
{
    AlphaFunction   = CompareFunction.Greater,
    ReferenceAlpha  = 128,
    VertexColorEnabled = true,
    View       = Matrix.Identity,
    Projection = Matrix.CreateOrthographicOffCenter(
        0, GraphicsDevice.Viewport.Width,
        GraphicsDevice.Viewport.Height, 0, 0, 1),
};

// In Draw:
_spriteBatch!.Begin(effect: _alphaTestEffect);
_spriteBatch.Draw(_checkerTex, position, Color.White);
_spriteBatch.End();

OcclusionQuery — pixel count

OcclusionQuery reports how many pixels passed the depth test in the enclosed draw calls (result is from the previous frame due to GPU pipeline latency):

// In LoadContent — requires HiDef graphics profile:
_occlusionQuery = new OcclusionQuery(GraphicsDevice);

// In Draw:
if (_occlusionQueryPending && _occlusionQuery.IsComplete)
{
    _pixelCount = _occlusionQuery.PixelCount;
    _occlusionQueryPending = false;
}

if (!_occlusionQueryPending)
{
    _occlusionQuery.Begin();
    DrawMainContent();          // the geometry to test
    _occlusionQuery.End();
    _occlusionQueryPending = true;
}

OcclusionQuery requires GraphicsProfile.HiDef — set it in the constructor:

_graphics.GraphicsProfile = GraphicsProfile.HiDef;

DrawInstancedPrimitives

// Set up vertex and instance buffers, then:
GraphicsDevice.DrawInstancedPrimitives(
    PrimitiveType.TriangleList,
    baseVertex:    0,
    startIndex:    0,
    primitiveCount: triangleCount,
    instanceCount:  numberOfInstances);

Instanced drawing submits one draw call for many copies of the same geometry, each positioned/coloured by a per-instance vertex stream. Use it for particles, crowds, or any repeated geometry that would otherwise require thousands of individual draw calls.

latest ▼