This project is still in progress
VRez-RT started as a real-time ray tracing renderer. After reading
Game Engine Architecture, the scope expanded significantly.
This project is now both:
- A major upgrade to VRez
- A foundation for a full-featured game engine
The goal is to integrate a modern PBR rendering pipeline alongside hardware-accelerated ray tracing, and use this unified rendering system as the core of the engine.
Rasterization
The rasterization path is based on the deferred and forward PBR rendering pipeline previously implemented in VRez.
Data Oriented Design (WIP)
While object-oriented design (OOD) is still widely used, modern game and rendering engines increasingly adopt data-oriented design (DOD) along with bindless descriptors to reduce CPU overhead. This becomes especially important when thousands of entities in a scene require rendering (or even frequent logic updates). As a result, I transitioned from a traditional OOD structure to a more data-driven workflow.
To improve flexibility when moving resources in memory, I also represent meshes, materials, and other assets using lightweight handles (a simple uint32_t for now) rather than raw pointers.
Below is a simplified structure of the render system:
class SceneRenderer{
public:
void Render()
private:
std::vector<glm::mat4> m_entityTransforms{};
std::vector<MeshHandle> m_entityMeshes{};
std::vector<MaterialHandle> m_entityMaterials{};
......
}
Instead of calling entity->Draw() for every object, the renderer groups and sorts entities based on their mesh data, then issues batched vkCmdDrawIndexed calls. Materials are uploaded as an array at the beginning of the render pass, and push constants are used to pass the index of the active texture into the shader. This approach reduces cache misses and minimizes expensive draw and binding operations, resulting in improved rendering performance.
This design is still evolving as implementation details are refined, but the same data-oriented pattern will also be extended to other engine systems in the future.
Ray Tracing (WIP)
Workflow
Below is a brief workflow (subject to updates).

Physics (TODO)
Planned integration of NVIDIA PhysX for:
- Rigid body simulation
- Collision detection
- Physics queries
Scripting (TODO)
Planned support for Lua scripting to enable:
- Gameplay logic
- Rapid iteration
- Tool-side customization
Improvement
Here are some improvements compared to VRez.
GLTF and Scene Resource
Instead of using obj format for meshes, I switch to use gltf, which provides a more compact file format and built-in support for PBR materials.
Below is the resource management between gltf files and sence resource. The system is designed to support the DOD architecture (subject to updates).

Frames In Flight
VRez only uses a single frame of rendering context and relies on strict CPU–GPU synchronization. As a result, the CPU often has to stall while waiting for the GPU to finish processing the current frame before it can begin preparing the next one. This can significantly reduce overall throughput and introduce unnecessary idle time.
Modern rendering and game engines typically adopt a double-buffering (or multi-buffering) approach, where the CPU can begin updating resources for frame n+1 while the GPU is still consuming resources for frame n. In Vulkan, this technique is commonly referred to as frames in flight.
I integrated this pattern into the project to improve parallelism between the CPU and GPU, reducing stalls and achieving smoother frame pacing.
Reference-Counted Vulkan Object Management
Because Vulkan objects must be destroyed manually and in a specific order, I designed a reference-counted wrapper that automatically manages object lifetimes.
template<typename Handle, class Deleter>
struct VkRcObject {
std::atomic<uint32_t> m_refCount{1u};
Handle m_handle{};
Deleter m_deleter;
VkRcObject(Handle handle, Deleter deleter)
: m_handle(handle)
, m_deleter(std::move(deleter)) {}
};
template<typename Handle, class Deleter>
class VkRc {
public:
VkRc() noexcept = default;
static VkRc MakeVkRc(Handle handle, Deleter deleter) {
if (handle == VK_NULL_HANDLE) {
return {};
}
return VkRc(new VkRcObject<Handle, Deleter>(handle, std::move(deleter)));
}
VkRc(const VkRc &other) noexcept
: m_object(other.m_object) {
this->IncreaseRef();
}
VkRc(VkRc &&other) noexcept
: m_object(other.m_object) {
other.m_object = nullptr;
}
VkRc &operator=(const VkRc &other) noexcept {
if (this != &other) {
other.IncreaseRef();
Release();
m_object = other.m_object;
}
return *this;
}
VkRc &operator=(VkRc &&other) noexcept {
if (this != &other) {
Release();
m_object = other.m_object;
other.m_object = nullptr;
}
return *this;
}
~VkRc() { Release(); }
[[nodiscard]] const Handle &GetHandle() const noexcept {
if (m_object) {
return m_object->m_handle;
}
return {};
}
private:
VkRcObject<Handle, Deleter> *m_object = nullptr;
explicit VkRc(VkRcObject<Handle, Deleter> *object) noexcept
: m_object(object) {}
void IncreaseRef() {
if (m_object) {
++m_object->m_refCount;
}
}
void DecreaseRef() {
if (m_object) {
--m_object->m_refCount;
}
}
void Release() {
if (!m_object) {
return;
}
DecreaseRef();
if (m_object->m_refCount == 0) {
if (m_object->m_handle != VK_NULL_HANDLE) {
m_object->m_deleter(m_object->m_handle);
}
delete m_object;
}
m_object = nullptr;
}
};
Better Logging
I integrated spdlog for structured logs with timestamps and levels, replacing SDL_Log.

Better Shader Lanaguage
I migrated the project to use Slang, a modern shader language with a more expressive and ergonomic design. Compared to GLSL, Slang offers improved usability, better abstraction support, and a cleaner workflow for cross-platform shader development.