Filip Smola

Categories

  • Open Sea

Tags

  • C++
  • OBJ
  • OpenGL
  • model

With more complex models, it would be foolish store them in a hardcoded array of points. For those, you want to store them in a file and load that file when initialising the game. When testing the cameras, I decided to add this capability to Open Sea. I chose to write the loader myself, rather than use a library such as AssImp, to properly understand how this process works. To make it easier, I chose to support the simple Wavefront OBJ format. All code related to models is contained in the open_sea::model namespace. The relevant pull request is available here.

Model Variants

There are currently two model variants supported, textured and untextured. The untextured variant is purely position data and the color needs to be given to the vertices in the fragment shader. The textured variant is the same, just with additional UV coordinates associated with each vertex. This means that if you don’t need the UV coordinates (for example you might want to render the wireframe of a model), you can load the model as untextured and save some memory by not having to store the useless UV data.

For the rest of the article I will be talking about the textured variant unless otherwise specified. The points are the same for the untextured variant, just ignoring any UV coordinates. The textured variant is represented by the Model object, while the untextured variant is represented by its child UntexModel.

Loading Models

Models can be loaded from any triangulated OBJ file. I have built the loader functions to match the general OBJ specification with the assumption there are exactly three vertices per face. This is due to the models being rendered as triangles and the parsing being easier that way. In the future, I will look into using triangle fans, which would allow non-triangular faces. The problem is that processing the file would be more complicated, so I went with the simplest version for this first iteration.

You can load an untextured model using the textured model class, which will assume UV coordinates of (0,0) for each vertex. This will result in more memory being used, but allows using the base class when the properties of the model are not known. In such a case, a warning will be logged suggesting you use the subclass for this file.

The other way of creating a model is directly from data. You can build a vector of vertex descriptions (using the Vertex struct in the desired model class) and a vector of indices, and pass those directly to the desired model class constructor. This allow model creation from hardcoded or dynamically created data. It is also used by the file loaders to separate the file format from the actual model data. This will make adding support for more file formats easier.

Drawing

To draw any model, use the draw() method. This binds the vertex array, draws it using triangles, and then unbinds it again. It assumes shaders have been properly used and all required uniforms set around the call. This makes models independent of specific shaders, all that you need to ensure is that the shader uses the right vertex attribute layout.

Vertex Attributes

At this time, there are only two vertex attributes. The first one is the position, which is at location 0 and is a vector of 3 floats. The other is the UV coordinate, which is at location 1 and is a vector of 2 floats (as mentioned before, this is not present in untextured models). Position is in model space, therefore to place the model in the world, you will need a world matrix.

Debug

Each model can provide basic debug information about itself. So far it is the number of vertices (size of index array), the number of unique vertices (size of vertex array) and the size in memory. This can be used to compare different variants of models.

Test model with debug window shown