top of page

LEVEL RENDERER

This Level Renderer is a solo project I created using the Vulkan graphics API. This project was created during my time at university, it took 1 month (December 2021) to complete.

Summary

This project uses Vulkan to parse and render game levels created using Blender (3D graphics software). I created/modeled the levels using Blender and used a python script to convert the level data (ex. object matrices) from Blender into a .txt file. I then used Visual Studio, Vulkan, and C++ to parse that data from the .txt files and render them to the console using their correct locations, colors, and materials. I achieved this using a mix of Data-Oriented and Object-Oriented Programming.

Level 1_Point Light.PNG

TRY IT OUT!!!

*** If you want to run the project and test it out for yourself, consider the link(s) below. ***

Project Build: https://mctigerr17.itch.io/vulkan-level-renderer

MY ROLE | What I Worked On

Pipeline Creation

- Descriptor Sets

-Binding Descriptions

​

File-IO

- Extracting Level Information

Storage/Constant Buffers

- Weapon functionality; crossbow and magic staff
- Player abilities; Fireball, Lightning

- Item pickups; artifacts, health, mana, and antidote

HLSL

- Write Shader Code

- Program Lighting

- Linking Constant buffer

- Linking Storage buffer

STORAGE & CONSTANT BUFFER

Storage and constant buffers are used to send information to the vertex and pixel shaders. This information includes object matrices, the view matrix, the projection matrix, object materials, lighting, etc. 

Push Constant (C-Buffer)

In Vulkan, push constants allows us to quickly and effectively push/send small amounts of uniform data to shaders. They are fast but have a big size limitation. The minimum size that Vulkan requires is 128 bytes. In my case, I used push constants for the world matrices of each mesh and their mesh Id. The mesh Id is used for assigning the correct mesh information (color,  materials, etc.) to the its corresponding object 

C-Buffer.PNG
C-Buffer_struct.PNG
RenderEachFrame.PNG
Storage (Structured) Buffer

Storage buffers allows to pass a large amount of data/information to the shaders.

Although they are slower than uniform/constant buffers, storage buffers can be significantly bigger in size. In this project, I used a storage buffer to store the data of all the objects in the scene (matrices, materials). The storage buffer also contained lighting information (direction, position, color, ambient term, etc.) and viewing information (view & projection matrix). The shaders get a similar structure that aligns with the original, and the syntax lets the computer know to expect a structured buffer. The shaders will use the data to perform their calculations when drawing the frames.

StorageBuffer_C++.PNG

C++ Version of Storage buffer

StorageBuffer_Shader.PNG

HLSL Version of Storage Buffer

PIPELINE CREATION

Creating the pipeline was probably the most important of the project. It's crucial to get this right, otherwise, nothing else will work.

Descriptor Sets

Descriptor Sets were one of the most challenging aspects of this project, the reason being that you need to get everything right. 

Like Push Constants, Descriptor Sets are used for uploading data from the CPU to the GPU. The difference is that descriptor sets allow us to do things that aren't possible using Push Constants. For example, pointing to buffers, uploading arrays, and using textures are all made possible with the use of Descriptor Sets. 

Binding Descriptions

Binding descriptions are used to tell Vulkan how to pass data to the vertex shader once it has been uploaded into GPU memory. Vertex binding describes at which rate to load data from memory throughout the vertices. It specifies the number of bytes between data entries and whether to move to the next data entry after each vertex or after each instance.

In my case, I used binding and attribute descriptions as a way to describe my vertex structure to the shaders. For example, I describe how many elements to expect and the size of each element.

VertexStruct.PNG
VertexBinding.PNG

HLSL (High-Level Shading Language)

HLSL was important for writing the shader code. This was also used to make sure storage & constant buffer data were linked up properly and in the correct order.

Vertex Shader

The vertex shader is used to transform object vertices. My vertex shader uses the structured and constant buffers to access the different matrices (world, view, projection) and material id for every mesh in the scene. The vertex shader takes the world position, multiplies it by the view matrix, then multiplies that result by the projection matrix to convert vertices into screen space.

ShaderFunctions.PNG
VertexShader.PNG
Pixel Shader

The pixel shader makes everything look pretty.
My pixel shader was used for lighting calculations. The level renderer supports two types of lighting, daytime, and nighttime lighting. When the 'M' key is pressed the pixel shader will render objects using the Directional Lighting calculations. When the 'N' key is pressed, point light calculations will be used to simulate a nighttime setting.

In order to perform these light calculations, the pixel shader uses the structured buffer to access information about each mesh (diffuse, sharpness, illumination, etc.)

PixelShader1.PNG
PixelShader2.PNG

FILE-IO

File IO was used to extract object matrices.
The matrices of the camera, lights, and objects were all converted and stored into a .txt file using a provided python script. This text file was used for getting each matrix and creating instances of them in C++ code 

Extracting Object Info

Surprisingly, extracting object matrices was one of the most challenging processes of this project. This was because extracting this information had to be done in a very specific way. Each matrix is described using the "MESH" keyword. Below the mesh is the name of the object. If there are multiple copies of the object, the name will be followed by a number (This number will have to be ignored when looking for the model). There is a model for each object located in a different folder, the 3D model will have the same name as the object.

Now for the hard part. Under the name of the model, there are 4 lines representing the matrix of the object in world space. This data needed to be extracted and converted to a matrix object (struct) in code, that struct will be part of a mesh object and then stored in a vector of other meshes.

​

I created a mesh class that will be used for each mesh info that is extracted from the txt file. The mesh class contains field members for the world matrix, vertex count, index count, materials, etc.

​

The constructor of the mesh class takes in a string parameter for the name of the mesh. it uses this name to locate the 3D model inside the assets folder. Once this model is found the parser is used to get information about the 3D model (vertex count, index count, amount of materials, batches, etc.

​

MeshClass.png

Object Mesh Class

File-IO_Txt.JPG

Level Objects .txt File

File-IO: Retrieving Matrix Info

bottom of page