Open-source project
SolarLune/tetra3d avatar
SolarLune/tetra3d

Tetra3D is a deliberately janky software renderer for Go games on Ebitengine

Tetra3D is a 3D hybrid software/hardware renderer made for games written in Go with Ebitengine.

523 stars26 forksGoMIT

At a glance

What is it?
Tetra3D is a 3D hybrid software and hardware renderer written in Go on top of Ebitengine, aiming for the look of a PS1 or N64 rather than the speed of a modern GPU pipeline. It loads glTF scenes, has colliders and particles, and ships a Blender add-on.
Who is it for?
Tetra3D suits a Go developer who wants a few 3D objects inside an otherwise 2D Ebitengine game, or a small PS1-scale 3D project, and who is happy with software-rendered output. It does not suit anyone needing modern 3D performance, dense scenes, or accurate depth intersection.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The goal is a PS1 look, not a fast renderer

Tetra3D is a 3D hybrid software and hardware renderer written in Go by means of Ebitengine, primarily for video games. The author's own framing is blunt about what that means. Compared to a professional 3D rendering system like OpenGL or Vulkan, it is slow(er), but the README adds that it is also janky, and the author says they love it for that.

The rendering evokes a similar feeling to primitive 3D game consoles like the PS1, N64, or DS. The author is also clear about the ceiling. Being that a largely-software renderer is not nearly fast enough for big, modern 3D titles, the best you are going to get out of Tetra is drawing some 3D elements for a primarily 2D Ebitengine game, or a relatively simple fully 3D game, something on the level of a PS1 or N64 game. The author adds that limitation breeds creativity and that they are intrigued at the thought of what people could make with Tetra.

That is the entire value proposition stated honestly. This is not a general purpose 3D engine that happens to be slow. It is a deliberate aesthetic and a deliberately small scope, and if you are evaluating it for a modern 3D title, the answer is already in the README.

The name comes from the tetrahedron, described as a relatively primitive but visually interesting 3D shape made of 4 triangles. The alternative name was Jank3D, which the author admits was the second-best choice.

The GPU is used for triangles and depth, and the depth is optional

The hybrid part of the hybrid renderer is worth understanding precisely, because it determines both the visual result and the performance ceiling.

Tetra3D is largely implemented in software, but uses the GPU a bit for rendering the triangles and for performing inter-object depth testing, by use of Kage shaders to compare and write depth and composite the result onto the finished texture.

So the triangle rasterisation is on the GPU, but the shading and the composition of the frame happen in software, and the depth comparison is done with a shader pass. That is a different arrangement from the usual hardware depth buffer, and it is why the renderer needs a shaders/ directory in its source tree.

The consequence is stated in one sentence in the README, and it is the sentence to remember before you build a scene: depth testing can be turned off for a slight performance increase in exchange for no visual inter-object intersection.

That is a real trade, not a bug. Without depth testing, objects are drawn in the order they are submitted and will visibly intersect each other, which for a PS1-style look can be an acceptable stylistic choice, since the era's hardware had similar visual quirks. For anything where correct occlusion matters, the depth pass has to stay on, and that is the cost of the hybrid arrangement.

The source layout backs this up. Alongside the shaders/ directory there are files named sortingTriangles.go, drawscratch.go, lightGroup.go, sector.go and broadphase.go, which are the pieces of a software renderer doing its own visibility and batching work rather than relying on a hardware pipeline to do it.

Loading a glTF scene, and the coordinate system you inherit

The API is small, and the README's example is the whole story: load a scene, render it.

The core of the example is a call that loads a glTF or glb file and returns a library:

go
	library, err := tetra3d.LoadGLTFFile("example.gltf", nil)

The comments in the example explain what you get. LoadGLTFFile takes a filepath and any loading options, nil being a valid default set, and returns a *tetra3d.Library and an error if it was unsuccessful. There is also tetra3d.LoadGLTFData() for the case where you do not have access to the host operating system's filesystem, for example when the assets are embedded.

A Library is essentially everything that got exported from your 3D modeler, all of the scenes, meshes, surfaces and animations, and the ExportedScene of a Library is the scene that was active when the file was exported. The example then clones it:

go
	g.GameScene = library.ExportedScene.Clone()

The clone is not cosmetic. Making a clone of a Tetra3D resource, whether a Scene, Node, Mesh or Camera, makes a deep copy of it, and the example clones so that changes to the scene do not change the loaded library irreversibly.

The coordinate system is the detail that bites people coming from Blender. Tetra3D uses OpenGL's coordinate system, with +X right, +Y up and +Z backward towards the camera, in comparison to Blender's system where +X is right, +Y forward and +Z up. Models loaded via GLTF or DAE are converted automatically, so up is +Z in Blender and +Y in Tetra3D without you doing anything.

Cameras come from the scene, and the example grabs an existing one from the scene root by name, casting the node to *tetra3d.Camera. If you exported through the Blender add-on the camera size can be set from inside Blender, otherwise you create one with tetra3d.NewCamera(ScreenWidth, ScreenHeight) so it can make its own buffer textures, which are *ebiten.Images.

The scene graph is nodes, and everything is an INode

The scene model is built on an interface called INode, and the example comment explains why that matters.

Camera implements the tetra3d.INode interface, which means it can be placed in 3D space and can be parented to another Node somewhere in the scene tree. Models, Lights and Nodes, which are essentially empties one can use for positioning and parenting, can as well.

So a camera is not a special case in the scene graph. It is a node, and it can be a child of another node. That single design decision is what lets you build a rig, animate a camera on a path, or parent a light to a moving object, without the engine special-casing each of those types.

The positioning API follows the same pattern. You can place models, cameras and other nodes with node.SetWorldPosition() or node.SetLocalPosition(), and the example notes that there are also variants that take more arguments, though the example itself stops there.

The repository file list shows how much sits on top of that. There is node.go, model.go, light.go, mesh.go, camera.go and scene.go, plus a separate file for surface definitions, plus nodesearch.go for finding nodes by name or property, properties.go for a property system, and separate files for each collider shape: colliderAABB.go, colliderCapsule.go, colliderSphere.go and colliderTriangles.go. There is ray.go for raycasts, path.go for path handling, particles.go, text.go, textureAnimation.go, callbacks.go and animation.go.

That is a substantial feature set for a project the author calls simple. The 3D frame is the part that is deliberately weak, not the scene management around it.

Colliders, particles, grids and a long list of examples

The examples/ directory is the best catalogue of what Tetra3D can do, and it is long: 3dSprite, animatedTextures, animation, animation_queueing, autoBatch, baking, blendmodes, cameraTest, collections, colliders, engine, grids, grids2, lightgroups, lighting, lightvolume, offscreen, orthographic, parenting, particles, paths, properties, rays and sectors.

Read that list as a feature map. There is a dedicated example for 3D sprites, which is the 2D-game-with-3D-elements case the author describes as the natural use. There is one for baking, for animatedTextures, and for blendmodes. There is one for orthographic projection and one for offscreen rendering. There is one for autoBatch, which is the software renderer batching draw calls itself, and one for sectors, which is a spatial subdivision structure, presumably to limit how much geometry has to be considered per frame.

The collider file names in the root tell you the collision vocabulary: axis-aligned bounding box, capsule, sphere and triangles. Combined with ray.go, that is enough for character controllers and picking, which is the usual reason a game needs 3D at all while the visuals are 2D.

A word about 3dSprite specifically. It is a good example of the hybrid nature of the project: a sprite is a flat quad in 3D space, which is exactly what you want for a 2D game that needs a GUI panel or an item floating in a 3D scene.

The other end of the tree is the performance work. There are matrix_amd64.s, matrix_none.go and matrix_simd.go, so the matrix math has assembly for amd64, a pure Go path, and a SIMD path, and the build picks between them. There is viewPProfInHTML.sh for profiling, and the dependency on golang.org/x/image plus the qmuntal/gltf library for asset handling.

Getting it, and the Go version floor that is not the real floor

The installation is a single Go get of the module path, `go get github.com/solarlune/tetra3d`.

Tetra depends on Ebitengine itself for rendering, and the module path in go.mod is github.com/solarlune/tetra3d, with the module requiring Go 1.24.0 in the manifest, while the README states that Tetra3D requires Go v1.18 or above.

The README is upfront that this minimum is somewhat arbitrary, since it could run on an older Go version if a couple of functions, primarily the ones that load data from a file directly, were changed. So the 1.18 figure is a policy rather than a hard technical limit, and the go.mod file records a newer one.

The dependency list is short and tells you the shape of the project: hajimehoshi/ebiten/v2 for the 2D framework it is built on, qmuntal/gltf for glTF parsing, tanema/gween for the tweening used in animation, and golang.org/x/image. Everything else in the manifest is an indirect dependency of Ebitengine, including the purego and gomobile packages that Ebitengine uses for its platform bindings.

There is an optional Blender add-on, tetra3d.py, downloadable from the releases page or from the repository directly. The add-on offers some useful helper functionality that makes using Tetra3D simpler, and the README points at a wiki page for more information. There is also a Quickstart Project Repo linked from the README, and the examples are viewable online through a hosted site, plus a separate tool called TetraTerm described as an easy-to-use tool to visualize your game scene.

The project also has a wiki and package documentation on pkg.go.dev, and a Discord for the author alongside the Ebitengine Discord.

Where Tetra3D is the wrong tool

The first wrong-tool case is a modern 3D game. The README is explicit that a largely-software renderer is not nearly fast enough for big, modern 3D titles, and the hybrid design with a Kage shader depth pass cannot change that. If your project needs hundreds of thousands of triangles, shadows, and a modern lighting model, you want a real GPU API, and the Go ecosystem has bindings for it.

The second case is a project that needs correct occlusion. Turning depth testing off is offered as a slight performance increase, but it means no visual inter-object intersection, and turning it on means paying for the depth pass. A game where characters walk behind walls and must disappear correctly has no use for the fast path.

The third case is a team that wants a mature, standard engine. Tetra3D is a single author's project with a 0.x version, and while the last push to the repository was on 2026-10-14 and the recent releases are v0.18.0 from 2026-07-11, v0.17.1 from 2025-09-10 and v0.17.0 from 2025-06-16, the release numbers say the API is still moving.

For the alternative approach, the README itself names the alternatives and the reason for starting. There is not really an ability to do 3D for gamedev in Go apart from g3n, go-gl and Raylib-go. The difference in approach between those and Tetra3D is the abstraction level. g3n is a scene graph with surfaces and lights, go-gl is direct OpenGL binding with no engine structure at all, and Raylib-go is a game framework with a simple immediate-mode API and its own renderer. Tetra3D sits between them: it inherits Ebitengine's windowing, input and audio so the whole game stays one framework, and adds a 3D layer that is deliberately small and deliberately retro.

The author also gives the reason for basing a 3D framework on an existing 2D one. Any platforms that Ebiten supports should also work for Tetra3D automatically, and improvements or refinements to Ebitengine may be of help to Tetra3D, which keeps the codebase small and unified between platforms. That is the real argument for this library, and it is a good one if you are already in Ebitengine and just need a few 3D objects.

MIT licensed, single author, with a plan you can read

The licence is MIT, which is the permissive choice and removes any question about using this inside a commercial game.

The project is by a single author, SolarLune, and the support section points to itch.io, Steam, Patreon and a Discord server. The Steam entry in particular, for a project with this scope, is a signal that at least one real game has been shipped with it, though the README does not say which.

The structure of the repository suggests a serious long-term commitment rather than a weekend experiment. Alongside the Go source there is a tetra3d.py Blender add-on, a colors/ directory, a math32/ package, a shaders/ directory, and test files named for the areas they cover, such as depthcode_test.go, depthrect_test.go, frame_test.go, gltf_test.go, gltf_texture_test.go and gltf_vertexdata_test.go, along with vector_test.go and matrix_test.go. The depth tests in particular are the right place to look if you want to know how the hybrid depth approach is verified.

The project also has a readme.md at the root as well as README.md, and a .gitignore, a go.mod and a go.sum, so it is a conventional Go module with a conventional repository.

The upgrade cost is the usual one for a young 3D library. The version is 0.x, the API can change between minor releases, and the renderer will look different if the depth path or the sorting strategy changes. If you are building a game on it, pin the version, and re-check the examples/ directory after each upgrade, because the examples double as the documentation of what still works.

Editorial conclusion

Tetra3D suits a Go developer who wants a few 3D objects inside an otherwise 2D Ebitengine game, or a small PS1-scale 3D project, and who is happy with software-rendered output. It does not suit anyone needing modern 3D performance, dense scenes, or accurate depth intersection. Before adopting, read the depth testing note in the README, because turning it off for speed costs correct inter-object intersection, and check the examples/ directory in the repository to see the range of features before committing, since the current release is v0.18.0 from 2026-07-11 while the last push to the repository was on 2026-10-14.

Frequently asked questions

What is Tetra3D?

Tetra3D is a 3D hybrid software and hardware renderer written in Go using Ebitengine, primarily for video games. It is largely implemented in software but uses the GPU for triangle rendering and inter-object depth testing through Kage shaders.

How do I install Tetra3D?

Run `go get github.com/solarlune/tetra3d`. The README states Tetra3D requires Go v1.18 or above, and the module manifest records go 1.24.0. The README notes the 1.18 figure is somewhat arbitrary.

What happens if I turn off depth testing in Tetra3D?

You get a slight performance increase in exchange for no visual inter-object intersection, so objects draw over each other rather than occluding correctly. The README presents this as a deliberate trade for a PS1-style look.

How do I load a model in Tetra3D?

Use tetra3d.LoadGLTFFile with a filepath and loading options, where nil is a valid default, and it returns a *tetra3d.Library and an error. Use tetra3d.LoadGLTFData() when the assets are embedded and the host filesystem is not accessible.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. SolarLune/tetra3d on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/solarlune-tetra3d.svg)](https://hysenlabs.com/projects/solarlune-tetra3d)

Was this useful?