Thursday, 28 January 2016

Adding a setup window to our tutorial project

I've just checked in some changes to our tutorial project that I'm going to keep aside of our tutorial series. I'm not entirely sure if I'm going to keep this, enhance it or replace it.

The problem I kept running into is that we had hard coded our window mode and window size in our main.c source code. This mainly because writing a GUI could be a tutorial series all by itself and it detracts to what I'm trying to do here.

But then my eye fell on this library called AntTweakBar which is listed on the GLFW site. It is a really neat little library but it has a few rather troublesome drawbacks:
  1. it doesn't compile on the Mac anymore, lucky these were easy to fix up after a bit of reading on the support forum
  2. the mappings to GLFW are still based on GLFW 2.7, not 3.0. I've got them working except for our keyboard mappings. To properly fix this I'll also have to recompile the windows binaries
  3. there isn't a lot of freedom in the controls and layout of the window
  4. it is no longer maintained
Also the library compiled as a dylib which I wasn't too fond of. I changed the makefile to output a static library instead and that is included in the mac build on my github site.

I'm planning to do more with the library as time goes by and do a proper tutorial writeup if I do enjoy using it. If so I might be tempted to start enhancing it. But equally so I may end up replacing it.

So for now I'm just going to give a very brief overview of the changes that I've checked into our tutorial project.
First off, all the new support code is embedded into two new files:
  • setup.h - our header
  • setup.c - our source code
Yes, no single file approach for this one.
The library has a single public function which is SetupGLFW and all it does is open up a window that provides 3 dropdowns:
  • choosing the monitor to use for running in fullscreen, or the option to choosing windowed mode
  • choosing the resolution (if fullscreen)
  • choosing the stereo mode for optional 3D stereo rendering
After the user makes a selection a structure is populated which can be used in the calling function to properly setup a window like so:
  ...
  if (!SetupGLFW(&info)) {
    glfwTerminate();
    exit(EXIT_FAILURE);
  };
  
  // make sure we're using OpenGL 3.2+
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  if (info.monitor != NULL) {
    glfwWindowHint(GLFW_RED_BITS, info.vidmode.redBits);
    glfwWindowHint(GLFW_GREEN_BITS, info.vidmode.greenBits);
    glfwWindowHint(GLFW_BLUE_BITS, info.vidmode.blueBits);
    glfwWindowHint(GLFW_REFRESH_RATE, info.vidmode.refreshRate);
    if (info.stereomode == 2) {
      glfwWindowHint(GLFW_STEREO, GL_TRUE);    
    };

    errorlog(0, "Initializing fullscreen mode %i, %i - %i",info.vidmode.width, info.vidmode.height, info.stereomode);
    window = glfwCreateWindow(info.vidmode.width, info.vidmode.height, "GLFW Tutorial", info.monitor, NULL);
  } else {
    errorlog(0, "Initializing windowed mode %i, %i - %i",info.vidmode.width, info.vidmode.height, info.stereomode);
    window = glfwCreateWindow(info.vidmode.width, info.vidmode.height, "GLFW Tutorial", NULL, NULL);
  };
  
  if (window == NULL) {
    errorlog(-1, "Couldn''t construct GLFW window");
  } else {
    ...

The end result looks like this:

Tuesday, 26 January 2016

Stereoscopic rendering (part 18)

Ok, I decided to take a little fun sidestep. Those who've been following my blog for awhile know I've played around with this before. Back then it was just a bit of fun but a month or two ago Ian Munsie dropped by our little game developers meetup at the North Sydney TAFE and showed of the fun he was having with stereoscopic rendering. He brought in his AlienWare laptop that has a 3D capable screen using active shutter glasses and demoed a copy of Abe's Oddessy that had been enhanced for stereoscopic rendering. Needless to say it very much impressed me and I very much believe the industry gave up on 3D stereoscopic games way too early.

As I have a 3D TV I've played a number of titles on my PS3 and XBOX 360 that allow stereoscopic rendering but in order to cope with the additional work often go down to a 720p resolution and it does suffer but is still a lot of fun. The demo Ian gave was at full resolution of his laptop (full 1024p I'm guessing) and simply looked rock solid.

Unfortunately my AlienWare laptop does not have stereoscopic support (though the NVidia chip may be able to output a 3D signal over HDMI which I hope to test later on) nor does my trusty Macbook.
My Panasonic 3D TV however does have the capability to use a split screen signal.

It is incredibly easy to convert our little example to allow stereoscopic rendering. It is also incredibly easy to do it wrong. Below are two links which explain this stuff way better then I possibly could:
There are basically 3 things that we need to know about to do things right.
The first is that it is not as simple as moving the camera position slightly. The eye looks forward in a parallel direction but the created frustrum is slightly skewed. Have a read through the NVidia document as it explains it way better then I could possibly word it and more importantly, it has pictures :)

The second is the distance between the eyes, the "intraocular distance". Every persons eyes are spaced slightly differently and getting this value wrong can create a very uncomfortable experience or even ruin the effect completely. The NVidia document mentions this distance on average is 6.5cm which is the value I'm using and seems to work well on my TV screen but it is likely something you'd want to make configurable. Also it's not a given a unit in your 3D world is actually equal to 1 cm so play around with what works.

The last thing that we're introducing is the "screen projection plane" or convergence distance. This is the distance from the eye(s) at which something rendered overlaps. If you render something at that distance it would be rendered in the same place for both eyes. Anything closer to the view will seem to pop out of the screen, anything after that will seem to be behind the screen.

Obviously things popping out of the screen is the bit that gives the 3D thrill, but it is also the one that can confuse the eyes if it isn't fully visible as the 3D effect is ruined if part of the object falls outside of our viewing frustrum as a different part of the object is obscured for each eye.
For games having the convergence distance be relatively close and having everything seem to be behind the screen is possibly a good practice.

The biggest drawback of stereoscopic rendering is that we need to do everything twice, everything needs to be rendered for each eye. Now there is a lot that can be reused like shadow maps, occlusion checks (with the footnote you might end up rendering something twice that is only visible for one eye, though that should already be a red flag), calculating model matrices effected by animation, etc.
We have none of those in our example yet but basically we would do those as part of rendering our left eye, and then reusing the information for our right eye, or doing it as part of our engineUpdate call which we do before our rendering happens.

A new matrix function in math3d.h


First thing is first, we need a new function for calculating a projection matrix that is adjusted for each eye. Now I've gone down the route to have our view matrix based on our mono view and have the adjustment for each eye fully embedded in the projection matrix. There is an argument for adding the needed translation to the view matrix and we may end up doing so but for now this seems to make sense.

Anyway our new function is pretty much a refactor as it's presented in the OpenGL document linked earlier on in this writeup:
// Applies a 3D projection matrix for stereo scopic rendering
// Same parameters as mat4Projection but with additional:
// - pIOD = intraocular distance, distance between the two eyes (on average 6.5cm)
// - pProjPlane = projection plane, distance from eye at which our frustrums intersect (also known as convergence)
// - pMode = 0 => center eye, 1 => left eye, 2 => right eye (so 0 results in same projection as mat4Projection)
mat4* mat4Stereo(mat4* pMatrix, MATH3D_FLOAT pFOV, MATH3D_FLOAT pAspect, MATH3D_FLOAT pZNear, MATH3D_FLOAT pZFar, float pIOD, float pProjPlane, int pMode) {
  MATH3D_FLOAT left, right, modeltranslation, ymax, xmax, frustumshift;
  vec3 tmpVector;

  ymax = pZNear * tan(pFOV * PI / 360.0f);
  xmax = ymax * pAspect;
  frustumshift = (pIOD/2)*pZNear/pProjPlane;

  switch (pMode) {
    case 1: { // left eye
      left = -xmax + frustumshift;
      right = xmax + frustumshift;
      modeltranslation = pIOD / 2.0;
    }; break;
    case 2: { // right eye
      left = -xmax - frustumshift;
      right = xmax - frustumshift;
      modeltranslation = -pIOD / 2.0;
    }; break;
    default: {
      left = -xmax;
      right = xmax;
      modeltranslation = 0.0;
    }; break;
  };
  
  mat4Frustum(pMatrix, left, right, -ymax, ymax, pZNear, pZFar);
  mat4Translate(pMatrix, vec3Set(&tmpVector, modeltranslation, 0.0, 0.0));
  
  return pMatrix;
};
Note that if you specify pMode as 0 our IOD and projection plane have no effect and the function is as if we're calling mat4Projection and I'm using that fact later on in the implementation.

Changes to main.c


As we may extent this later on or even completely replace our main container by something else I've decided to put the logic for whether we render stereoscopic inside our main.c file.
For now there is no switching between modes, we simply have a variable called stereo_mode with the values:
0 = mono rendering
1 = stereo rendering in split screen
2 = stereo rendering with full left/right back buffers (untested)

For option 2 we need to give another window hint to initialize our stereo buffers, the main render loop now looks like this:
      ...
      ratio = (float) frameWidth / (float) frameHeight;
      
      switch (stereo_mode) {
        case 1: {
          // clear our viewport
          glClearColor(0.1, 0.1, 0.1, 1.0);
          glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);      

          // render split screen 3D
          int halfWidth = frameWidth / 2;

          // set our viewport
          glViewport(0, 0, halfWidth, frameHeight);
      
          // and render left
          engineRender(halfWidth, frameHeight, ratio, 1);          
          
          // set our viewport
          glViewport(halfWidth, 0, halfWidth, frameHeight);
      
          // and render right
          engineRender(halfWidth, frameHeight, ratio, 2);          
        }; break;
        case 2: {
          // render hardware left/right buffers
          
          // clear our viewport
          glDrawBuffer(GL_BACK);
          glClearColor(0.1, 0.1, 0.1, 1.0);
          glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);                

          // set our viewport
          glViewport(0, 0, frameWidth, frameHeight);
          
          // render left
          glDrawBuffer(GL_BACK_LEFT);
          engineRender(frameWidth, frameHeight, ratio, 1);
               
          // render right
          glDrawBuffer(GL_BACK_RIGHT);
          engineRender(frameWidth, frameHeight, ratio, 2);          
        }; break;
        default: {
          // render normal...

          // clear our viewport
          glClearColor(0.1, 0.1, 0.1, 1.0);
          glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);      
          
          // set our viewport
          glViewport(0, 0, frameWidth, frameHeight);
      
          // and render
          engineRender(frameWidth, frameHeight, ratio, 0);          
        }; break;
      };
      
      // swap our buffers around so the user sees our new frame
      glfwSwapBuffers(window);
      glfwPollEvents();

      ...
Note that I've moved the screen ratio outside of our engineRender function, mainly for our splitscreen option as we're now only have half our space but still need our normal aspect ratio to properly render things.

Note that our glClear ignores our glViewPort in our split screen rendering which is why we do this upfront. With our left/right buffer we set it to updating the full backbuffer when clearing.
Next we either set our viewport to the right half of the screen or select the correct back buffer for each eye and call engineRender for each eye.

Changes to engine.c


Besides using our pRatio instead of calculating it we simply switch over our projection matrix call to our new mat4Stereo function:
  ...
  // distance between eyes is on average 6.5 cm, this should be setable
  mat4Stereo(&matrices.projection, 45.0, pRatio, 1.0, 10000.0, 6.5, 200.0, pMode);
  ...
As mentioned we've hard coded our IOD to 6.5. I've also set our convergence distance to 200.0, as our tie bomber is rendered roughly 550.0 away from our eye that seemed a pretty sound option.

One unexpected little thing was moving the camera around so the little earth globe is positioned right in front of the camera it actually really nicely popped out of the screen :)

I have not done anything to the projection matrix for our FPS counter. Obviously when rendering stereoscopic more care needs to be taken with this as bad positioning can be very intrusive. You can either start rendering this using a proper Z positioning or make sure that everything that is being rendered is indeed behind the convergence distance as rendering 2D the way that is currently implemented positions everything at that distance.

Below is a stereo rendered video of the end result, it actually worked great playing it on youtube on my 3D TV, I even managed to play it on my phone with google cardboard. 


Now I wish I had a Oculus Rift lying around to hook up to my laptop :)

Download the source code here

So where from here?


So obviously this is a very basic stereoscopic setup. There are a few improvements needed such as making a few things configurable and when rendering for a HMD adjusting the image for lens distortion needs to be added.

What's next


As I mentioned in my previous post I've got some ground to cover at this end in creating some 3D content before I can move on with my platformer. I'll probably throw up a few more of these intermediate posts though coming out of the summer holidays I'm not sure how much spare time I'll have in the coming weeks. We'll see:)



Sunday, 24 January 2016

Fixing my NVidia troubles....

Owh what a ride on Windows. The latest check-in on Github now compiles and runs correctly (for me). Older ones probably will work fine for most people as well though I did miss out on a few files in the makefile. Do note that I did upgrade to the latest Visual Studio Express so if you're on something older you may need to grab older build files from the GLFW website.

The problems I was having were all related to my recent Windows 10 upgrade. After a lot of headaches and googling I found that I wasn't the only person having troubles. To be exact some people got badly stuck halfway through the Windows 10 upgrade.

I guess I got lucky as I'm using an AlienWare laptop with both an Intel graphics card and an NVidia GT 555M and the intel card being used by default to save on power usage (and thus battery life).

Anyways, something went wrong in the drivers. Something went even more wrong in the automatic driver update which kept telling me I had the latest drivers but that was a pretty lie.

NVidia fixed the issues a while ago and released new drivers and as soon as I installed those, all my problems (including a few that had been haunting me with certain games) went away.

Saturday, 23 January 2016

GLFW 3.1.2, GLEW 1.13.0 and latest stb_images

I've updated my GLFW tutorial files to use these latest versions of GLFW, GLEW and STB. I've also made several changes to make the source compile on Windows. These changes have now been checked into the master branch.

Unfortunately compiling GLFW on my new Windows 10 machine has proven to be troublesome and it keeps crashing. Even bringing it back to the basic example of GLFW it is proving not to be very happy. I think it's related to Windows 10 and still using VC 2010 compilers as the older examples won't run properly either.

So sorry to you windows guys out there, it's all still dodgy. If anyone has any suggestions please feel free to provide feedback on my github page.

Friday, 22 January 2016

Loading models #2 (part 17)

Well it sometimes helps going places were there is no internet, I managed to write most of the model loading code over the weekend. I was originally planning on doing this in two more parts but here we go with an extra long write-up and it thus took me a little while longer before this was ready:)

First off, I made two structural changes to the mesh object that need to be highlighted.

The first one is that I added a retain count to the object. When you create a mesh object the retain count is set to 1. If you pass the pointer around and store the pointer in an array you can call meshRetain to increase the retain count.
When you no longer need to keep the pointer to the object you call meshRelease, this decreases the retain count. Once the retain count reaches 0 the memory is freed up.

The second change is that I changed the two arrays that hold the mesh data. I introduced a new support library called dynamicarray.h that manages an array that holds data. A very simple implementation where you would use std::vector in C++.

The release/retain approach I've started retrofitting to a number of structs where I found this handy.

Also before I forget, I've compiled GLFW 3.1.2 for Mac and removed the windows files until I get the change to compile GLFW for windows so please be aware of that if you're on windows.

New support libraries

I've added a number of new support libraries with rudimentary code to do some common tasks. I'll either enhance them as time goes by or replace them if I find existing C libraries that others have created that save me the trouble. The really cool ones I have are usually C++ libraries, alas.
I'll briefly discuss them here but point to the code within for further details as they are really out of the scope of what we're trying to learn here.

As with the other libraries, they are once again single file implementations.

errorlog.h

This needs very little explanation, I got tired of copying the pointer to the error log function around so I placed it in a library that I'm using everywhere. I kinda disliked forcing an error log on everyone who uses the libraries but it's pretty easy to gut it or replace it with your own internals.

I haven't retrofitted it into 2D libraries yet but will soon.

varchar.h

Standard C libraries work pretty well with a fixed sized string but for variable length strings there is a lot of remembering to reallocate buffers and whatnot. Here is a simple implementation of an object that resizes the buffer containing the string for you. While the structure does have a length variable to indicate how long the string is, the string is also zero terminated so it can be used with standard C string functions. It's basically a poor mans std::string implementation. It also uses a retain counter.

Basically only the variable text, which contains the string, should be accessed directly as a read only variable or things will start falling apart. Note that this can be NULL pointer if no string is loaded.

So far the following functions are available:
newVarchar allocates memory for a new string object returning a pointer and initializing the struct held within.
varcharRetain increases the retain count.
varcharRelease decreases the retain count and frees up the memory once retain count reaches zero.
varcharCmp compares our string with a zero terminated string similar in function to strcmp.
varcharPos tries to find the position of a zero terminated string within our string.
varcharMid returns a substring of our string as a new varchar object (which must be released!).
varcharAppend appends a string at the end of our string.
varcharTrim removes and spaces and tabs from the start and end of the string.

As time goes by I'll add further string functions that makes sense, this hardly even covers the basics but it was all I need for now.

dynamicarray.h

I mentioned this in my introduction, this is a simplified implementation of a dynamic array similar to std::vector. It can hold any type of data but seeing C doesn't understand templating we can only tell our logic how many bytes long each entry in our array is. It is up to us to properly cast this data.

As such you should treat all variables in our struct as private and not access them directly.

newDynArray allocates memory for a new array, you specify the size in bytes for each entry.
dynArrayFree frees up the memory held by the array. If the data you placed within contains pointers those are NOT freed.
dynArrayCheckSize increases the amount of memory allocated for the array if required, handy to give a starting size and prevent unnecessary calls to realloc.
dynArrayPush adds a new entry to our array, increases the size of memory allocated if required. The contents of the data is copied into the array.
dynArrayDataAtIndex returns a pointer to a location within the array so you can either extract or change the data for that entry.

Again there are a few basic functions still missing, like being able to remove an entry or merge two arrays but again, this is all I need for now. One that we'll add soonish is the ability to quick-sort our array.

linkedlist.h

This is maybe one of the more complex support libraries. A linked list is an array where each entry in the array is allocated separately and the list is formed by the entries pointing to each other like a chain.
The big advantage of this is that it is fairly inexpensive to make changes to rather large lists, there is no need to reallocate all the data.
The disadvantage is that it is harder to traverse the list, you can basically only loop from the first to the last, and from the last to the first entry (and this later only only because I've implemented both a next and a previous pointer). It can thus be expensive to find an entry somewhere in the middle of the list.

There are two structs here, one is a container for meta data about our list, the second is a struct allocated for each entry. The data in these structures should be treated as read only.

The most common code fragment you'll need is to loop through all the entries of a linked list and you do this as follows:
llistNode * node = myList->first;
while (node != NULL) {
  mystruct * object = (mystruct *) node->data;

  // do your thing here

  // next!
  node = node->next;
};

I've chosen to store a pointer to the data 'held' within the list instead of copying the data like we've done with our dynamic array. As a result I've made the implementation optionally aware of the retain and release functions of our of our objects. When a pointer to an object supporting retain/release is added to our linked list, the object is automatically retained. If we remove an entry, or get rid of the linked list all together, the relevant objects are released.

newLlist allocates memory for our new linked list object, you can optionally provide a retain and release function for your entries.
llistFree frees up the memory for our new linked list object, note that if a release function was provided when the linked list was created, the release function is called for every entry currently in the linked list.
llistAddTo adds an entry to our linked list. If a retain function was provided it is called.
llistRemove finds the entry that points to the specified object and removes it. Only if both a retain and release function was provided do we release the object.
llistMerge adds all entries from one list to another. If a retain function was specified the objects are also retained. The source list is not modified.
llistDataAtIndex returns the pointer to the object (data) added at this index in our linked list.

Wavefront


We're nearly ready to start enhancing our application to actually load 3D object data. There are many formats out there each with their strengths and weaknesses. The biggest issue I always run into, especially with more modern modeling software, is that those tools are designed for creating rendered animations with a focus on realism and not always best suited for use in games. Often the formats are bloated with functionality that gets in the way of what we're doing. Formats are usually designed to fit well within the functionality offered by the modeling software, not optimized to how the hardware can most easily render the data.

One thing I often find myself doing is creating loaders for easily available file formats but then writing the data back out in a format that matches the engine I've build.

The format I've chosen for our tutorial is an oldy but goldy file format created by Wavefront. There are two things that really attract me to this format:
1) it's text which makes it easily readable and easy to learn and understand,
2) it's very too the point, it just contains mesh data, not other stuff we don't need.

It has only got two drawbacks:
1) it allows polygons with any number of vertices. We thus need to split them into triangles which doesn't always give a nice result. We could off course load the polygons as patches and use a geometry shader, but that is a topic for a tutorial far in the future.
2) our positions, normals and texture coordinates are 3 separate arrays which each vertex of our polygons maintains separate indices for. As we now know OpenGL doesn't work this way and we thus need to rearrange the data. This can be time consuming. Also all objects contained within a single file share a single array (even though they are often split up) which again we don't do.

Now there really are two formats that we're dealing with. An .mtl file which describes the materials used, and the .obj file which contains the actual mesh data.

We're going to look at the first one first but as we didn't have materials yet in our system we need to design those first.

Owh, and before I forget, for our example today I managed to get my hands on a Star Wars Tie-Bomber 3D model modeled by someone named Glenn Campbell. The model was downloaded from this great site called TF3DM. This is like a marketplace for 3d models with loads of free models and paid models to chose from. Many of the free models are only for non-commercial use but that suits us just fine. I chose the tie-bomber mostly because it was relatively small.
The only downside is that it doesn't use any texture mapping but as we've already applied texture mapping to our globe it's not a big miss. I have added (but not tested) all the code to load texture maps and texture coordinates for our meshes. I could be mixing up with 3DS files but it may be the texture will have ended up upside down:)

Materials


I've consciously kept materials apart from the shaders that actually bring those materials to life. A material describes the properties of the materials that forms our 3D objects, the shader then uses that to draw it appropriately. All the logic is contained in a new library called materials.h

Now we do need to talk about the shaders first.
You'll see that I've now got 3 separate shaders. Our texture based shader we had before, a single color shader and a reflection shader (more on this later). I could have written a single shader with if statements but due to the way graphic cards work even the "false" condition can end up being executed and its results discarded if the parallel processing deems it can use some idle core to do that work on the assumption the condition may be true. If we do expensive lookups that 9 out of 10 times are not needed, we're just burning GPU cycles for nothing.
The effects can however be combined so in theory we need a large combination of shaders to do our rendering.
This is something we'll look into at some later stage as it's a topic on its own but our approach will remain similar to what we're doing now. For each material we'll chose the best shader for the job. Right now we do that in our render loop but we may move that to an initialization stage at some point.

The focus thus lies on a structure that retains information for a particular material:
// structure for our material info
typedef struct material {
  unsigned int  retainCount;      // retain count for this object
  char          name[50];         // name of our material
  
  GLfloat       alpha;            // alpha for our material
  vec3          matColor;         // diffuse color for this material
  vec3          matSpecColor;     // specular color for this material
  GLfloat       shininess;        // shininess of this material
  GLuint        textureId;        // id of our texturemap
  GLuint        reflectId;        // id of our reflectionmap
} material;
This structure may grow as we keep adding functionality to our rendering logic but it has all the basics.
Most of the variables in our struct can be changed directly but some, like our texture maps, must be set through the appropriate functions.

Because a material may be shared amongst multiple models and we want to properly manage the memory usage we again use a retain count for this object.

We've then implemented the following methods:
newMaterial allocates memory and initializes a new material.
newMatList returns an empty linked list object properly configured to store material objects into.
matRetain increases the retain count on our object.
matRelease decreases the retain count on our object and frees up memory used once the count reaches zero.
getMatByName searches a linked list containing materials for a material with the given name. Note that if you want to hang on to that material you must retain it!
matLoadTexture loads the specified image file as the texture map for our material.
matLoadReflect loads the specified image file as the reflection map for our material.
matParseMtl loads the materials defined in the specified wavefront mtl data and adds them to a linked list.

This last function, matParseMtl is where the magic happens. Just like with our shaders I've kept loading the material file separate and we thus provide already loaded data to our function.
We also use a materials linked list to store our materials in. There is no protection to ensure a material with the same name doesn't appear more then once in our materials list, we assume we're taking care with loading files for now. We load our material data as follows:
  // create a retainer for materials
  materials = newMatList();
  ...
  // load our mtl file
  text = loadFile("tie-bomber.mtl");
  if (text != NULL) {
    matParseMtl(text, materials);
      
    free(text);
  };
  ...
I'm not going to go through the entire code for loading these files but the parser is fairly simple. We extract each line of the file one by one and place it in a varchar object. We skip any empty lines or lines starting with a # as this is a comment.
We then make sure we trim any unwanted spaces and then find the first space in the string. The text returned here identifies what data we're loading at.
Each new material always starts with a line starting with newmtl and we create a new material object each time we encounter it.
I've added comments for each other type of line that we encounter and currently parse but it should be pretty self explanatory.

Object file


Now that we've loaded our material information we can load our object file. The object file tells us which material file it uses but I'm ignoring this as we've already loaded it.

We've taken the same approach of loading the data into a buffer first and then calling the function meshParseObj that has been added to our mesh3d.h file.

A single wavefront object file can contain any number of "objects" which are subdivided into "groups". Think of this as the objects being cars, bikes, street lamps, etc, while the groups subdivide an object such as a car into the body, wheels, windscreen, etc.
I'm currently ignoring the objects taking a "single file is single object" approach as this requires a layer on top of our mesh objects which we have not yet build.
Just like with our materials we provide a linked list containing mesh3d objects to which we'll add the object we load. We also provide our materials linked list so we can look up materials and assign them to our mesh3d objects.

An important rule is that each mesh3d object is rendered using a single material. So our mesh3d objects really fit well with the definition of groups in the wavefront files.

While a bit crude, and we do support it, both objects and groups are optional and if not specified we should assume a default object is required.

Just like with materials, there is no problem in adding two mesh3d objects into our list that happen to have the same name.

Parsing the object file is remarkably similar to parsing the materials file, it is no surprise the structure of the files are relatively the same. We do see that as we're loading position vertices, normals and texture coordinates we are loading those into temporary arrays.
Then as we're loading faces we're keeping track of combinations of these 3 units for which we've added vertices to our mesh3d object. Especially on large wavefront files this can churn through a fair amount of memory and isn't implemented particularly fast but as I mentioned before for a real production application I would implement some sort of conversion tool to export the data into a format more closely mimicking my 3D engine.

The source is too bulky to present here so I again refer to the comments I've made in the source.

Changes to shaders


Next I needed to enhance the shader implementation. Our reflection shader is the first that works in world space so I needed to ensure my shader had a normal matrix without our view matrix applied and I needed to communicate the location of our eye.

I also decided to add in a number of other standard IDs likely used for our materials to our shaderStdInfo struct and ensure those IDs got setup correctly. I also added our light position but in this case it's a temporary fix. More about that much later in the series.

I've also introduced a structure that simple holds our main 3 matrices (perspective, view and model matrices) called shaderMatrices so it is easy to pass this to our shaderSelectProgram function.
I also introduced a structure called lightSource that holds both our world position of our light and the position adjusted by our view matrix.

But the biggest addition is sending our material to our shaderSelectProgram function to communicate the properties of our material.

This will do fine for what we're doing so far. The biggest issue we're having right now with our shader is that each time we render a mesh, we initialize the entire shader. This is something we'll have to work on as our project becomes more complex. Again however, a topic for later.

As mentioned earlier, we now have our textured shader, a single color shader where instead of getting the color from our texture map we just set our color we now also have a reflection shader. Soon I'll write one session in my tutorial on better organizing our shaders but this split will do us fine for now.

The reflection shader, also often called environment shader, is an old trick that can be very useful. I included it because our model had a number of maps setup this way.

The very last material in our material file, CHROME_GIFMAP probably showcases its use the best as a method for rendering shiny metal. Unfortunately there are only two small parts in the model that use this. The body of the tie-bomber also has a reflection map but the map provided with the model seemed to be a template. I think the original idea was to reflect stars. I've added a starmap but it's far to bright making the effect look very fake but in doing so it serves as a pointer to the limits of this technique.

The technique is very simple, you take the vector from your eye to where it hits the surface, then use our normal to reflect that vector to see where the light that is reflected comes from. You then use a texture map to look up the color. It works amazingly well on curved surfaces but less on flat surfaces.
It is often used to simulate metal as this works well with very blurry maps that don't reveal to much detail and give the effect away. It is also a great way to simulate ambient light by taking an actual spherical map of the surroundings, blur the image and multiply with the ambient color of the material and voila. Another use is for reflections on a shiny or wet floor or on the surface of the water where again the reflection is distorted and you can get away with using a static texture.

But as a true reflective surface like a mirror, this technique often falls short especially on flat surfaces. You'll quickly realize the reflection isn't related to whats around you. But luckily there are better solutions to that which we'll undoubtedly get to in due time.

Changes to render loop


And now on to our last step, actually rendering our whole tie-bomber. Our fixed two meshes we were rendering has now been replaced by a loop that loops through our linked list and renders each mesh individually:
  if (meshes != NULL) {
    int count = 0;
    llistNode * node = meshes->first;
    while (node != NULL) {
      mesh3d * mesh = (mesh3d *) node->data;
      if (mesh == NULL) {
        // skip
      } else if (!mesh->visible) {
        // skip
      } else {
        bool doRender = true;
        // set our model matrix, this calculation will be more complex in due time
        mat4Copy(&matrices.model, &mesh->defModel);

        // select our shader, for now we have 3 shaders that do different types of shading
        // eventually we'll add something that allows us to make combinations
        if (mesh->material == NULL) {
          shaderSelectProgram(&colorShader, &matrices, &sun, NULL);
        } else if (mesh->material->alpha != 1.0) {
          // postpone
          alphaMesh aMesh;
          GLfloat z = 0; // we should calculate this by taking our modelview matrix and taking our Z offset
        
          // copy our mesh info
          aMesh.mesh = mesh;
          mat4Copy(&aMesh.model, &matrices.model);
          aMesh.z = z;
        
          // and push it...
          dynArrayPush(meshesWithAlpha, &aMesh); // this copies our structure
          doRender = false; // we're postponing...
        } else if (mesh->material->reflectId != MAT_NO_TEXTURE) {          
          shaderSelectProgram(&reflectShader, &matrices, &sun, mesh->material);          
        } else if (mesh->material->textureId != MAT_NO_TEXTURE) {          
          shaderSelectProgram(&texturedShader, &matrices, &sun, mesh->material);          
        } else {
          shaderSelectProgram(&colorShader, &matrices, &sun, mesh->material);
        };

        if (doRender) {
          // and render it, if we fail we won't render it again
          mesh->visible = meshRender(mesh);          
        };
      };
    
      node = node->next;
      count++;
    };
  };
Chosing which shader to use is a bit simplistic but fine for where we are now however I'm making an exception for any material that isn't fully opaque but has a level of transparency.

Opaque materials are easy, the Z-buffer will sort things out, but with transparent materials we need to know the underlying color and blend. Also this blending process is relatively expensive as it requires reads from our output buffer so we gather our transparent meshes in a list, and then postpone rendering them till later:
  // now render our alpha meshes
  if (meshesWithAlpha->numEntries > 0) {
    // we should sort meshesWithAlpha by Z
  
    // we blend our colors here...
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    for (int i = 0; i < meshesWithAlpha->numEntries; i++) {
      alphaMesh * aMesh = dynArrayDataAtIndex(meshesWithAlpha, i);
    
      mat4Copy(&matrices.model, &aMesh->model);
      if (aMesh->mesh->material->reflectId != MAT_NO_TEXTURE) {          
        shaderSelectProgram(&reflectShader, &matrices, &sun, aMesh->mesh->material);          
      } else if (aMesh->mesh->material->textureId != MAT_NO_TEXTURE) {          
        shaderSelectProgram(&texturedShader, &matrices, &sun, aMesh->mesh->material);          
      } else {
        shaderSelectProgram(&colorShader, &matrices, &sun, aMesh->mesh->material);
      };

      aMesh->mesh->visible = meshRender(aMesh->mesh);
    };    
  };
The above code still has various holes in it but I wanted to keep things as simple as I could. As mentioned in the comments we should sort this list by distance, to render transparent meshes that are distant first.

But that is something that we'll be changing in the entire loop. Our initial loop will eventually result in two dynamic arrays, one with opaque meshes, one with transparent meshes, which subsequently are rendered one after the other. I'm getting ahead of myself but this approach has a number of reasons:
  1. meshes will have relationships to each other, a wheel of a car will have a model matrix that positions it relative to the center of the car. When rendering the car we take the model matrix of the car to position the car, but we combine the model matrix of the car with the model matrix of the wheel to position the wheel
  2. meshes may be off screen. We can exclude many models that are part of our world but currently not on screen from the rendering process.
  3. we can sort our opaque meshes in a way that makes best use of our shaders and as mentioned before sort our transparent meshes by distance.
I'm sure I'm leaving some other important ones out.

Well after all this work, we have our tie-bomber....

As you can see it isn't without fault. One problem that isn't apparent in the video is that the model isn't properly centered. This is something I'll need to deal with in the model loading code in due time.
The other issue is that the dark indented areas on the 'wings' aren't fully rendered on one side of the model, at this point I'm not sure what the cause is.

Download the source here

What's next


Well unfortunately I'll likely be taking a bit of a break. I might throw in a few blog posts on several subjects such as shaders and other smaller improvements I'll be making but the next step will be to pick up our platform game again and start porting it to 3D.

The biggest hurdle at this point is that it requires a lot of non-programming work before I can actually build the next step. Copying and pasting images into a tilemap is easy, creating 3D models takes a lot of time. But we'll get there in the long run :)

Sunday, 17 January 2016

glActiveTexture

I'm just taking a quick sidestep from my tutorial series to spend a few words on glActiveTexture and when to a bind texture which may have been unclear as far as this tutorial has gone.

OpenGL is a tad strange when it comes to textures. One would think that glGenTextures would return an ID that we can use whenever we identify a texture and OpenGL would sort itself out. It makes sense that we can only do operation like loading on one texture at a time and therefor need to bind it, but the rest seems a bit silly.

glGenTextures gives us IDs that allow OpenGL to identify which texture we're working with (e.g. texture objects). In theory you can have as many texture objects as you like provided you've got enough memory to load textures into.

But from the early days of OpenGL you could only use a limited number of textures at any given time simultaneously. Usually not much of a problem as most shaders only use a single texture, and more complex ones may use 3 or 4. You could have hundreds of texture objects loaded, but you'd only use a select few textures at any given time.

You could look at the set of active textures as a fixed size register and each entry in this register is identified with the GL_TEXTUREn constants (also called texture units).
So GL_TEXTURE0 is our first active texture, GL_TEXTURE1 our second, GL_TEXTURE2 our third, etc. These constants generally go up to GL_TEXTURE16 even if more are supported but you can simply do GL_TEXTURE0 + 16 to access GL_TEXTURE17.

When we want to do something with textures we therefor first need to select one of these texture units and then bind the texture object we wish to use as follows:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId); // bind the ID we got from glGenTextures
This binds our texture object id to our first texture unit. Initially as we're loading textures, we'd now configure the texture, load the texture data, etc. All these types of actions modify the texture itself, that we are using our first texture unit is simply a necessity but binding a different texture will not effect the settings and/or data we changed on our current texture.
This is why you often see in loading code that all textures are loaded while our first texture unit is active. It is often simply not even set as our first texture unit is the active one by default. In some extreme cases this can be a problem if other bits of code do not expect the texture bound to a particular unit to change. I would argue that is bad coding and you should specify the texture unit you wish to use and bind the correct texture object, not assume either is set already.

Once we're in our render loop our units become important. If we want to use 4 textures at the same time we can't simply bind one after the other. Each would overwrite the binding of the next. We need to bind our 4 textures to 4 individual units like so:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[0]); // bind our first texture object
glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[1]); // bind our second texture object
glActiveTexture(GL_TEXTURE2); // make our third texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[2]); // bind our third texture object
glActiveTexture(GL_TEXTURE3); // make our fourth texture unit active
glBindTexture(GL_TEXTURE_2D, TextureId[3]); // bind our fourth texture object
Now our four textures are all active. It will come as no surprise that it is the texture units, not the IDs generated by glGenTextures, which we subsequently use in our shaders:
glUniform1i(samplerId1, 0); // use our first texture unit for sampler 1
glUniform1i(samplerId2, 1); // use our second texture unit for sampler 2
glUniform1i(samplerId3, 2); // use our third texture unit for sampler 3
glUniform1i(samplerId4, 3); // use our fourth texture unit for sampler 4
Put the two examples together and a real life example may look something like this:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, ColorTextureID); // bind our color texture object
glUniform1i(ColorSamplerID, 0); // use our first texture unit for our color sampler

glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, NormalMapID); // bind our normal map texture object (bump map)
glUniform1i(NormalMapSamplerID, 1); // use our second texture unit for our normal map sampler

glActiveTexture(GL_TEXTURE2); // make our third texture unit active
glBindTexture(GL_TEXTURE_2D, EnvMapID); // bind our environment map texture object
glUniform1i(EnvMapSamplerID, 2); // use our second texture unit for our environmnet map sampler

// it is considered good sport to make our first texture unit the active one again:
glActiveTexture(GL_TEXTURE0);
It's also considered good form once you are done rendering to unbind all the textures:
glActiveTexture(GL_TEXTURE0); // make our first texture unit active
glBindTexture(GL_TEXTURE_2D, 0); // unbind
glActiveTexture(GL_TEXTURE1); // make our second texture unit active
glBindTexture(GL_TEXTURE_2D, 0); // unbind
etc...

Like I mentioned before, this may seem a little silly and bloated, why not just use our texture object IDs directly in our shaders?

There is a little more to this. There is a cost to having textures active as it enables sampling from these textures.
Having more textures active then needed may claim resources unnecessarily and slow things down especially once you hit cache limits.
At the same time, binding a new texture can result in overhead if that texture is currently not loaded into faster cache memory and needs to be brought forward. 

It can be a bit of a balancing act however I'd try and abide by two rules:
- have only the textures active you need for your current shader
- minimize the number of times you need to change which textures are bound by ordering what you draw based on the shader and the texture(s) used.

This last remark is important because it feels counter productive. Especially in the past where graphics cards were much simpler or in the days that everything was done on the CPU often objects were ordered by distance to draw objects closer to the viewer first especially after Z-buffers were added into the mix. It meant the largest number of fragments could be discarded and you would save many costly calculations.

While this definately still holds true and it makes sense to draw particularly large things first (so for an FPS game, draw your ground first, your objects next and your sky last), todays graphics cards are multicore beasts making the most of parallel processing with the result that your carefully picked order of drawing objects may end up being completely distorted as polygons you've carefully put later on your list are drawn first by a core that happens to be idle at the time.
Sorting your objects to make sure you draw objects that use the same shader and same/similar textures together may result in better performance improvements then using a Z-sort.

So hope that clears up a few things that can be a little confusing.

I'll be back to my tutorials soon, I've finished about 95% of the 3D object loading code but I've yet to do a lot of cleanup of the code as it is a lot. I think I've nearly doubled the size of my example code :)
In the process I also found a few dumb mistakes, mentioned them in my previous post and a comment I added to the last part of the tutorial. I want to make sure the next version has as little bugs as possible :)

Thursday, 14 January 2016

Changed archive folder

I've changed the GitHub repository for my GLFW tutorial files so that the full source is stored in the archive folder for each part. I figured it would make it easier to have a look at the source code for each part this way.

It may take a bit of time before I fix all the download links in previous parts.

Click here to go to the archive

Wednesday, 13 January 2016

Loading models #1 (part 16)

Before we begin with this next session I found that there were three issues with the work so far that became apparent when I started adding a few more things.

The first is that I managed to remove the code that turned the Z-buffer on before rendering our 3D objects. As it is turned off before rendering our FPS counter that kinda broke.

The second is that inverting the modelview matrix and then transposing it does not seem to give a correct matrix usable for updating our normals. I've disabled that code for now and gone back to using the rotation part of our modelview matrix directly. As mentioned before, this will cause issues if non-uniform scaling is used but I'm not a big fan of that as it stands. I'll revisit it if I ever find a more trustworthy way of dealing with this.

The third is our specular highlight code in our fragment shader. I've added a slightly different calculation for this which basically does the same thing but I found it gave a slightly nicer highlight. 

Ok, back to the topic at hand. We want to start loading more complex 3D objects from disk. This is a fairly big topic so I'll be splitting it into a few parts. This first part is mostly preparation. We need a structure to load our 3D objects into so it's time to provide this structure.

When we look at an object, say we want to render a car, it isn't a single object. The body of the car will be separate from the wheels, and the windshield, etc. Often we're talking about a collection of objects. Some 3D formats separate this out nicely, others will share vertices between objects especially when the separation is purely due to different materials being used.
In our approach each of those sub-objects will be a separate entity and in this session we'll lay the foundation for that entity. For lack of a better term I've dubbed this a mesh and we'll start work on a new library called mesh3d.h again following our single file approach.

It's object orientation, sort-of...

 
Before we dive into our code I want to sidestep a little to look at a general approach that I've used in a few other places as well. I'm building everything in C but there are certain concepts in object orientation that I find hard to let go. When we look at our math3d library we've used structures for our different data containers and send a pointer to the structure we're modifying as the first parameter. In fact a language such as C++ pretty much works like that behind the scenes. When you call a method on an instance you're actually calling a function where a pointer to your instance is provided as a first 'hidden parameter' also known as the this pointer. The compiler simply does a lot of additional magic to make working with your object easier. But in essence there is little difference between:
vec2* vec2Set(vec2* pSet, MATH3D_FLOAT pX, MATH3D_FLOAT pY) {
  pSet->x = pX;
  pSet->y = pY;

  return pSet;
};
and
vec2* vec2::Set(MATH3D_FLOAT pX, MATH3D_FLOAT pY) {
  this->x = pX;
  this->y = pY;

  return this;
};
But for our vector and matrix structures we're not allocation any internal information and haven't got much need for implementing constructors and destructors. As we're often setting the entire array anyway its overkill to do so.

For our mesh this does start to become important as we'll be allocating buffers. We want to make sure our variables are properly initialized so we know whether buffers have been allocated, and we want to call a "destructor" to free up any memory that has been allocated.

Now here there is a choice to make, do we want to allow for using a variable directly (stack) or do we always want to allocate the entire object (heap). C++ solves this nicely for us either by just defining a variable from our class or by using the function new to allocate. If the stack is used C++ will automatically destruct the object.
But when we look at say objective-C we can see that pointers are solely used and we actually always perform the two needed steps, first calling alloc to allocate memory, and then init, our constructor. The thing here is that we know we also need to call release once the object is not longer used to free up the memory used (not withstanding any reference counting through retain, but that is another subject).

We don't have the luxury of the compiler making the right choice so for our mesh library I've decided to go down the "always use a pointer" route but provide a single constructor call that allocates and initializes our mesh (I may change our spritesheet and tilemap libraries to follow suit). As a result you must remember to call our free function (not C's) to properly depose of the object.

Our mesh library


For this write-up I'll explain our new mesh3d.h library, as it is right now, in detailed form. We'll repeat a few things as a result of this but I think it's important to go through it. We'll then modify our current example to use the new mesh library for rendering our cube. I'm also adding a sphere because it shows the shading a little better.

We've discussed the structure of a single file implementation before but just to quickly recap, basically we are combining our header and implementation into a single file instead of two separate files as is normal. To prevent code being compiled and included multiple times we only include the implementation if MESH_IMPLEMENTATION is defined. We do this in our main.c file before including our library.
Also our mesh library uses our opengl and math3d libraries but doesn't include it, assuming it has already been included previously.

We start by defining our structures:
// structure for our vertices
typedef struct vertex {
  vec3    V;          // position of our vertice (XYZ)
  vec3    N;          // normal of our vertice (XYZ)
  vec2    T;          // texture coordinates (XY)
} vertex;

// structure for encapsulating mesh data
typedef struct mesh3d {
  char          name[50];             /* name for this mesh */
  
  // mesh data
  GLuint        numVertices;          /* number of vertices in our object */
  GLuint        verticesSize;         /* size of our vertices array */
  vertex *      verticesData;         /* array with vertices, can be NULL once loaded into GPU memory */
  GLuint        numIndices;           /* number of indices in our object */
  GLuint        indicesSize;          /* size of our vertices array */
  GLuint *      indicesData;          /* array with indices, can be NULL once loaded into GPU memory */

  // GPU state
  GLuint        VAO;                  /* our vertex array object */
  GLuint        VBO[2];               /* our two vertex buffer objects */
} mesh3d;
Our vertex structure is the one we used before and simply combines our position, normal and texture coordinate vectors in a single entity.

Our object is defined through the mesh3d structure. This structure will grow over time but for now it contains:
  • name - the name of our mesh, handy once we start having more complex 3D objects
  • numVertices, verticesSize and verticesData, 3 variables that manage our vertex array while our mesh is loaded into normal memory
  • numIndices, indicesSize and indicesData, 3 variables that manage our index array while our mesh is loaded into normal memory
  • VAO and VBO, our two OpenGL variables for keeping track of our Vertex Array Object and two Vertex Buffer Objects which contain our mesh data once copied to our GPU
Next we forward declare our 'public' methods. Pretty straight forward that one.

After that we start our implementation section which, as mentioned, is only included if MESH_IMPLEMENTATION is defined.

First up is our callback to error handler for logging errors:
MeshError meshErrCallback = NULL;

// sets our error callback method
void meshSetErrorCallback(MeshError pCallback) {
  meshErrCallback = pCallback;
};
Next we include our first 'private' function which is meshInit. meshInit will be called by our 'constructor' to initialize all our variables:
// Initialize a new mesh that either has been allocated on the heap or allocated with
void meshInit(mesh3d * pMesh, GLuint pInitialVertices, GLuint pInitialIndices) {
  if (pMesh == NULL) {
    return;
  };
  
  strcpy(pMesh->name, "New");

  // init our vertices
  pMesh->numVertices = 0;
  pMesh->verticesData = pInitialVertices > 0 ? (vertex * ) malloc(sizeof(vertex) * pInitialVertices) : NULL;    
  pMesh->verticesSize = pMesh->verticesData != NULL ? pInitialVertices : 0;
  if ((pMesh->verticesData == NULL) && (pInitialVertices!=0)) {
    meshErrCallback(1, "Couldn''t allocate vertex array data");    
  };
  
  // init our indices
  pMesh->numIndices = 0;
  pMesh->indicesData = pInitialIndices > 0 ? (GLuint *) malloc (sizeof(GLuint) * pInitialIndices) : NULL;
  pMesh->indicesSize = pMesh->indicesData != NULL ? pInitialIndices : 0;
  if ((pMesh->indicesData == NULL) && (pInitialIndices!=0)) {
    meshErrCallback(2, "Couldn''t allocate index array data");    
  };
  
  pMesh->VAO = GL_UNDEF_OBJ;
  pMesh->VBO[0] = GL_UNDEF_OBJ;
  pMesh->VBO[1] = GL_UNDEF_OBJ;
};
Our two memory arrays for vertices and indices are allocated if pInitialVertices and/or pInitialIndices are non-zero. Important here is that our numVertices/numIndices tell us how many vertices and indices we have while verticesSize/indicesSize inform us how big our memory buffer is and how many vertices and indices we can thus still store in our arrays before running out of space.

Just jumping ahead a little here, numVertices/numIndices can still be used even if our arrays have been freed up. To save on memory we allow our buffers to be freed up once we copy our mesh data to our GPU but we still need to know these values.

As 0 is a valid value for either VAO or VBO we've declared a constant to know we haven't created these object in OpenGL and initialize them as such.

Next is our 'constructor' that returns and empty mesh object:
mesh3d * newMesh(GLuint pInitialVertices, GLuint pInitialIndices) {
  mesh3d * mesh = (mesh3d *) malloc(sizeof(mesh3d));
  if (mesh == NULL) {
    meshErrCallback(1, "Couldn''t allocate memory for mesh");        
  } else {
    meshInit(mesh, pInitialVertices, pInitialIndices);
  };
  return mesh;
};
This allocates a memory buffer large enough for our structure and then initializes the structure by calling meshInit.

We also need a 'destructor':
// frees up data and buffers associated with this mesh
void meshFree(mesh3d * pMesh) {
  if (pMesh == NULL) {
    return;
  };
  
  if (pMesh->verticesData != NULL) {
    free(pMesh->verticesData);
    pMesh->numVertices = 0;
    pMesh->verticesSize = 0;
    pMesh->verticesData = NULL;
  };

  if (pMesh->indicesData != NULL) {
    free(pMesh->indicesData);
    pMesh->numIndices = 0;
    pMesh->indicesSize = 0;
    pMesh->indicesData = NULL;
  };
  
  if (pMesh->VBO[0] != GL_UNDEF_OBJ) {
    // these are allocated in pairs so...
    glDeleteBuffers(2, pMesh->VBO);
    pMesh->VBO[0] = GL_UNDEF_OBJ;
    pMesh->VBO[1] = GL_UNDEF_OBJ;    
  };
  
  if (pMesh->VAO != GL_UNDEF_OBJ) {
    glDeleteVertexArrays(1, &(pMesh->VAO));    
    pMesh->VAO = GL_UNDEF_OBJ;
  };
  
  free(pMesh);
};
There is a bit more going on here as we free up any buffers we've allocated and tell OpenGL to delete our VBOs and VAO. While overkill we make sure we unset our variables as well.
Finally we free the memory related to our structure itself.

The next function adds a vertex to our vertex array:
// adds a vertex to our buffer and returns the index in our vertice buffer
// return GL_UNDEF_OBJ if we couldn't allocate memory
GLuint meshAddVertex(mesh3d * pMesh, const vertex * pVertex) {
  if (pMesh == NULL) {
    return GL_UNDEF_OBJ;
  };
  
  if (pMesh->verticesData == NULL) {
    pMesh->numVertices = 0;
    pMesh->verticesSize = BUFFER_EXPAND;
    pMesh->verticesData = (vertex *) malloc(sizeof(vertex) * pMesh->verticesSize);
  } else if (pMesh->verticesSize <= pMesh->numVertices + 1) {
    pMesh->verticesSize += BUFFER_EXPAND;
    pMesh->verticesData = (vertex *) realloc(pMesh->verticesData, sizeof(vertex) * pMesh->verticesSize);
  };

  if (pMesh->verticesData == NULL) {
    // something bad must have happened
    meshErrCallback(1, "Couldn''t allocate vertex array data");
    pMesh->numVertices = 0;
    pMesh->verticesSize = 0;
    return GL_UNDEF_OBJ;
  } else {
    memcpy(&(pMesh->verticesData[pMesh->numVertices]), pVertex, sizeof(vertex));
    
    return pMesh->numVertices++; /* this will return our current value of numVertices and then increase it! */
  };
};
It first checks if we have memory to store our vertex and allocates/expands our buffer if needed. Then it adds the vertex to our array.

We do the same for indices but add them 3 at a time (as we need 3 for every triangle):
// adds a face (3 indices into vertex array)
// returns false on failure
bool meshAddFace(mesh3d * pMesh, GLuint pA, GLuint pB, GLuint pC) {
  if (pMesh == NULL) {
    return false;
  };
  
  if (pMesh->indicesData == NULL) {
    pMesh->numIndices = 0;
    pMesh->indicesSize = BUFFER_EXPAND;
    pMesh->indicesData = (GLuint *) malloc(sizeof(GLuint) * pMesh->indicesSize);
  } else if (pMesh->indicesSize <= pMesh->numIndices + 3) {
    pMesh->indicesSize += BUFFER_EXPAND;
    pMesh->indicesData = (GLuint *) realloc(pMesh->indicesData, sizeof(GLuint) * pMesh->indicesSize);
  };

  if (pMesh->indicesData == NULL) {
    // something bad must have happened
    meshErrCallback(2, "Couldn''t allocate index array data");    
    pMesh->numIndices = 0;
    pMesh->indicesSize = 0;
    return false;
  } else {
    pMesh->indicesData[pMesh->numIndices++] = pA;
    pMesh->indicesData[pMesh->numIndices++] = pB;
    pMesh->indicesData[pMesh->numIndices++] = pC;
    
    return true;
  }; 
};

Now it's time to copy the data held within our arrays to our GPU:
// copies our vertex and index data to our GPU, creates/overwrites buffer objects as needed
// if pFreeBuffers is set to true our source data is freed up
// returns false on failure
bool meshCopyToGL(mesh3d * pMesh, bool pFreeBuffers) {
  if (pMesh == NULL) {
    return false;
  };
  
  // do we have data to load?
  if ((pMesh->numVertices == 0) || (pMesh->numIndices==0)) {
    meshErrCallback(3, "No data to copy to GL");    
    return false;
  };
  
  // make sure we have buffers
  if (pMesh->VBO[0] == GL_UNDEF_OBJ) {
    glGenVertexArrays(1, &(pMesh->VAO));
  };
  if (pMesh->VBO[0] == GL_UNDEF_OBJ) {
    glGenBuffers(2, pMesh->VBO);
  };
  
  // and load up our data
  // select our VAO
  glBindVertexArray(pMesh->VAO);
  
  // now load our vertices into our first VBO
  glBindBuffer(GL_ARRAY_BUFFER, pMesh->VBO[0]);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertex) * pMesh->numVertices, pMesh->verticesData, GL_STATIC_DRAW);
  
  // now we need to configure our attributes, we use one for our position and one for our color attribute 
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);
  glEnableVertexAttribArray(2);
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid *) 0);
  glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid *) sizeof(vec3));
  glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), (GLvoid *) sizeof(vec3) + sizeof(vec3));
  
  // now we load our indices into our second VBO
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, pMesh->VBO[1]);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * pMesh->numIndices, pMesh->indicesData, GL_STATIC_DRAW);
  
  // at this point in time our two buffers are bound to our vertex array so any time we bind our vertex array
  // our two buffers are bound aswell

  // and clear our selected vertex array object
  glBindVertexArray(0);
  
  if (pFreeBuffers) {
    free(pMesh->verticesData);
//    pMesh->numVertices = 0; // we do not reset this because we wish to remember how many vertices we've loaded into GPU memory
    pMesh->verticesSize = 0;
    pMesh->verticesData = NULL;

    free(pMesh->indicesData);
//    pMesh->numIndices = 0; // we do not reset this because we wish to remember how many indices we've loaded into GPU memory
    pMesh->indicesSize = 0;
    pMesh->indicesData = NULL;    
  };
  
  return true;
};
The code here is pretty much the same as it was in our previous tutorial but now copied into our library. We do reuse our VAO and VBOs if we already have them. At the end we optionally free up our arrays as we no longer need them. I've made this optional because for some effects we may wish to manipulate our mesh and copy an updated version to our GPU.

Now it's time to render our mesh:
// render our mesh
bool meshRender(mesh3d * pMesh) {
  if (pMesh == NULL) {
    return false;
  };
  
  if (pMesh->VAO == GL_UNDEF_OBJ) {
    meshErrCallback(4, "No VAO to render");    
    return false;
  } else if (pMesh->numIndices == 0) {
    meshErrCallback(5, "No data to render");    
    return false;
  };
  
  glBindVertexArray(pMesh->VAO);
  glDrawElements(GL_TRIANGLES, pMesh->numIndices, GL_UNSIGNED_INT, 0); 
  glBindVertexArray(0);
  
  return true;
};
Again this code should look familiar. We do not set up our shader nor matrices here, we assume that is handled from outside. While we'll add some material information to our mesh data later on that will be used by our shader moving this logic outside allows us to re-use our mesh for multiple purposes especially once we start looking at instancing meshes (not to be confused with instancing in OO terms).

This forms our entire mesh logic itself. For convinience I've added two support functions, one that loads our cube data into our object (meshMakeCube) and another which generates a sphere (meshMakeSphere). For these have a look at the original source code.
As time goes by I'll probably add additional primitives to our library as they can be very handy.

Putting our new library to use


Now it is time we change our example to use our new library. I've gutted all the code that generates the cube and loads it into GPU memory as we're now handling that in our our mesh3d object.
As mentioned I'm also showing a sphere for which I've add a nice little map of the earth as a texture (I'm afraid I'm not sure of the source of this image, might have come from NASA but I'm pretty sure it was made available to the public domain).

In our engine.h I've added a new enum entry in texture_types for this texture.

In our engine.c file we make a fair number of changes. First we define our global variables for our meshes:
mesh3d *      cube;
bool          canRenderCube = true;
mesh3d *      sphere;
bool          canRenderSphere = true;
Note the two canRender variables. If there is a problem loading our rendering our object it is likely it will be a problem for every frame. This will quickly clog up our logs and make it hard to find the problem. If rendering fails the first time we do not render the object again.

Next in engineSetErrorCallback we also register our callback for our mesh library.

Our load_objects function has slimmed down alot:
  ...
  cube = newMesh(24, 12);                     // init our cube with enough space for our buffers
  meshMakeCube(cube, 10.0, 10.0, 10.0);       // create our cube
  meshCopyToGL(cube, true);                   // copy our cube data to the GPU

  sphere = newMesh(100, 100);                 // init our sphere with default space for our buffers
  meshMakeSphere(sphere, 15.0);               // create our sphere
  meshCopyToGL(sphere, true);                 // copy our sphere data to the GPU
  ...
We also load our two texture maps here.

Now it's time to render our cube which we do in engineRender as before:
  // set our model matrix
  mat4Identity(&model);
  mat4Translate(&model, vec3Set(&tmpvector, -10.0, 0.0, 0.0));

  // select our shader
  shaderSelectProgram(shaderInfo, &projection, &view, &model);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, textures[TEXT_BOXTEXTURE]);
  glUniform1i(boxTextureId, 0);
  glUniform3f(lightPosId, sunvector.x, sunvector.y, sunvector.z);   

  // now render our cube
  if (canRenderCube) {
    canRenderCube = meshRender(cube);    
  };

We repeat the same code for our sphere but using our sphere mesh and a slightly different model matrix and voila, we have a cube and a sphere:

Download the source here

What's next

Now that we have a structure into which we can load meshes it is time to start loading some. While we'll take it in steps next session we'll load an object from disk consisting of multiple meshes but leaving out any material information.




Sunday, 10 January 2016

Adding a camera (part 15)

So we've looked at our model matrix which positions our object within our 3D space and we've looked at our projection matrix which defines our lens in a manner of speaking. The matrix we've so far left alone is our view matrix.

Our view matrix in essence defines the position and orientation of our camera in our 3D world. Well to be precise, it defines the inverse of that because we actually end up moving the entire 3D world to make our camera the center of this world.


Modifying the view matrix directly


Our first option therefor is to modify the view matrix directly. We could simply apply matrix operations onto this matrix to get the camera to move through space. We thus need to do the opposite of what we intent to do. Move the camera 10 units forward? Apply a translation to our matrix to move the world 10 units back. Rotate the camera 10 degrees to the left? We need to rotate our matrix 10 degrees to the right. Etc.

Align our view matrix with another object


Our second option is a nice one for certain types of games such as racing games. When we are sitting in the drivers seat of a car our camera is basically inside of that car. If we take the model matrix for our racing car, add a translation to move the center where the driver would be, and then inverse that matrix, we have a view matrix looking nicely out of the cars windshield. This is assuming the model of the car is properly oriented so we're not looking through the side window or the floor but that is easy to rectify.

The good old 'lookat' matrix


But the option we'll look at today is using a look-at calculation. This is a function that was available in the standard glu library and I've added to my math3d.h implementation.

The look-at calculation takes the location of your camera (or eye), the location you are looking at, and what you consider to be "up" and applies a proper view matrix.

Our changes are fairly simple. First we add our lookat and position variables to track our camera (for now globals to keep things easy):
// our camera (and view matrix)
mat4          view;
vec3          camera_eye = { 10.0, 20.0, 30.0 };
vec3          camera_lookat =  { 0.0, 0.0, 0.0 };
Next we initialize our view matrix using our lookup function in our engineLoad function:
  // init our view matrix
  mat4Identity(&view);
  mat4LookAt(&view, &camera_eye, &camera_lookat, vec3Set(&upvector, 0.0, 1.0, 0.0));  
And that's it.

I've also removed the code that rotates the box and instead added this code to our engineUpdate function to allow basic interaction with the camera using the WASD keys:
  vec3  avector, bvector, upvector;
  mat4  M;
    
  // handle our keys....
  if (engineKeyPressedCallback(GLFW_KEY_A)) {
    // rotate position left
    
    // get our (reverse) looking direction vector
    vec3Copy(&avector, &camera_eye);
    vec3Sub(&avector, &camera_lookat);
    
    // rotate our looking direction vector around our up vector
    mat4Identity(&M);
    mat4Rotate(&M, 1.0, vec3Set(&bvector, view.m[0][1], view.m[1][1], view.m[2][1]));
    
    // and update our eye position accordingly
    mat4ApplyToVec3(&camera_eye, &avector, &M);
    vec3Add(&camera_eye, &camera_lookat);
  } else if (engineKeyPressedCallback(GLFW_KEY_D)) {
    // rotate position right
    
    // get our (reverse) looking direction vector
    vec3Copy(&avector, &camera_eye);
    vec3Sub(&avector, &camera_lookat);
    
    // rotate our looking direction vector around our up vector
    mat4Identity(&M);
    mat4Rotate(&M, -1.0, vec3Set(&bvector, view.m[0][1], view.m[1][1], view.m[2][1]));
    
    // and update our eye position accordingly
    mat4ApplyToVec3(&camera_eye, &avector, &M);
    vec3Add(&camera_eye, &camera_lookat);
  } else if (engineKeyPressedCallback(GLFW_KEY_W)) {
    // get our (reverse) looking direction vector
    vec3Copy(&avector, &camera_eye);
    vec3Sub(&avector, &camera_lookat);
    
    // rotate our looking direction vector around our right vector
    mat4Identity(&M);
    mat4Rotate(&M, 1.0, vec3Set(&bvector, view.m[0][0], view.m[1][0], view.m[2][0]));
    
    // and update our eye position accordingly
    mat4ApplyToVec3(&camera_eye, &avector, &M);
    vec3Add(&camera_eye, &camera_lookat);    
  } else if (engineKeyPressedCallback(GLFW_KEY_S)) {
    // get our (reverse) looking direction vector
    vec3Copy(&avector, &camera_eye);
    vec3Sub(&avector, &camera_lookat);
    
    // rotate our looking direction vector around our right vector
    mat4Identity(&M);
    mat4Rotate(&M, -1.0, vec3Set(&bvector, view.m[0][0], view.m[1][0], view.m[2][0]));
    
    // and update our eye position accordingly
    mat4ApplyToVec3(&camera_eye, &avector, &M);
    vec3Add(&camera_eye, &camera_lookat);    
  };

  // update our view matrix
  mat4Identity(&view);
  mat4LookAt(&view, &camera_eye, &camera_lookat, vec3Set(&upvector, 0.0, 1.0, 0.0));
Note that I am using my current view matrix to determine what my current up and right are as far as my camera is concerned and rotate around those vectors.

Well that was quick:)

Download the source here

What's next


Now that we have a camera that we can move around, it's time to start loading more complex objects instead of our hard coded cube...

Tuesday, 5 January 2016

Basic lighting (part 14)

We're slowly getting through the boring stuff, next up is basic lighting. I'm keeping this simple with a single light source for now, we'll look at more complex lighting at some later time.

First lets go through a tiny bit of theory. Our basic lighting model will implement three types of lighting: ambient, diffuse and specular lighting.
This type of shading is known as Phong Shading

The ambient part of our lighting model is the easy part. Imagine if you will you are in a dark room and you turn on a single light that illuminate the entire room. Now hold up an object like a book and look at the side that is not facing the light. Even though no light hits the object directly from our light source you would expect it to be completely in shadow and black. Yet you'll still see its colors. This is because the light in the room bounces off all the walls and other surfaces and the object becomes lighted indirectly.
To accurately calculate this is a complex task but in computer graphics we take a rather simple shortcut by stating the object is always illuminated by a fixed amount.

The diffuse part of our lighting model requires a bit more calculation. When light hits a surface it scatters. Depending on the type of surface it can be reflected in all directions relatively evenly (and give a nice solid appearance) or be reflected in roughly a single direction (making it a mirror). More complex surfaces might even react differently for different wave lengths.
For our lighting we assume the first situation, the light gets reflected in all directions evenly. The intensity of the light being reflected is a factor of the angle at which the light hits the surface from reflecting no light if the light travels parallel to the surface to full intensity if the light hits the surface at a perfect 90 degree angle.
In order to calculate this we use the normal vector of the surface. The normal vector is a vector that is perpendicular to the surface and we simply calculate the angle between the normal vector and the vector pointing from the surface to the light source. By taking the cosine of this angle we have a great variable that is 1.0 at full illumination, 0.0 when the light travels parallel to our surface and is negative if the light lies behind the surface and we're thus in shadow.
Luckily for us calculating the cosine of a surface is incredibly simple as this happens to be the dot product of two unit vectors.  So we normalize our normal vector and light direction vector and call this handy function called dot().

The specular part of our lighting is the most complex one presented here. Here we are looking at light reflecting off the surface in a specific or narrow direction. If the light reflects towards our viewpoint we see its reflection. Our normal vector again is used but now to determine the vector of the light reflected off of our surface, we have a nice function called reflect() for this.
We again calculate a cosine but now using our reflected light vector and a vector from our eye to the surface.

Because our specular calculation needs to calculating a vector from our eye/camera/viewport it makes sense to do all lighting calculations after we apply our model and view matrices but not our projection matrix. As long as we also adjust the position of our light using our view matrix we're all set.

Normal vectors


As we discussed above we need the normal vector of our surface to perform our lighting calculations. For our cube this is incredibly simple, as each face of our cube is a flat surface the normal vector applies to the entire face and can be easily calculated using the cross product of two edge vectors of our face.

But for more complex shapes this becomes a lot more complex. Also when we look at curved surfaces, even though we're rendering them with flat triangles, we can interpolate the normals between each vertex to create the illusion of a curved surface (we'll see an example of this later). For this reason OpenGL assumes we store a normal for each vertex and just like with texture mapping we have to duplicate vertexes when used for different faces with different normals.

As all this can be relatively complex it makes a lot of sense to calculate all the normals one time and store them for each vertex. To be exact, most 3D modeling software does this for us and handily store all the normals along with the model.

I've adjusted our vertex structure and vertex array to include our normals for our cube:
// we define a structure for our vertices
typedef struct vertex {
  vec3    V;          // position of our vertice (XYZ)
  vec3    N;          // normal of our vertice (XYZ)
  vec2    T;          // texture coordinates (XY)
} vertex;

vertex vertices[] = {
  // front
  -0.5,  0.5,  0.5,  0.0,  0.0,  1.0, 1.0 / 3.0,       0.0,          // vertex 0
   0.5,  0.5,  0.5,  0.0,  0.0,  1.0, 2.0 / 3.0,       0.0,          // vertex 1
   0.5, -0.5,  0.5,  0.0,  0.0,  1.0, 2.0 / 3.0, 1.0 / 4.0,          // vertex 2
  -0.5, -0.5,  0.5,  0.0,  0.0,  1.0, 1.0 / 3.0, 1.0 / 4.0,          // vertex 3

  // back
   0.5,  0.5, -0.5,  0.0,  0.0, -1.0, 1.0 / 3.0, 1.0 / 2.0,          // vertex 4
  -0.5,  0.5, -0.5,  0.0,  0.0, -1.0, 2.0 / 3.0, 1.0 / 2.0,          // vertex 5
  -0.5, -0.5, -0.5,  0.0,  0.0, -1.0, 2.0 / 3.0, 3.0 / 4.0,          // vertex 6
   0.5, -0.5, -0.5,  0.0,  0.0, -1.0, 1.0 / 3.0, 3.0 / 4.0,          // vertex 7
   
   // left
  -0.5,  0.5, -0.5, -1.0,  0.0,  0.0, 1.0 / 3.0, 1.0 / 4.0,          // vertex 8  (5)
  -0.5,  0.5,  0.5, -1.0,  0.0,  0.0, 2.0 / 3.0, 1.0 / 4.0,          // vertex 9  (0)
  -0.5, -0.5,  0.5, -1.0,  0.0,  0.0, 2.0 / 3.0, 2.0 / 4.0,          // vertex 10 (3)
  -0.5, -0.5, -0.5, -1.0,  0.0,  0.0, 1.0 / 3.0, 2.0 / 4.0,          // vertex 11 (6)

  // right
   0.5,  0.5,  0.5,  1.0,  0.0,  0.0, 1.0 / 3.0, 1.0 / 4.0,          // vertex 12 (1)
   0.5,  0.5, -0.5,  1.0,  0.0,  0.0, 2.0 / 3.0, 1.0 / 4.0,          // vertex 13 (4)
   0.5, -0.5, -0.5,  1.0,  0.0,  0.0, 2.0 / 3.0, 2.0 / 4.0,          // vertex 14 (7)
   0.5, -0.5,  0.5,  1.0,  0.0,  0.0, 1.0 / 3.0, 2.0 / 4.0,          // vertex 15 (2)

  // top
  -0.5,  0.5, -0.5,  0.0,  1.0,  0.0,       0.0,       0.0,          // vertex 16 (5)
   0.5,  0.5, -0.5,  0.0,  1.0,  0.0, 1.0 / 3.0,       0.0,          // vertex 17 (4)
   0.5,  0.5,  0.5,  0.0,  1.0,  0.0, 1.0 / 3.0, 1.0 / 4.0,          // vertex 18 (1)
  -0.5,  0.5,  0.5,  0.0,  1.0,  0.0,       0.0, 1.0 / 4.0,          // vertex 19 (0)

  // bottom
  -0.5, -0.5,  0.5,  0.0, -1.0,  0.0, 2.0 / 3.0,       0.0,          // vertex 20 (3)
   0.5, -0.5,  0.5,  0.0, -1.0,  0.0, 3.0 / 3.0,       0.0,          // vertex 21 (2)
   0.5, -0.5, -0.5,  0.0, -1.0,  0.0, 3.0 / 3.0, 1.0 / 4.0,          // vertex 22 (7)
  -0.5, -0.5, -0.5,  0.0, -1.0,  0.0, 2.0 / 3.0, 1.0 / 4.0,          // vertex 23 (6)
};
Note that we change our attributes so that attribute 0 remains our position, 1 becomes our normal and 2 is now our texture coordinate.

Matrices


First lets revisit our matrices for a minute. So far we've calculated our model-view-projection matrix as that is all we needed but for our light calculations we need a few more. We need our model-view matrix and we need what is called our normal matrix.

The normal matrix is a matrix that only applies our rotation and can be used to update the direction of our normal vectors.

Because we adjust everything to our view matrix we also apply our view matrix to our normal matrix. The easiest way to get your normal matrix is to take your model-view matrix and take the inner 3x3 matrix from it. This is what I normally do but it does create some issues when your model applies a non-uniform scale. Now I read somewhere that the solution is to take the inverse of your matrix and then transpose it. Since we may need the inverse of our matrix later on I've gone down this route for now but I may revert to just using the model-view matrix directly as calculating the inverse of a matrix is costly as I rarely use non-uniform scaling anyway.

Anyway, because we'll be doing these calculations a lot I've added a structure to shader.h that can store IDs for the most applicable matrices:
// typedef to obtain standard information, note that not all ids need to be present
typedef struct shaderStdInfo {
  GLuint  program;
  GLint   projectionMatrixId;       // our projection matrix
  GLint   viewMatrixId;             // our view matrix
  GLint   modelMatrixId;            // our model matrix
  GLint   modelViewMatrixId;        // our model view matrix
  GLint   modelViewInverseId;       // inverse of our model view matrix
  GLint   normalMatrixId;           // matrix to apply to our normals
  GLint   mvpId;                    // our model view projection matrix
} shaderStdInfo;
There is also a function called shaderGetStdInfo() that populates this structure.
Finally I've added a function called shaderSelectProgram() that binds the shader program referenced by our structure and then calculates and applies all our matrices from a model, view and projection matrix that is passed to it.

What is very important to know is that GLSL will remove any uniform that isn't actually used in the source code so there is no use defining say modelViewInverse if you're not using it.
While the code logs that it doesn't exist shaderSelectProgram() simply skips those.

There is a way to use a VBO to load all matrices in one go which I may look into at a later date.

Our load_shaders function in engine.c now calls our shaderGetStdInfo function. We still have two uniforms that fall outside of our structure: our light position and our texture sampler (and there are several other uniforms in the shader.

Our engineRender function similarly now calls shaderSelectProgram. 

Our new shaders


The real magic however happens inside of our shaders. The changes to our vertex shader are very straight forward. First we now have an attribute for our normals.
We also have 2 new outputs, one for our position (V) and one for our normal (N). For both the correct matrix is applied.

Our fragment shader has grown substantially, we'll look at each part individually:
#version 330

// info about our light
uniform vec3      lightPos;                         // position of our light after view matrix was applied
uniform float     ambient = 0.3;                    // ambient factor
uniform vec3      lightcol = vec3(1.0, 1.0, 1.0);   // color of the light of our sun

// info about our material
uniform sampler2D boxtexture;                       // our texture map
uniform float     shininess = 50.0;                 // shininess

in vec4           V;                                // position of fragment after modelView matrix was applied
in vec3           N;                                // normal vector for our fragment
in vec2           T;                                // coordinates for this fragment within our texture map
out vec4          fragcolor;                        // our output color
We have a couple of new uniforms. Note that we've added default values for a couple of them so you can set them from code but don't have to:
  • lightPos - the position of our lightsource with view matrix applied to it
  • ambient - our ambient factor, our default is 30%
  • lightcol - the color of our light source, white for now
  • boxtexture - we had this one already, our texture sampler
  • shininess - the shininess for our specular lighting
Our input variables also match the output variables of our vertex shader.
void main() {
  // start by getting our color from our texture
  fragcolor = texture(boxtexture, T);  
  if (fragcolor.a < 0.5) {
    discard;
  };
  
  // Get the normalized directional vector between our surface position and our light position
  vec3 L = normalize(lightPos - V.xyz);
We start by getting our color from our texture map as before. Then we calculate vector L as a normalized directional vector that points from our surface to our lightsource. This vector we'll need in both our diffuse and specular calculations.
  // We calculate our ambient color
  vec3  ambientColor = fragcolor.rgb * lightcol * ambient;
This is our ambient color calculation, we simply multiply our surface color with our light color and our ambient factor.
  // We calculate our diffuse color, we calculate our dot product between our normal and light
  // direction, note that both were adjusted by our view matrix so they should nicely line up
  float NdotL = max(0.0, dot(N, L));
  
  // and calculate our color after lighting is applied
  vec3 diffuseColor = fragcolor.rgb * lightcol * (1.0 - ambient) * NdotL; 
For our diffuse color we first calculate our dot product for our normal and light vector.
We then multiply our surface color with our light color and our dot product taking out the ambient factor we've already used.
  // now for our specular lighting
  vec3 specColor = vec3(0.0);
  if ((NdotL != 0.0) && (shininess != 0.0)) {
    vec3 R = reflect(-L, N);
    float VdotR = max(0.0, dot(normalize(-V.xyz), R));
    float specPower = pow(VdotR, shininess);
  
    specColor = lightcol * specPower;
  }; 
For our specular highlight we first calculate our reflection vector then calculate our dot product using our position.
Finally we apply our shininess using the power function and multiply the outcome with our light color. The higher the shininess value the smaller our reflection.
We only do this if we have a shininess value and if we're not in shadow.
Note that we do not apply our texture color here because we are reflecting our light. An additional specular color related to our surface material may be applied here or even a separate specular texture map but I've left that out in our example.
  // and add them all together
  fragcolor = vec4(clamp(ambientColor+diffuseColor+specColor, 0.0, 1.0), 1.0);
The last step is to add all these colors together. The clamp function we call here makes sure our color does not overflow.

And the end result:
A box really is a terrible shape to show off the lighting, as the normals for each face are all parallel we're basically 'flat shading' this cube. Once we start loading more complex shapes it should look a lot nicer. Also with the specular highlighting implemented the way it is the box has a mirror finish, not really something suitable for cardboard.

Download the source here

So where from here?


You could easily extent the shader to allow for more then one lightsource by simply repeating the ambient, diffuse and specular lighting calculations for those other light sources and just adding the results together. There are a number of additional parameters that you could add to improve on this One I already mentioned is the surface materials specular color. Another that is relatively simple to add are restraints on the angle to the lightsource and a limit to the distance to the lightsource to create a spotlight type light source.

There are many other shader techniques that are worth learning:
Bump mapping or normal mapping which is a technique to simulate groves and imperfections on the surface by adjusting the surface normal. This can be achieved by using a texture encoding normals that you look up instead of using our vertex normals.
Environment mapping is a cool way to do reflections or to solve our ambient issue discussed above. Ever looked at a CGI movies documentary and wondered why they hold up a mirror ball and take a picture? Use that picture as a texture and use your normal vectors X and Y (ignoring it's Z) as a lookup (adjusted so 0.0 is at the center of that image) and voila.
Shadow maps, so objects cast shadows onto other object, but that's a topic for later.

What's next


Now that we've got our basic rendering of 3D objects all sorted the next quick detour will be looking at moving the camera around.
We also need to write something that will let us load 3D objects from disk instead of hardcoding them in source code.
So that will be our next two subjects, hopefully after that we'll jump back into our platform game.