Building the Lua mesh into a binary
The first part of this week’s assignment was to create a binary mesh file and load in our game. We use our MeshBuilder to load the Lua formatted mesh file and convert it into a binary file. This simply means parsing the Lua file (which we did already in a previous assignment) and spitting out the data as bytes into a new file. The binary file in a hex editor looks like this:
The first 4 bytes are the number of vertices. The next 4 bytes are the number of indices. This is followed by the array of vertices which is followed by an array of indices. Because this is little endian, the least significant byte comes first.
The length of an array always has to come before the array itself so we know how many bytes to read. This mean the number of vertices must come before the vertices and the number of indices must come before the indices. I keep the vertices and the indices as the first two because this makes it easy to read in these values in code and I can also just look at the hex and figure out how many there are. I think it doesn’t matter much as long as whatever you decide works.
The main advantage of the binary format compared to a human-readable format is that we can load the entire file at runtime in one go and process it without having to do any parsing. This makes it faster to load assets. It also helps us reduce file size because we are only storing the data and no metadata. The binary file in the above screenshot is 56 bytes vs 347 bytes in the Lua formatted mesh file.
A human-readable format is helpful for storing data during development. Disk space is relatively cheap and an expressive format allows us to open a file and figure out what it is for and make it easier to debug. Obviously we don’t need to do this for the built game because the game already knows what the files are and performance is our priority.
The mesh files should be built appropriate to the platform we are targeting. A clear example here is that GL vertex colors are ordered as RGBA while D3D colors are ordered as BGRA. If we don’t consider this when building then we will have to switch the color values around depending on the format of the mesh file and the graphics API we are running on. This is additional processing which can be simply avoided if we use the same vertex format during asset building and runtime.
Loading the binary mesh
Loading the binary mesh was pretty straightforward:
char * current = buffer; uint32_t vertex_count = *reinterpret_cast<uint32_t*>(current); current = current + sizeof(uint32_t); uint32_t index_count = *reinterpret_cast<uint32_t*>(current); current = current + sizeof(uint32_t); sVertex* vertices = reinterpret_cast<sVertex*>(current); current = current + vertex_count*sizeof(sVertex); uint32_t* indices = reinterpret_cast<uint32_t*>(current); *o_mesh = createMesh(vertices, vertex_count, indices, index_count);
Effects (a fancy name for encapsulating shaders)
The last requirement of the assignment was to combine the vertex and fragment shaders into an “effect”. This is similar to how we made a platform independent “Mesh” interface. The purpose is that want to be able to load and set the shaders in a platform independent way.
// Create an effect from file s_effect = eae6320::Graphics::createEffect("data/square.vert", "data/square.frag"); if (!s_effect) goto OnError; ... ... // Set the effect result = SetEffect(s_effect); assert(SUCCEEDED(result));
Currently its a static variable because we only have one effect and I don’t know how we will use more effects. But it should be pretty easy to make more effects in the future using createEffect().
I think the binary conversion was fairly easy, mainly because of the simplicity of our mesh files. Figuring out error checking with C++ fstreams and writing the Effects struct and interface took some time.
The output is same as last time:
Controls: Press Esc to quit