Screenshots
Here are some screenshots.
Workflow
Here is the high-level workflow of the app:

Rendering Features
The renderer is implemented by Vulkan, with GLSL for shaders.
Deferred & Forward Rendering Pipeline
Very basic deferred & forward pipeline.
- Shadow Maps (Depth Only)
- Render shadow-casting objects to shadow maps
- Deferred Pass (GBuffer + Lighting)
- Render ever opaque object
- Render skybox
- Forward Pass
- Share depth buffer with Deferred Pass
- Combine deferred buffers and calculate PBR Lighting
- Render transparent objects (But for simple testing, I only used a opaque Castle, and it looks the same as the deferred rendered one)
- Post Processing
- FXAA
- ** UI Pass**
- ImGui UI
Based Rendering & Image Based Lighting
Inspired by Learn OpenGL, I implemented Physically Based Rendering (PBR) and Image-Based Lighting (IBL) to achieve realistic rendering results.

Fast Approximate Anti-Aliasing
Based on NVIDIA FXAA - TIMOTHY LOTTES, I integrated FXAA (Fast Approximate Anti-Aliasing) into my renderer, achieving a fast and visually pleasing anti-aliasing effect.
Other Features
Thread Pool
To improve loading performance, I implemented a thread pool to handle parallelized asset loading. This allows multiple assets (such as textures, meshes, and shaders) to load concurrently, reducing startup time and improving responsiveness.
Below is a snippet of the critical part of the worker thread logic:
void ThreadPool::Worker() {
while (true) {
std::function<void()> job;
{
std::unique_lock lock(m_mutex);
// Wait for an available task
m_cv.wait(lock, [this]() { return !m_tasks.empty() || m_stopped; });
// Exit when
// 1) Thread pool is shutting down and all tasks are finished
// 2) Explicitly asked to stop
if (m_stopped && m_tasks.empty()) {
return;
}
// Fetch one task
job = std::move(m_tasks.front());
m_tasks.pop();
}
job();
m_pendingTasks.fetch_sub(1, std::memory_order::memory_order_acq_rel);
if (m_pendingTasks.load(std::memory_order_relaxed) == 0) {
m_idleCv.notify_all();
}
}
}
Shader System
I implemented a runtime GLSL-to-SPIR-V compilation system using glslang. And I also integrated shader reflection with SPIRV-Reflect, allowing the engine to automatically extract descriptor bindings and push constants.
To Improve
During development, I identified several areas for improvement:
- The current logging system uses SDL_Log, which could be replaced with a more structured and formatted logging framework.
- Vulkan objects currently require manual destruction. This approach is error-prone, and adopting a resource management pattern (such as reference counting or RAII wrappers) would make the system safer and more maintainable.
I am addressing these issues and exploring improved designs in my next project, VRez-RT.