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
posFixupvertex 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.