Engine Update: Materials and a Skybox
I grew weary of staring into the void
Seeing is believing
I received some feedback that I need to include more pictures. Super valid criticism. On all future blog posts going forward, I’ll be including more visuals. Graphics is all about seeing things, anyway.
Assets and Resources and Instances, oh my
What’s the difference? Why does it matter? In general, assets are for the file IO layer. We load a .obj file, we get an asset. We try to load that same model, we get the asset we already loaded! We don’t have to load it again. If I ever decide to have editing tools for assets, it would be responsible for writing that data out to the file.
Assets
Lets talk about file IO. I mentioned Chai and plugins before, but that offers some serious flexibility here.

The model loaders on the left are plugins, Chai doesn’t directly know about them. All loaders have a canLoad(const std::string& extension) function to determine if the plugin is suitable for loading the file. The load function returns a unique pointer to a generic asset object. Sweet. Now we just need a way to manage all of this. Enter the Asset Manager. The Asset Manager contains a list of loaders (from the plugins!) and matches files with the right loader. Then it calls load and caches the resulting Asset. The Manager owns the Asset! How clean.

Resources and Instances
Resources are for made for the gpu, but they’re expensive to build. We’ll just share it, and only do it as necessary. So we have assets, which are sort of the immutable blueprint. We can definitely create Resources from Assets, but what about programmatically? Let’s make a builder!
First we build a material from a shader. This is our resource, the thing that actually exists on the GPU. We set up sensible defaults: white colors, moderate shininess, a texture.
auto material = chai::MaterialSystem::Builder(phongShaderAsset)
.setVec3("u_DiffuseColor", chai::Vec3(1.0, 1.0, 1.0))
.setVec3("u_SpecularColor", chai::Vec3(1.0, 1.0, 1.0))
.setFloat("u_Shininess", 64.f)
.setTexture("u_DiffuseMap", textureResourceHandle)
.build();
Now imagine we want another object with the same material, but tweaked slightly. Don’t rebuild it, just make an instance, and tweak as needed.
auto materialInstance = std::make_unique<chai::MaterialInstance>(material);
materialInstance->setParameter("u_DiffuseColor", chai::Vec3(1.0, 0.0, 1.0));
And that’s it! The instance is just a thin override layer to edit. (Later we’ll edit it at runtime)
One more thing to add, the components I use on my application side store Resource Handles, which are thin id objects. This helps with threading concerns and keeps objects super clean!
Suzanne can now have a make over!

Textures
Unfortunately, Suzanne still resides in the deep, dark void. But let’s bring Suzanne to the light side, which means… a skybox! A skybox means… textures! It’s high time I had textures. Starting with a cube texture would be insane, let’s go 2D. We’re getting to the fun part!
I decided to render a texture on a flat plane. I created a Game Object and made a super simple obj to represent the vertex plane. Behold, a white 2d plane!
Let’s get to texturing. Given the architecture we’ve been building, this is cake.
- Make an image loader plugin (stb image makes this super easy)
- Adjust my fragment shader to take a texture
- Add a section in my Upload Queue for uploading textures
- Hook it all up on the application side by allowing uniforms to have a type of a ResourceHandle
- Profit 😎
Is that how it really went? Almost. I used a screenshot of a tardis model I made in blender.

Well it’s got some issues. The texture is backwards, it looks super dark and gray. The backwards part was a super simple fix, the uvs in my object file were flipped. I adjusted the material by setting diffuse and specular to white, upping the shininess. Then finally, I adjusted the lighting to be brighter.

The lighting looks a bit crazy, but thats a task for a different day. The important thing is that textures totally work! Huzzah!
The sky is the limit (and it’s always infinitely far away)
A skybox is actually a super simple concept. I made an obj of a simple cube (and this time I did the uvs correctly). Cube textures are basically just a collection of 6 textures. The gl call is just a little different:
for (int i = 0; i < 6; i++) {
const auto& face = texResource->getFace(i);
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0,
texData->internalFormat,
face.width,
face.height,
0,
texData->format,
texData->type,
face.pixels.data()
);
}
Then make a simple shader to sample the 3d texture.
I did all of this, and what did I get?

Whatever this is.
But seriously, when I panned around I could see parts of the skybox flashing in and out. To me this screams “Depth issue!!” And that’s when I realized I never set up a way for objects to request different rasterizer settings. I had been using a one size fits all approach, and that just isn’t suitable, especially when a skybox is involved.
I made this class
struct RasterizerState
{
enum class CullMode { None, Front, Back };
enum class FrontFace { CounterClockwise, Clockwise };
enum class FillMode { Solid, Wireframe, Point };
CullMode cullMode = CullMode::Back;
FrontFace frontFace = FrontFace::CounterClockwise;
FillMode fillMode = FillMode::Solid;
//Depth bias
float depthBiasConstant = 0.0f;
float depthBiasSlope = 0.0f;
bool depthBiasEnable = false;
//Extras
bool depthClampEnable = false;
float lineWidth = 1.0f;
};
and simply check if I should state change before draw calls. And…

All done. A seamless skybox, with Suzanne and a tardis plane as bonus. Hell yeah!
Next up: multiple lights and maybe finally tackling PBR. The void has been vanquished, but there’s still plenty to do.