---
title: "Draw a 3D physics debug overlay in C#"
url: https://recursiveai.org/onegame/cookbook/draw-3d-physics-debug-overlay/
description: "Draw a 3D physics debug overlay in C# with onegame. Includes a runnable .NET project, sample output and the game integration details."
version: "1.26.0"
---

# Draw a 3D physics debug overlay in C#

**onegame** is a free, open-source game engine for .NET from Recursive AI. MIT or Apache-2.0, no editor required.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneGame.Physics) · [GitHub](https://github.com/recursive-ai-oss/onegame) · [Docs](https://recursiveai.org/onegame/docs/) · [Download example (.zip)](https://recursiveai.org/onegame/cookbook/draw-3d-physics-debug-overlay/draw-3d-physics-debug-overlay.zip)

Draw collision shapes over a 3D scene. The download writes an offscreen PNG and requires a graphics device.

```shell
dotnet add package \
  RecursiveAI.OneGame.Physics
dotnet add package \
  RecursiveAI.OneGame.Host
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneGame.Core;
using OneGame.Physics;
using OneGame.Render3D;

var space = new PhysicsSpace3D();
space.AddBox(
    new FVec3(1, 1, 1), new FVec3(0, 2, 0));
var debug = new PhysicsDebug3D();
var scene = new Scene3D();
debug.Draw(space, scene.Debug,
    debugVisible: true);
Console.WriteLine(
    $"Debug primitives: {scene.Debug.Count}");
```

Prints:

```text
Debug primitives: 36
```

Unzip [the example](https://recursiveai.org/onegame/cookbook/draw-3d-physics-debug-overlay/draw-3d-physics-debug-overlay.zip), then `dotnet run`.

`Fixed` is a deterministic fixed-point number type; `FVec3` is a three-dimensional vector of those numbers.

## The full program

```csharp
using OneGame.Core;
using OneGame.Host;
using OneGame.Physics;
using OneGame.Render3D;

var definition = new DebugGame();
using var game = GameInstance.Create(definition,
    new GameOptions { Headless = true, Gpu = GpuMode.Required });
definition.View.DebugVisible = true;
var frame = game.Capture(640, 360);
File.WriteAllBytes("overlay.png", Png.Encode(frame));
Console.WriteLine("Wrote overlay.png");

sealed class DebugGame : GameDefinition
{
    public SceneView3D View { get; } = new();
    public override string Name => "Physics overlay";
    public override void DefineFonts(OneGame.Text.FontLibrary fonts)
        => fonts.Add(File.ReadAllBytes("NotoSans-Regular.ttf"));
    public override void RegisterEntities(EntityRegistry registry) { }
    public override void Setup(World world)
    {
        PhysicsSpace3D.Of(world)!.AddBox(
            new FVec3(1, 1, 1), new FVec3(0, 0, 0));
    }
    public override void RegisterSystems(World world) => PhysicsSpace3D.AddTo(world);
    public override void DefineScreens(ScreenSet screens)
        => screens.Add("main", _ => OneGame.UI.Ui.Column());
    public override IRenderStage CreateRenderStage(HostResources resources)
        => new DebugStage(resources, View);
}

sealed class DebugStage(HostResources resources, SceneView3D view)
    : Render3DStage(resources, view)
{
    private readonly PhysicsDebug3D debug = new();
    protected override void UpdateScene(Scene3D scene, in FrameInfo frame)
    {
        if (PhysicsSpace3D.Of(frame.World) is { } space)
            debug.Draw(space, scene.Debug, View!.DebugVisible);
    }
}
```

`dotnet run` prints:

```text
Wrote overlay.png
```

The snippet at the top collects wireframe primitives in a scene; it does not render pixels. The full program runs a headless host, draws the box through a render-stage hook, and writes `overlay.png`. The Noto Sans font ships in the zip and is required by the render stage even though this scene has no text. `RecursiveAI.OneGame.Render3D`, included through `RecursiveAI.OneGame.Host`, provides the `OneGame.Render3D` types.

The full program requires a graphics device; `GpuMode.Required` fails rather than silently replacing the 3D overlay with a CPU capture.

## Variations

### Inspect occluded geometry

```csharp
// With space, debug and scene from the snippet at the top:
scene.Debug.Clear();
debug.Draw(space, scene.Debug, true,
    DebugDepth3D.AlwaysOnTop);
Console.WriteLine(scene.Debug.Count);
```

Prints:

```text
36
```

## Related recipes

[Shift the physics origin](https://recursiveai.org/onegame/cookbook/shift-physics-origin/) · [Create 2D physics bodies](https://recursiveai.org/onegame/cookbook/2d-physics/)

---

## The short answer

Reuse a `PhysicsDebug3D` in a render stage. After the scene view has built the frame, call `debug.Draw(space, scene.Debug, View!.DebugVisible)`. The adapter collects physics geometry and adds it to the renderer’s debug list. The shared visibility switch is controlled by `overlay` or F3 in an interactive game.

A standalone `DebugDraw3D` list is not a picture. The short snippet demonstrates collection; the full download supplies the host, render stage, font and capture call needed to write pixels. This separation lets you reuse the same adapter in a running game instead of creating a second physics visualization system.

## Where can I download a C# 3D physics debug draw example?

The example linked above contains a .NET console project with pinned packages and a complete `DebugStage`. Unzip it and run `dotnet run` on a machine with a supported graphics device. The output file is `overlay.png`, a capture of the offscreen scene. “Headless” means no window; it does not mean that this 3D rendering path is software-only or independent of graphics drivers.

The full program registers `PhysicsSpace3D` on the world before adding a box. It provides a `SceneView3D` and returns a custom `Render3DStage` from the game definition. The stage owns one debug adapter, so it can retain scratch storage across frames. `UpdateScene` finds the world’s space and draws into that frame’s scene debug list.

The sample enables `View.DebugVisible` explicitly because it captures one frame without keyboard input. In an interactive game pass the view’s existing switch instead of hard-coding `true`. When the switch is off the adapter does not collect or draw physics geometry. This keeps the physics overlay aligned with the same debug visibility control the player or developer already uses.

The adapter supplies shape wireframes, wheel outlines, suspension, joint anchors, contact points and normal arrows. Those are inspection primitives, not replacement character or vehicle models. A missing mesh can still have a visible collider; conversely, a decorative mesh is not evidence that a collision shape exists. Compare the two representations when checking scale, placement and contact behavior.

Depth-tested debug primitives are the default. Scene surfaces can therefore hide portions of a collider, which is useful when judging where it intersects visible geometry. Use `DebugDepth3D.AlwaysOnTop` when you need to inspect hidden shapes. That option changes how the overlay is rendered, not how the bodies collide or where they exist.

The console variation clears the destination before collecting again. The adapter appends to a debug list and does not clear that list for you. In your own collection loop, retaining old primitives will make the overlay accumulate stale shapes. Follow the renderer’s frame lifecycle rather than treating the debug list as a persistent history of body positions.

The adapter never changes simulation state. Keep it on the view/render side; do not use its floating-point drawing geometry as a source of collision decisions. The authority—the game instance that decides shared state—keeps the physics table hidden from clients, so a client does not automatically have a full physics world to inspect. Draw the authority space where it exists, rather than assuming every client has it. See the [physics overlay documentation](https://recursiveai.org/onegame/docs/physics/#drawing-the-3d-physics-overlay) for the render hook and visibility contract.
