Blightspire
Blightspire engine is a custom engine made in Vulkan, aimed to facilitate creating
the wave-based arena shooter Blightspire inspired by games like Quake, CoD zombies and Ultrakill.
This project was made as part of the Game Programming
course at BUAS with a team of 8-11 programmers. designers and artists over the course of 8 months.
Our project is open source, so feel free to take a look at it on GitHub. Our game
is also available for free on Steam!
Graphics Programmer
Tools Programmer
1 Producer/Designer
1 Artist
7 Graphics Programmers
2 Engine Programmers
8 months
Windows
Steam Deck
Custom Engine
Vulkan
ENTT
Jolt Physics
Wren Scripting
Main responsibilities
Throughout the past half year of development, I've been solely responsible for the GPU based particle system and its editor. It was inspired
by Wicked Engine's particle system and is currently functional, but still in progress.
I also implemented screen space decals into our deferred renderer to further enhance our game feel.
Aside from working on graphical and engine features, I also picked up and helped with other tasks in various fields, like production,
design and art, to help fill in for the lack of people specializing in those fields in our team. These tasks included organizing
and conducting playtests, giving internal feedback on level and gameplay design and helping with some UI art and design.
Particles
One of the biggest struggles I faced when implementing the GPU based particles was that I had to learn Vulkan,
a modern graphics API. Before this project I only had some experience with OpenGL and the PS5 graphics API.
By far the hardest things to set up for the particles were the buffers and all the descriptor sets and layouts.
I eventually managed to set-up everything I needed for the particle system by reverse-engineering existing graphics
passes in our engine while reading up on Vulkan/modern graphics API concepts in several tutorials (mostly at vulkan-tutorial.com").
Another hurdle I encountered about 3 months in, was that I lost the original vision for our particles both visual wise,
customizability wise and behavior wise. This was also due to being in my own bubble for those 3 months
being the sole person working only on the particle system. To take a step back and get a better view of the state of the system
and what was still needed, I sat down together with another team member to try and create various particle effects
(e.g. environmental fire/dust and particles when shooting the gun). This helped greatly with identifying missing functionality
and/or customizability for the particles.
The GPU based particle system is utilizing 3 compute passes and a rendering pass separate from our uber render pass
to emit, simulate and render the particles. This is heavily inspired by Wicked Engine's particle system,
adjusted to fit to our game's needs.
The system uses several storage buffers to keep track of the amount of particles, indices to particles that are alive and dead
and the particle's data. A uniform buffer is used to copy over emitter data to the emit compute stage, where it then spawns new
particles by writing these into the related storage buffers. There is another storage buffer where all particles instances to
be rendered are written to that is used by the DrawIndexedIndirect render pass.
The particle behavior is fairly simplistic as of now with movement using velocity calculated with the particle's assigned mass,
starting velocity and some randomness. Particle size and rotation can also be simulated. The particle rendering
only supports billboard particles with sprite sheets (if desired) to aid our more stylized look.
C++ code snippet of dispatching the emitter compute stage from particle_pass.cpp
The logic for spawning emitters is still handled on the CPU using the ECS. Emitters are components and can be
spawned on an entity, with parameters from an EmitterPreset, and take the velocity/position of other components
of that entity, including from a Jolt rigid body. The SpawnEmitter function used to spawn emitters with is also
already integrated into our scripting using wren.
The EmitterPresets are also editable in engine. This supports live EmitterPreset editing being reflected on spawned emitters and
serialization of EmitterPresets. This editor is functional as of now, but still being iterated and improved upon with feedback received
from my team. Some particle effects made using this editor can be seen in videos above and on the left.
As of now 121.000 particles take about 3.396ms on PC. The only major bottleneck I've recognized so far
is the rendering of the particles, in this case taking about 3.148ms. Optimizing this is still on our backlog,
but since it doesn't impact us as of yet and there's bigger bottlenecks to be handled, it hasn't been prioritized.
C++ code snippet for caching/loading images for emitter presets from particle_module.cpp
Screen Space Decals
To help enhance our game feel, I implemented screen space decals, currently in our lighting pass in our deferred renderer.
For my implementation I followed Warhammer 40k: Space Marine's screen space decals.
Since they're screen space, they can only be spawned on static meshes and in our game are used dynamically, with the decal buffer
being a ring buffer wrapping around once the spawned decals hit the max amount.
Per pixel per decal, it transforms the pixel's position to decal 'box' space and checks if the pixel is within its 'box'.
If it is, it will check the pixel's normal against the decal's orientation to avoid artifacts called side stretching. If
they're pointing to a similar enough direction, decided by an adjustable threshold value, the albedo is fetched from the decal
image to be mixed with the pixel's albedo. Since so far our models don't use (noticable) normal maps and our visuals are rather stylized,
it saved us on an extra calculation for offsetting the pixel's normal with the decal's normal map.
Currently, the performance is less than ideal since I'm calculating for every decal at every pixel. Optimizations are on
the schedule for this, most likely in the form of a separate compute stage and/or utilizing the clusters currently in place
for our clustered lighting.