"Written at 5am, drunk, sleep-deprived, and somehow it works."
This engine is no longer in active development.
STUPA Engine GL was a personal learning project built to understand real-time rendering, ECS architecture, physics integration, and engine editor design from scratch. That goal was accomplished.
Development has fully moved to a private Vulkan engine — a ground-up rewrite with proper architecture, modern GPU techniques, and none of the accumulated mess you'll find here. The Vulkan engine is my primary project. This one is archived and open sourced as a learning reference.
What this means practically:
- No new features will be added
- No bug fixes are planned (audio and animation are broken and will stay that way)
- Pull requests may be merged if they fix something critical and don't break anything else, but don't count on it
- Issues will not be actively monitored
If you're here to learn how an OpenGL engine is structured, how ECS works in practice, or how to wire up Bullet3/Assimp/ImGui together — this is a useful reference. If you want a production engine, look elsewhere.
— built for learning. learned. moving on.
A real-time 3D game engine built on OpenGL, born from a personal journey — started as a learning project, survived multiple rewrites, a brief Vulkan detour, a homemade version control tool called VCT, and eventually arrived here: open source, cross-platform, and genuinely usable as a reference.
This is STUPAEngineGL. The OpenGL branch. There is a Vulkan engine too — that one is private, actively maintained, and where all future work lives.
- Background
- Features
- Known Broken / Work In Progress
- Architecture Overview
- Getting Started
- Editor Usage
- Scripting
- Adding Game Scripts to the Build
- Scenes
- External Libraries
- Project Structure
- Platform Support
- Contributing
- License
This engine started as a drunk 5am experiment. The original "version control" was a homemade tool called VCT — hand-rolled, chaotic, and eventually abandoned in favour of just using Git like a normal person. The commit history would tell a story if it wasn't so embarrassing.
At some point a fully separate Vulkan engine was built from scratch, which taught more about GPU architecture in two months than years of OpenGL tutorials. That engine is polished, modern, and private. This one — the OpenGL one — is what you're looking at now.
Why open source it? Because the codebase is good enough to be useful. Because binary releases had zero traction. Because maybe someone else is also building a game engine at 5am and wants a real working reference that isn't a tutorial project.
To the reader: please don't judge the slang. Half of this engine was drunk-written. The other half was written in a fugue state of sleep deprivation. It works anyway.
- OpenGL 3.3 Core Profile renderer
- PBR-style material system — roughness, metalness, emissive, ambient occlusion
- Multi-texture support — diffuse, normal map, roughness/metalness packed map, emissive map
- Environment mapping — HDR and LDR equirectangular skybox
- Screen Space Reflections (SSR) — ping-pong FBO based, configurable strength, step size, max distance
- Shadow mapping — up to 4 simultaneous shadow-casting lights
- Grid overlay — editor mode reference grid
- Wireframe mode toggle
- Viewport FBO — scene renders into an offscreen framebuffer displayed in the editor panel
- Camera preview — secondary camera entities render into a live inspector thumbnail
- Point lights — omni-directional with configurable range and intensity
- Spot lights — cone-shaped with inner/outer cutoff angles
- Directional lights — sun-style with shadow casting
- Up to 4 shadow-casting lights per scene
- Lights bound per-entity via
LightComponent
- EnTT — fast, header-only ECS
- Components:
Transform,Mesh,Material,Collider,Rigidbody,Name,CameraComponent,LightComponent,Animator,ScriptComponent,Parent,Children,LocalOffset,ModelPath,MovementCommand - Hierarchy system — parent/child relationships with local offset preservation
- Drag-and-drop parenting in the scene graph
- Unparent support
- Bullet3 physics integration
- Rigid bodies — dynamic and static
- Colliders — box colliders with offset and extents
- Trigger volumes
- Rotation axis freeze — per-axis X/Y/Z locking
MovementCommandcomponent for character controller style movement (set velocity per-frame)- Physics resets cleanly on play/stop cycle
- Assimp — supports
.obj,.fbx,.gltf,.glb,.daeand more - Multi-mesh model import — parts auto-parented to root entity
- Bone/skinning data imported and stored in
Animatorcomponent - Auto-attach
Animatorcomponent on import for animated formats (fbx, dae, gltf, glb) - Per-mesh material assignment on import
⚠️ Animation is currently broken. The system exists, the importer runs, bone palettes upload to the GPU, and the timeline/graph workspace UI is present — but playback has known issues. Treat it as a work-in-progress foundation, not a shipped feature. PRs welcome.
AnimationSystem— timeline-based clip playback with speed and loop controlAnimationWorkspace— docked timeline editor and graph editor UI panels- Blend weight support (infrastructure exists, not fully wired)
- Play/Pause/Stop controls in inspector and animation panel
⚠️ Audio is currently broken. TheAudioSysteminitialises and the mixer UI renders, but playback reliability is inconsistent across platforms. The bus mixer, asset library, and quick-load test panel are all wired up in the UI — the underlying audio backend needs work. Do not ship audio in production builds using the current state.
STUPA::AudioSystem— miniaudio-backed audio engine- Asset loading and caching by handle
- One-shot and looping playback
- Volume, pitch, randomization options
- Listener position/direction follows editor camera
- Bus mixer UI — Master, SFX, Music, UI buses (UI only, backend routing not complete)
- 3D positional audio infrastructure (not fully implemented)
- Dear ImGui — docking-ready, custom dark theme with rust/teal/gold accent palette
- ImGuizmo — translate/rotate/scale gizmos with world/local space toggle
- Scene Graph — hierarchical node tree, drag-to-parent, inline rename, context menus
- Inspector — transform, material, physics, scripts, animator per-entity editing
- Material Editor — panel with library, per-material surface/texture/shader properties, apply-to-selected
- Animation Editor — docked timeline and graph panels
- Audio Mixer — bus faders, asset library, quick-load test section
- Model Loader panel — path input, scale, physics toggle, import button
- Spawn Pad — quick-spawn buttons for primitives and lights
- Console — runtime log and build log tabs with level-coloured output
- Scene IO panel — save/load with path field, status toast
- Viewport — resizable scene view with camera position overlay and gizmo manipulation
- Camera view panel — live thumbnail of scene cameras
- C++ scripting via
ScriptBase—OnCreate()/OnUpdate(float deltaTime)virtual interface - Scripts registered globally in
RegisterAllScripts()inmain.cpp - Attach scripts to entities at runtime from the editor Inspector
- New script template creation from the editor
- Safe deferred attachment — avoids EnTT sparse set assertion that fired in older versions
- Scripts survive play/stop cycle (re-initialized on each play)
- Save/load scenes as JSON (via nlohmann/json)
- Editor camera position saved/loaded alongside scene (
.editorcam.jsonsidecar) - Material library saved/loaded as
.matlib.jsonsidecar - Scene path configurable at runtime
- CMake — cross-platform, supports Linux and Windows
- Editor build —
main/main.exe - Game build —
game/game.exe(stripped of editor UI, scene path baked in) GameBuilder— in-editor build and launch buttons (calls CMake/compiler under the hood)- Output lands in
windows_build/orlinux_build/depending on platform setup_deps.sh— bash script, clones and builds all external libraries (Linux/WSL/Git Bash)setup_deps.ps1— PowerShell script, same for Windows (vcpkg/winget OpenSSL support)
- DebugOverlay — F3 toggle for HUD, collider boxes, velocity vectors, entity info, screen-space labels
- Logger — timestamped log levels: INFO, SUCCESS, WARNING, ERROR, DEBUG
- Physics debug visualisation toggle in toolbar
- Play mode indicator with pulsing animation
| System | Status | Notes |
|---|---|---|
| Audio | Initialises, UI renders, playback unreliable. Backend needs work. | |
| Animation | Bone data imports, GPU upload works, playback has known bugs. | |
| Skybox auto-load | 🔧 Partial | Windows path list commented out in main.cpp. Uncomment and adjust paths for your setup. |
| SSR | ✅ Works | Ping-pong FBO approach. Roughness threshold at 0.75. |
| Linux skybox | 🔧 Manual | No auto-discovery on Linux yet. Load via absolute path manually. |
| Multi-material models | 🔧 Partial | Multi-mesh import works, sub-meshes auto-parented. Full per-part material editing via inspector. |
| Audio bus routing | ❌ UI only | Fader values exist, backend routing to buses not wired. |
| Animation blending | ❌ UI only | Infrastructure exists, blend weight not fully wired. |
main.cpp — GLFW window, OpenGL init, main loop
│
├── Renderer — FBO management, geometry, shader programs, camera preview
├── SceneManager — Editor UI (handleImGui), camera input, scene logic, physics orchestration
├── LightingSystem — Shadow pass rendering, light uniform binding
├── AnimationSystem — Clip playback, bone transform palette upload
├── AnimationWorkspace — Timeline + graph editor UI panels
├── AudioSystem — miniaudio wrapper, asset management
├── BulletPhysics — Bullet3 wrapper, rigid body lifecycle
├── ScriptBase / ScriptManager / ScriptSystem — C++ scripting layer
├── SceneSerializer — JSON save/load for scenes, camera, material library
├── ModelLoader — Assimp wrapper, mesh/bone/texture extraction
├── Resources — Texture loading (stb_image)
├── Logger — In-memory log with levels
├── DebugOverlay — Collider/velocity/label debug rendering
├── GuizmoManager — ImGuizmo wrapper
└── InputManager — GLFW input abstraction
ECS data flows through entt::registry. Systems iterate views each frame. Physics, scripts, animations, and hierarchy sync all run in SceneManager::handleSceneLogic() during the main loop.
Windows:
- Git — https://git-scm.com
- CMake 3.20+ — https://cmake.org
- A C++17 compiler — LLVM/MinGW (recommended), MSVC 2022, or MSYS2/MinGW-w64
- PowerShell 5.1+ (included in Windows 10/11)
Install LLVM/MinGW via winget:
winget install MartinStorsjo.LLVM-MinGW.UCRTLinux (Ubuntu/Debian):
sudo apt update
sudo apt install -y build-essential cmake git \
libgl1-mesa-dev libx11-dev libxrandr-dev \
libxinerama-dev libxcursor-dev libxi-dev \
libssl-dev pkg-configLinux (Arch):
sudo pacman -S --needed base-devel cmake git mesa \
libx11 libxrandr libxinerama libxcursor libxi opensslLinux (Fedora):
sudo dnf install -y gcc-c++ cmake git mesa-libGL-devel \
libX11-devel libXrandr-devel libXinerama-devel \
libXcursor-devel libXi-devel openssl-develgit clone https://github.com/yourusername/STUPAEngineGL.git
cd STUPAEngineGLRun the setup script once. It clones and builds all required external libraries into External/ and copies built artifacts into lib/.
Windows (PowerShell):
# Allow local scripts if needed (run once as admin)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
.\setup_deps.ps1Optional flags:
.\setup_deps.ps1 -SkipBullet
.\setup_deps.ps1 -SkipAssimp
.\setup_deps.ps1 -SkipOpenSSL
.\setup_deps.ps1 -HelpLinux / WSL / Git Bash:
chmod +x setup_deps.sh
./setup_deps.shOptional flags:
./setup_deps.sh --skip-bullet
./setup_deps.sh --skip-assimp
./setup_deps.sh --skip-openssl
./setup_deps.sh --helpThe script is idempotent — re-running it skips anything already built.
After running the setup script:
Editor (Linux):
mkdir -p build && cd build
cmake .. -DSTUPA_BUILD_EDITOR=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallel
# Output: linux_build/mainEditor (Windows):
mkdir build; cd build
cmake .. -DSTUPA_BUILD_EDITOR=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release --parallel
# Output: windows_build/main.exeGame build:
cmake .. -DSTUPA_BUILD_GAME=ON -DSTUPA_GAME_SCENE="../Scenes/game.json" -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallelBoth at once:
cmake .. -DSTUPA_BUILD_EDITOR=ON -DSTUPA_BUILD_GAME=ON -DCMAKE_BUILD_TYPE=Release
cmake --build . --parallelFor detailed build options see BUILD.md.
The viewport is in the centre panel. Right-click and hold to enable camera movement:
| Input | Action |
|---|---|
| Right Mouse + W/A/S/D | Fly camera |
| Right Mouse + Shift | Sprint (3× speed) |
| Right Mouse drag | Look around |
| T | Translate gizmo |
| R | Rotate gizmo |
| S | Scale gizmo |
| F3 | Toggle debug overlay |
The left panel lists all entities in the scene hierarchy.
- Click an entity to select it
- Double-click to rename inline
- Drag one entity onto another to parent it
- Right-click for: rename, focus, duplicate, unparent, add animator, edit animation, remove
- Root-level entities without a
Parentcomponent appear at the top level - Child entities are indented under their parent with a tree arrow
The right panel shows components for the selected entity.
Transform — position, rotation (Euler degrees), scale. Editable via drag sliders. Gizmo manipulation in viewport updates these values live.
Material — base colour, roughness, metalness, emissive intensity. Texture slots for diffuse, normal, roughness/metalness packed, and emissive maps. Set a path and click Set to load. Click X to clear.
Physics — add/remove Collider (offset, extents, trigger flag) and Rigidbody (mass, gravity, kinematic, freeze rotation axes). Static and Dynamic add buttons.
Scripts — attach registered C++ scripts to the entity. New Script creates a template file. Attach Script opens a selector of all registered scripts.
Animator — clip list count, playback speed, loop toggle, current clip name and scrub bar. Play/Pause/Stop controls. "Open Anim Editor" docks the animation workspace panel.
Left to right:
- T / R / S — gizmo mode (translate/rotate/scale)
- Wld / Loc — world/local space toggle
- Grid / Wire / Phys — viewport overlays
- Focus — snap camera to selected entity
- Reset — reset camera to default position
- Anim / Mat / Audio — toggle overlay panels
- Play/Stop — enter and exit play mode
- Build / Launch — compile game build and run it
- F3 Debug — toggle debug overlay
Clicking ▶ Play:
- Snapshots all Transform components
- Resets the physics world
- Re-initialises all scripts (calls
OnCreate) - Starts animation playback on any Animator with a clip selected
- Enables the debug overlay HUD
Clicking ■ Stop:
- Restores all Transform snapshots
- Resets physics
- Pauses and rewinds all animators
- Re-initialises scripts (ready for next play)
- Hides the debug overlay HUD
Scripts, physics, and animations only run in play mode. The scene graph and inspector remain editable in edit mode.
The Build button in the toolbar:
- Saves the current scene to
Scenes/build_game.json - Invokes
GameBuilder::BuildGame()which runs the CMake game build - Displays build output in the Build tab of the console panel
- Sets the launch button active if build succeeded
The Launch button runs windows_build/game.exe or linux_build/game with the built scene path.
You can also build manually from the command line — see BUILD.md.
Scripts are C++ classes that inherit from ScriptBase. They get OnCreate() called once when play mode starts, and OnUpdate(float deltaTime) every frame.
1. Create a script file (or use the editor New Script button):
// scripts/MyScript.h
#pragma once
#include "ScriptBase.h"
class MyScript : public ScriptBase {
public:
void OnCreate() override;
void OnUpdate(float deltaTime) override;
};// scripts/MyScript.cpp
#include "MyScript.h"
#include "components.h"
void MyScript::OnCreate() {
// runs once when play starts
}
void MyScript::OnUpdate(float dt) {
auto& t = GetComponent<Transform>();
t.pos.y += dt * 2.0f; // float upward
}2. Register it in main.cpp:
void RegisterAllScripts() {
auto& sm = ScriptManager::Instance();
sm.RegisterScript<MyScript>("MyScript");
}3. Attach it to an entity from the Inspector Scripts section, or from code:
auto& sc = registry.get_or_emplace<ScriptComponent>(entity);
auto script = ScriptManager::Instance().CreateScript("MyScript");
script->SetEntity(entity, ®istry);
sc.scripts.push_back(std::move(script));
sc.scriptPaths.push_back("MyScript");Scripts can access sibling components via GetComponent<T>() (provided by ScriptBase). They can also call GetEntity() to get their own entt::entity handle.
Game scripts must be compiled into the game binary. In CMakeLists.txt, find the game target section and add your .cpp files:
target_sources(game PRIVATE
${CMAKE_SOURCE_DIR}/scripts/MyScript.cpp
${CMAKE_SOURCE_DIR}/scripts/PlayerController.cpp
)Then rebuild:
cmake --build . --config Release --parallelScenes are saved as JSON files. The serializer saves all entities with their components. Three files are written when saving:
| File | Contents |
|---|---|
scene.json |
All entities and components |
scene.editorcam.json |
Editor camera position and orientation |
scene.matlib.json |
Material library (all materials created in Material Editor) |
Load and save from the Scene panel (bottom right), the File menu, or Ctrl+S / Ctrl+O.
The game build bakes in the scene path at compile time via -DSTUPA_GAME_SCENE=.... At runtime the game loads that scene directly without any editor UI.
All external libraries live in External/ and are managed by the setup scripts. None are vendored into this repository — the scripts clone and build them on first run.
| Library | Version | Purpose |
|---|---|---|
| GLFW | 3.4 | Window creation, OpenGL context, input |
| glad | — | OpenGL function loader (bundled in src/) |
| GLM | — | Math (bundled in include/) |
| EnTT | latest | Entity Component System |
| Dear ImGui | docking | Editor UI |
| ImGuizmo | latest | 3D transform gizmos |
| Assimp | v5.4.3 | Model loading |
| Bullet3 | 3.25 | Physics simulation |
| nlohmann/json | latest | Scene serialization |
| OpenSSL | 3.x | Networking / crypto (future use) |
| stb_image | — | Texture loading (bundled in include/) |
STUPAEngineGL/
│
│ — Root files —
├── .vctignore — Legacy: ignore file for VCT (old homemade version control)
├── CMakeLists.txt — Cross-platform build (Linux + Windows, editor + game targets)
├── BUILD.md — Detailed build instructions for both platforms
├── Quickstart.md — Short version: clone → setup → build → run
├── setup_deps.ps1 — Windows: clone + build all external libs (PowerShell)
├── setup_deps.sh — Linux/WSL/Git Bash: same, for Unix
│
├── src/
│ ├── main.cpp — Editor entry point
│ ├── game_main.cpp — Game entry point (no editor UI)
│ ├── glad.c — OpenGL function loader
│ ├── Engine/
│ │ ├── renderer.cpp/.h — FBO, geometry, camera preview
│ │ ├── camera.cpp/.h
│ │ ├── components.h — All ECS component structs
│ │ ├── inputManager.cpp/.h
│ │ ├── logger.cpp/.h
│ │ ├── modelloader.cpp/.h — Assimp wrapper
│ │ ├── resources.cpp/.h — Texture loading (stb_image)
│ │ ├── sceneserializer.cpp/.h — JSON save/load
│ │ └── DebugOverlay.cpp/.h — F3 HUD, collider boxes, velocity vectors
│ ├── UI/
│ │ ├── scenemanager.cpp/.h — All editor UI (~3000 lines, known mess)
│ │ ├── imguiManager.cpp/.h
│ │ └── guizmoManager.cpp/.h — ImGuizmo wrapper
│ ├── Shaders/
│ │ └── shaders.cpp/.h — Inline GLSL (PBR, depth, sky, simple)
│ ├── Physics/
│ │ └── bulletPhysics.cpp/.h — Bullet3 wrapper
│ ├── Lights/
│ │ └── LightingSystem.cpp/.h — Shadow passes, light uniform binding
│ ├── Animation/
│ │ ├── AnimationImporter.cpp/.h
│ │ ├── AnimationSystem.cpp/.h — ⚠️ Broken
│ │ ├── AnimationWorkspace.cpp/.h
│ │ ├── AnimationWorkspace_Timeline.cpp
│ │ └── AnimationWorkspace_Graph.cpp
│ ├── Audio/
│ │ └── audioSystem.cpp/.h — ⚠️ Broken
│ └── ScriptBase/
│ ├── ScriptBase.cpp/.h
│ ├── ScriptManager.h
│ └── ScriptSystem.h
│
├── scripts/ — User game scripts (.cpp/.h files go here)
├── include/ — GLM, stb_image, glad headers
├── lib/ — Built library outputs (GLFW dll/lib/a)
├── External/ — Auto-populated by setup scripts
│ ├── entt/
│ ├── json/
│ ├── imgui/
│ ├── gusmo/ — ImGuizmo
│ ├── glfw/
│ ├── assimp/
│ ├── bullet3-3.25/
│ └── ssl/
├── Assets/ — Textures, models, audio files
├── Scenes/ — JSON scene files + sidecars
├── windows_build/ — main.exe / game.exe (Windows)
└── linux_build/ — main / game (Linux)
This project is archived. That said, if you fix something real and submit a clean PR, it may get merged.
- Audio and animation are the two most broken systems. If you fix either, that's the most valuable possible contribution.
scenemanager.cppis enormous and messy. Splitting it into smaller files would be welcome.- The codebase mixes human-written and AI-assisted code — some files have header comments labelling which is which. Don't be surprised by the tone differences.
- Please do not remove the original file header comments. They're part of the history of this project.
- Don't expect responses to issues or active review of PRs. This is an archive.
| Platform | Compiler | Status |
|---|---|---|
| Windows 10/11 | LLVM/MinGW (UCRT) | ✅ Tested |
| Windows 10/11 | MSVC 2022 | ✅ Should work |
| Ubuntu 22.04+ | GCC 11+ | ✅ Tested |
| Arch Linux | GCC / Clang | ✅ Tested |
| WSL2 (Ubuntu) | GCC | |
| macOS | — | ❌ Not supported, not planned |
Skybox note: The auto-discovery path list in
main.cppis commented out. On Linux, uncomment the vector and adjust paths for your setup, or load via absolute path. This was never fixed and won't be.
MIT License. See LICENSE for details.
Use it, fork it, learn from it. That's the point.
| File | Purpose |
|---|---|
CMakeLists.txt |
Build system — Linux + Windows, editor + game |
setup_deps.sh |
Unix: clone + build all external libraries |
setup_deps.ps1 |
Windows: same, PowerShell |
BUILD.md |
Full build instructions for both platforms |
Quickstart.md |
TL;DR version of BUILD.md |
.vctignore |
Relic from VCT (old homemade version control) |
Started at 5am. Survived VCT. Survived the Vulkan rewrite. Open sourced, archived, done. The Vulkan engine is where I live now. — sleep driven raccoon