glTF 2.0 Sponza sample rendered in Palace.

Over the summer I’ve focused my time on developing a real-time Vulkan renderer that utilizes some of the modern features presented in C++20. This post is a summary of some of the context around dependency selection, learning resources, and some design choices. I won’t be covering many implementation details - Vulkan is a powerful and highly configurable modern graphics API, and covering the details would involve re-hashing hundreds of lines of boilerplate that’s better covered by the excellent tutorial resources in the references section.

Source code available on GitHub: roblesch/palace

Table of Contents

Catching Up

It’s been a busy few months. I finished my Directed Research project! We shipped Grandma Green! I graduated!

After taking some time off, I was eager to jump into a project that’s been burning in the back of my mind for some months now - each of the renderers I have written so far have been research-oriented software renderers that take no advantage of the GPU. Render times are painfully slow, and I don’t have the opportunity to learn any standard tooling. I want to write a more standard rendering system that I can continue to extend over the years with whatever new techniques I may find.

After a couple months of work I think I’ve got something worth sharing. Behold, Palace. Currently Palace supports loading glTF models with simple Blinn-Phong shading and directional shadow maps. In this post I’ll give an overview of development so far, and some thoughts on where I might go next. A deep technical explanation of the engine would be a poor recreation of the fantastic tutorial resources made available by the graphics community, so instead I’ll focus more on the choices unique to this project.

Link to repository: roblesch/palace

Why Vulkan?

Assisi - City Scene rendered in Palace.

I split my development time between Windows and Mac so DirectX 12 and Metal were less appealing. OpenGL is an obvious choice, but I’ve already got a bit of experience with it and would really like to learn something new. WebGPU is an interesting option but seems a bit niche. Eventually I’d like to implement backends for all of them, but I picked Vulkan to start.

Vulkan is a modern graphics API developed by the Khronos Group with the intent of offering higher performance and more efficient device usage compared OpenGL. Vulkan is appealing for its wide support (who knew you can run Vulkan on a Nintento Switch?), active community and abundant examples.

Dependencies

I use the LunarG Vulkan SDK on both my PC and Mac. It includes MoltenVK for Mac compatibility.

I did a quick run-through of Alexander Overvoorde’s Vulkan Tutorial to get a feel for the C API and ended up deciding to try the C++ API for this project. Typed enums and not having to fill in sType myself are appealing enough, but the UniqueHandle interface is what I was interested in trying. UniqueHandles are basically unique pointers with the same considerations. Vulkan-Hpp provides unique handle constructors for each function that creates a vulkan handle, and initializing unique handles for types not constructed by vulkan (e.g. application window) are straightforward. There is some overhead to using UniqueHandles, but for now the convenience outweighs the (currently negligible) costs. I chose to configure Vulkan-Hpp with no constructors and no setters, and initialize all classes by designated initializers. I found this configuration struck a good balance of clarity and conciseness.

I chose SDL2 over something like GLFW in the spirit of learning and for its reputation as a more robust cross-platform I/O layer. I use glm, stb and ImGui for convenience and familiarity. Similarly tinygltf was selected for its easy-to-use API. The general theme is to worry about performance when it’s measurably degraded. Finally I use Vulkan Memory Allocator (vma) for some help managing buffers and images. So far the benefits over my first non-vma implementation aren’t too significant, but I have a feeling I might be more excited about vma when I target more devices.

Current Features

glTF 2.0 chess set sample rendered in Palace.

Common vulkan tasks (initialization, device selection, swapchain creation, …) are implemented as provided in the listed tutorials (with vulkan-hpp instead of the C API and sdl2 swapped in for GLFW) but I branched out for a few features.

VMA wrapper

VMA simplifies creation of images and buffers, but it uses the C API for Vulkan and it’s a little clunky mixing the C/C++ API in the codebase. I have a small wrapper around VMA that handles all the C calls internally and manages memory cleanup so the other source won’t have to mix API styles.

glTF Meshes

I support glTF meshes with an implementation that loosely follows Sascha Willems’s glTF scene rendering example. I parse the glTF scene nodegraph into a custom scene format and compute the hierarchical transformation matrix.

Materials

I have yet to implement glTF PBR materials, so for now I just pull the texture & base color from the metallic roughness material and use the mesh normal textures to do simple Blinn-Phong shading. Each material has a color texture & normal texture that wrap the underlying vulkan image, sampler, descriptors, etc.

Shadow Mapping

The most recent feature was directional shadow mapping, implemented from a mix of Sascha Willems’s example and the Learn OpenGL tutorial. The best part of this one was realizing my shadow maps were upside down.

Next Steps

Only a couple months in and there are already some cobwebs. Top of mind are to improve the organization of descriptor sets, and calculate hierarchical model transforms for every node once instead of on each frame. I’ve jammed too many things into my push constants because it was an easy way to get things off the ground.

Scene scales are an issue, I’d like to resize models to a normalized size so I don’t have to mess with the camera and lights as much for each model. A simple skybox would go a long way as well, and I could probably make progress towards specular IBL at the same time.

Closing Thoughts

After all the chaos of school, it was nice to have some time to focus on just one thing! I’m enjoying working in Vulkan a lot so far, and I’m looking forward to continuing on this project. Maybe I’ll return to my roots and work towards some real-time ray tracing?

Bloopers

Here’s some screenshots of the ups and downs -

an avocado and some familiar looking cubes
neon sponz
moireposting
why are those upside down?
this is fine.

References & Resources

Alexander Overvoorde, VulkanTutorial

Victor Blanco, vulkan-guide

Sascha Willems, Vulkan

Joey De Vries, Learn OpenGL

Graphics Programming Discord