Creating the Material System
Wed 06 October 2021The need for a material system
The concept of shaders was not very developed in my head when I first set out into learning graphics programming. I thought: "oh, if I put vertices on the screen, I should be able to draw the figure represented by those vertices!" I then learned that sure, I could put some vertices into memory, but there was a very specific combination of things I had to do to properly display these.
It reminds me of the time my English teacher from tenth-grade tried to make a peanut butter and jelly sandwich using our advice. It was a simple task, but All was well until someone said to stick the knife in the peanut butter. That person's command wasn't specific enough, and my teacher began to force the knife into the peanut butter jar from underneath. That was probably my favorite memory from that class.
Anyway, you get the point. Some simple tasks may not be so simple with these libraries. Some things are implicitly done in OpenGL that have to be explicitly done in Vulkan. It took all the way until the time I wanted to load a model with multiple textures, that I finally figured out what the objects I created in my Vulkan renderer were actually doing. After some googling, I tried a naive approach, which was to simply make more descriptor sets for the textures and rebind those for each mesh, which gave me an atrocious shader error. I had to do some more complicated things.
Finally, I figured out that the shader code had to be updated, and the descriptor set layouts have to be allocated in a different way. It took some time to put it all together, but after battling validation layer errors and segmentation faults, I finally produced:
The first attempt
Woah, it's perfect! Hell, you can almost tell it's a house! Jokes aside, I knew there must have been an off-by-one error at some time when I was binding indices. I eventually found where that was, and then the crisis was averted:
Attempt two
Ah, there we go. Ignore the lack of MSAA or mipmaps, I hadn't developed the renderer to that point yet. Please also ignore the texture beneath the house, I needed an image for a missing texture, and it was the first image I found in the pictures folder of my computer.
With the multiple textures out of the way, it was time for making a material system where I can load in multiple shaders to be used for various things, such as skyboxes for example. You can tell it looks pretty boring with the pitch black sky.
After looking at a few material systems, I think the best system for my engine is one where basic shaders are defined as classes, and those classes are initialized at the program's start. In addition, I added a pipeline builder and descriptor set layout builder to automate reconstruction of objects associated with the shader (i.e. the uniform bufffer descriptor set layout needs to be recreated if the window is resized.)
The new shaders are allocated like so:
Shader initialization
// Setup built in shaders
BaseShader* basic_3d = CreateShader<Basic3D>( "basic_3d" );
The Basic3D
shader does what I initially thought graphics
APIs did. It simply draws the input vertices to the screen, with some uniform data
to manipulate the vertices, and a texture sampler to draw textures to those vertices.
For now, I'm happy with this material system. I expect to make a skybox for this boring house to finally give it some life using shaders.
Oh yeah, I didn't find any point to interject this, but the sad looking map is actually one of my first attempts at mapping for Source Engine. My friend who is obsessed with how awful it is thought it would be silly to load it into my engine, so he decompiled the .bsp and converted it to an .obj to be loaded.