# Neo 3D: a CPU-only 3D engine that renders into the system console

> Neo 3D is a C#/.NET 8 engine by IvanSobolev that does raycasting and raytracing on the CPU and paints frames as character gradients in a terminal. It is educational, GPL-3.0, and comes with a TCP multiplayer lobby, but the input layer and the console itself set hard limits.

**IvanSobolev/Neo3dEngine** — A minimalist CPU-based 3D console engine in C# (.NET 8) built from scratch. Features Raycasting/Raytracing, dynamic lighting & shadows, .obj loader, and custom TCP-based multiplayer with real-time chat. No external graphics APIs.

- Repository: https://github.com/IvanSobolev/Neo3dEngine
- Website: https://youtu.be/vhYE882B9dE
- Stars: 706 · Forks: 10
- Language: C#
- License: GPL-3.0
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/ivansobolev-neo3dengine

## What Neo 3D actually is, and who it is written for

Neo 3D renders 3D scenes without a graphics API. Every visible pixel is computed on the CPU, then mapped to a character from the gradient " .:!/r(l1Z4H9W8$@" and written to the system console. The README states the project was created for demonstration and educational purposes for a YouTube video, and that framing explains most of the design decisions: the engine ships its own Vector3, Vector2 and Ray structures, its own rotation matrices, and its own network serialization instead of pulling in libraries.

The audience is narrow and specific. If you want to study how a ray tracer is assembled from primitives, or how a binary protocol rides on top of TCP sockets, the repository is small enough to read in full: the solution is 3dEngine.sln, with the engine in 3dEngine/ and a runnable example in SampleGame/. If you want a renderer to ship inside a product, this is not that. The console is the display surface, the resolution is whatever the terminal reports, and the README itself notes that the most demanding computations are planned to move to the GPU via Vulkan in the future, which means they have not moved yet.

## The rendering path: bounding spheres, Möller-Trumbore, shadow rays

The pipeline described in the README is a classic CPU ray tracer with a few deliberate optimizations. Rays are tested against bounding spheres first, and only when that coarse check passes does the engine perform detailed polygon intersection using the Möller-Trumbore algorithm for ray-triangle intersection. That two-stage test is the difference between a scene that renders and one that does not, because triangle tests are the expensive part.

Lighting is computed with three separate mechanisms. Light attenuation depends on distance from the source, so brightness falls off with range. Lambertian diffuse lighting uses surface normals to shade geometry. Shadows come from casting a shadow ray from each intersection point toward the light source; if the ray is blocked, the point is dark. The README lists all three as features, and they compose in the obvious order: find the intersection, evaluate attenuation and diffuse terms, then test visibility.

Frames are assembled in a buffer before anything reaches the screen. The engine batches identical colors together, which the README describes as a way to minimize slow console system API calls. That detail matters more than it sounds: writing a character to a console is orders of magnitude slower than computing it, so the batching step is where a CPU renderer either keeps a usable frame rate or does not. Rendering itself is distributed with Parallel.For across all CPU cores, so the pixel loop is parallel while the console write stays serialized.

## Building and running the SampleGame scene

There are no third-party dependencies. The README says you need the .NET 8.0 SDK or newer, and nothing else. Clone the repository, then move into the SampleGame folder and build in Release configuration:

```bash
dotnet run --configuration Release
```

If the build succeeds, the active scene starts and you control it from the keyboard. The README gives the mapping: W, A, S, D move the camera, Space and Shift (or Shift plus any key under the dotnet input provider) control height, and the arrow keys rotate the view. The + and - keys raise and lower the scene's light intensity. In the scene containing the 3D object, holding Ctrl moves the light source.

On Linux there is a prerequisite the README states plainly: Wayland must be disabled for the terminal window, because Wayland does not grant the terminal direct access to X11. If Wayland is active, a warning appears when a scene starts. The README's suggested workaround is an independent terminal emulator forced into X11 compatibility mode:

```bash
apt install xterm
WAYLAND_DISPLAY= xterm
```

Run the build command inside that xterm window. The console size is detected and scaled at startup to fit the maximum terminal size, so resizing after launch is not part of the described behaviour.

## Writing your own scene against the engine API

A scene is a class deriving from Scene, constructed with an IDisplaysManagerAsync. The README's example creates a camera at (-10, 0, 0) looking at the origin, a red sphere of radius 1.5 at the origin, and a light at (-4, 3, -2) with lightPower 150f. The camera is registered with SetMainCamera, the sphere with AddDisplaysObject, and the light with AddLight. Per-frame logic goes in Update, where the README points at GameTime.GetDeltaTime() for timing.

```csharp
using _3dEngine;
using _3dEngine.AbstractClass;
using _3dEngine.Implementation;
using _3dEngine.Interfaces;
using _3dEngine.Shape;

public class PreviewScene : Scene
{
    private Camera _camera;
    private Sphere _sphere;
    private Light _light;

    public PreviewScene(IDisplaysManagerAsync manager) : base(manager) { }
}
```

The entry point is a Frame, constructed from your scene and a ConsoleScreenAsync, then driven by MainLoop:

```csharp
using _3dEngine;
using _3dEngine.Implementation;

class Program
{
    static void Main()
    {
        new Frame(new MyCustomScene(new DisplayManagerAsync()), new ConsoleScreenAsync()).MainLoop();
    }
}
```

Note the naming mismatch in the README's own snippet: the scene class is called PreviewScene in the first block and MyCustomScene in the second. Treat it as illustrative rather than copy-pasteable, and check the actual type names in the 3dEngine/ project before you build. The README does not document the full Scene base class, so anything beyond Start, Update, SetMainCamera, AddDisplaysObject and AddLight has to be read from source.

## Input providers, and the Linux case where Neo 3D is the wrong tool

Keyboard polling does not go through the console. The engine adapts to the host OS through an IInputProvider interface, with three implementations listed in the README: User32.dll on Windows, X11 on Linux with Wayland disabled, and a DotNet Input fallback used when system access to User32 or X11 is unavailable. The README is unusually candid about the fallback, calling it inefficient and noting limitations on simultaneous key presses and difficulty tracking modifier keys such as Shift, Ctrl and Alt. Since Ctrl moves the light source in the object scene, that limitation is not cosmetic.

The practical consequence is that Linux users on a Wayland session get the degraded provider or a warning, and the documented remedy is to run inside xterm with WAYLAND_DISPLAY unset. That is a real constraint on where the engine can run, not a configuration preference. If your workflow depends on a Wayland-native terminal, or if you cannot install xterm, Neo 3D will not behave as documented.

The second limitation is throughput. Every frame is characters written to a console, and the README's own batching exists because those writes are slow. Scenes with more geometry, more lights, or higher terminal resolution all push more work through the same serialized output path, and the parallel pixel loop cannot compensate for that. The README mentions a future stable UDP interface for online scenes, which tells you the current TCP transport is not considered sufficient for latency-sensitive play. Neither the README nor the changelog documents a rollback or recovery path for a failed multiplayer session.

## Multiplayer over TCP, and how it differs from a game networking stack

The networking layer is a custom client-server module on a TCP architecture. Packets use custom binary serialization, routing is automatic via stable type-hash IDs, and there is an event subscription system on top. The README lists two working examples: a multiplayer lobby with player position synchronization, and text chat.

The difference from a conventional game networking stack is the transport choice. TCP guarantees ordered, reliable delivery, which is what you want for chat and for a lobby where every player must agree on state. It is the wrong trade for position updates in a fast-moving scene, because a lost packet stalls everything behind it rather than being skipped. The README acknowledges this direction of travel by stating that a stable UDP interface is planned to increase performance in online scenes. Until that exists, the multiplayer examples should be read as demonstrations of the serialization and routing design, not as a netcode foundation.

If you want to compare approaches, the honest alternative is a library such as LiteNetLib or ENet for the transport, paired with a general-purpose engine like Godot or Unity for rendering. Those give you a UDP transport with reliability layers and a GPU renderer, at the cost of a much larger surface area and no console output. Neo 3D gives you roughly a thousand lines of readable C# and a character grid. The two are not substitutes; they answer different questions.

## Licence, maintenance and what an upgrade costs

Neo 3D is GPL-3.0. For a project whose stated purpose is education, that is a coherent choice: if you copy the engine into your own program and distribute it, the GPL's copyleft terms attach to that program. Linking a GPL-3.0 library into a closed-source product is the case to think about before you build on it, and that is a question for a lawyer rather than for this article. Reading the code to learn from it carries no such obligation.

The repository is not archived. The last push was on 2026-08-02, and the most recent release is v0.1.1 from 2026-06-11, following v0.1.0 on 2026-06-06. The README describes the default branch as the stable version, alongside a long-lived development branch and short-lived feature branches, so the project has a branching discipline even at version 0.1.x. It also points contributors at a CONTRIBUTING.md and states that requests for new features and deep architectural changes are accepted less frequently than other pull requests.

The upgrade cost is low in absolute terms because there are no third-party dependencies to reconcile: a newer .NET SDK and a git pull are the whole procedure. What you cannot rely on is API stability. At 0.1.x, with a Vulkan port described as a future plan, the interfaces shown in the README's scene example are the kind of thing that can move between releases. If you write a custom scene, pin the commit you built against rather than tracking main.

## Conclusion

Adopt Neo 3D if you want to read or write CPU rendering code in C# and see the result inside a terminal: the SampleGame scene, the Sphere and Light classes, and the TCP lobby are all small enough to follow end to end. Do not adopt it for a game you intend to ship, for anything that needs a GPU pipeline, or for a Linux desktop session where you cannot disable Wayland, because the X11 input provider will not attach. Before writing your own scene, run the SampleGame build once and confirm your terminal is recognised by the automatic size detection, since that step decides how many columns the frame buffer has to fill.

## FAQ

### What is an O3DE engine?

That question refers to a different project, Open 3D Engine, not to Neo 3D. Neo 3D is a C#/.NET 8 console 3D engine by IvanSobolev that performs raytracing and raytracing on the CPU and maps the result to characters in the system console. The two share no code and no maintainers.

### What do I need to install to build Neo 3D?

The README states that Neo 3D does not rely on third-party libraries and that you only need the .NET 8.0 SDK or newer. Clone the repository, move into the SampleGame folder and run the Release build command.

### Does Neo 3D work on Linux?

Yes, with a condition. The README requires Wayland to be disabled for the terminal window, because Wayland does not grant the terminal direct access to X11, and it suggests running inside xterm with WAYLAND_DISPLAY unset. If Wayland is active, a warning is displayed when starting a scene.

### Which keys control the camera in Neo 3D?

The README lists W, A, S, D for movement, Space and Shift for height, and the arrow keys for rotating the camera. The + and - keys change the scene's light intensity, and holding Ctrl moves the light source in the scene with the 3D object.

### What licence is Neo 3D released under?

The repository is licensed under GPL-3.0, as shown in the licence badge and the LICENSE file at the top level. That matters if you intend to distribute a program built from the engine rather than just read its source.

## Sources

- [IvanSobolev/Neo3dEngine on GitHub](https://github.com/IvanSobolev/Neo3dEngine)
- [License: GPL-3.0](https://github.com/IvanSobolev/Neo3dEngine/blob/main/LICENSE)
- [Project website](https://youtu.be/vhYE882B9dE)
- [README](https://github.com/IvanSobolev/Neo3dEngine/blob/main/README.md)
- [Releases](https://github.com/IvanSobolev/Neo3dEngine/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ivansobolev-neo3dengine
