Tuesday, November 13, 2012

A Guide to Instanced Geometry

As stated in the title, this is more of a guide than a tutorial. It isn't for the complete beginner, some experience is necessary. Throughout this I assume you have a general knowledge of what a mesh is, basic understanding of how matrices work, basic knowledge of GLSL, and experience with C++ or another object-oriented language. I am just writing about my own personal implementation of instanced geometry, which is definitely open for comments and suggestions. The code snippets are stripped down versions directly from my basic 2D OpenGL rendering engine I have dubbed IronClad.

What Is Instanced Geometry?

Primitive rendering techniques have many copies of a single object's data. Say you wanted to draw a tiled map, with 2 unique tiles. Say, for instance (no pun intended), a floor tile and a wall tile. Now, each of these objects contains, at the very least, 8 floats for vertex positions, and 8 floats for texture coordinates. That's 8 * 4 + 8 * 4 = 64 bytes. If each instance of a tile contains this information for rendering, and you have 1000 wall tiles, that's 64 kilobytes of memory! And that's not even considering the other tile types. Obviously, this is an example of a very simple mesh with only 4 vertices. Most games have models with hundreds if not thousands of vertices, so you can see why it'd be a serious problem to have multiple copies of that data.

Of course, there's a simple solution to this problem; you keep around one copy of the data in the first object you create, and the other objects simply refer to the original, just in their own position. Well, that's exactly where instancing comes in!

Friday, October 19, 2012

Development On Hold

Throughout the course of development for Collapse, I had been relying on OpenGL's age-old immediate mode for rendering. This proved to be a serious hindrance as I kept adding more advanced features such as lighting and shadows. My previous post about shadows works extremely well when shadows are limited to a single light. Clearly this is unacceptable, and the various techniques I tried to get it functioning with many lights either proved to be much too slow in immediate mode, or would require a serious amount of restructuring of the rendering system.

So, I put everything on hold temporarily and decided to learn modern OpenGL. I found a superb series of tutorials through StumbleUpon, and have been following them to gain a good base in the modern features of OpenGL, such as vertex array objects, matrices, and indexed geometry.

This task has sparked the development of a 2D engine, which I have dubbed IronClad. I've made a lot of progress in the past few weeks, implementing a VAO wrapper, support for a custom mesh file format, indexed geometry, shaders, and other nice features. I have also made a switch from SDL to GLFW for window management, and abandoned SDL_ttf in favor of FTGL. The reason for this is that the latest stable version of SDL only supports OpenGL contexts using versions <= 2.1, and I need more than that. The latest development build, SDL 2.0, supports the versions I need, but I'd most likely need a serious refactoring of my SDL code base to support it, so I decided to just make the switch to something OpenGL-specific.

Saturday, September 29, 2012

Working With Shadows

The logical course of action after finishing lighting is to move on to shadows. I opted for a relatively simple, software-based approach. It involves casting rays from the light source to individual edges of the tiles in the collision map, and drawing black quads based on the rays.


In my current implementation, the shadows are independent of the other lights. Thus, shadows cast by one light will be left untouched if there is another light in the way. This obviously causes problems, and a better method that will calculate cumulative shadows is currently in progress. But for a single light, the following algorithm suffices. Hopefully some OpenGL beginners trying to create shadows can gain some knowledge from my own trial-and-error!

Thursday, September 27, 2012

Update News

The latest version has several new features, including:

  • Inventory screen (work-in-progress) that contains player stats such as health, ammo, and other detailed information about game progress. I still need my artist to draw me up large images of the tank and weapons.
  • Rasterizer system was completely removed, as it ended up being extremely complicated and not versatile at all. Also std::map would automatically sort entities, causing problems with render order.
  • Weapons are added, with a simple weapon editor script to generate the files. This allows lots of weapons to be created, edited, and tweaked easily, without relying on tedious recompilation.
  • A generic asset manager has replaced the stupid system I used previously, which was one manager for fonts, one for textures, and one for audio. Now, the asset manager just handles a single CAsset base class.
  • The CBullet class now inherits from a generic CParticle which should later allow particle effects to be implemented easily.

Latest screenshots:
In-game, with new shader.
It looks the same because I haven't included lights within the level yet.

Inventory screen (no weapon artwork)


Friday, September 21, 2012

Let There Be Light!

"And George said unto Collapse, "let there be light," and there was, and George saw that it was good."

Edit (2.1.2012): My lighting shader has changed quite a bit since this post; I opted for a multi-pass shader, rather than maxing out the shader variables.

After several weeks of reading about shaders, learning GLSL, learning about lighting, observing other projects, finally writing my own, and spending hours debugging, tweaking, and improving, it's finally done. My shader supports multiple lights, and gets re-written on the fly to support larger and larger amounts, due to GLSL loop limitations. Whenever I want to increase the amount of lights I use, I merely say:

    LightingShader.SetMacro("NUM_LIGHTS", ++lights);

Which will then re-write, re-compile, and re-link the shader with a new light count. Though I was considering not releasing the shader source code, here it is anyway:


Here are some screen-shots of lighting:


Original shader - one light (in-game)

Final shader - multiple lights (test zone)

Monday, September 10, 2012

The Show Goes On!

Fear not, the project has not been abandoned! The incredible Arma II zombie mod Day-Z has been sucking away a lot of my free time lately, so you guys must excuse my lack of new posts. Despite that time-wasting haven, I managed to make a significant amount of progress in Collapse:

  • AI prototyping complete
  • Revamped level editor with support for player spawns and fine-grained control over AI behaviors
  • Rasterizer for versatility in adding shaders to textures
  • Several memory leaks fixed, several new ones created
  • Debug logging system integrated into a majority of the subsystems
  • Settings.ini file for control over file names and directories without recompilation
  • Re-designed class hierarchy
  • Asset managers for texture, audio, and fonts
  • Replacing SDL_mixer with OpenAL
  • Removal of the "library" naming system in favor of namespaces
  • System requirements checking on launch (just OpenGL > v2.1 so far)

Thursday, July 19, 2012

AI Experimentation

Collapse is a game set in a post-apocalyptic world in which a human-created AI faction known as the Mechs have taken over. Therefore, AI must be an essential part of the programming.
After extensive research in path-finding algorithms, I decided upon the ubiquitous A*. Then, after days of trying to implement it, using many different techniques from just simple std::vector<Node*> lists to a much more complex and efficient std::priority_queue<Node> binary heap, I gave up. None of them worked properly, obviously due to faults in my programming, but I simple couldn't figure it out.
So I settled on a different approach: pre-defined movement paths. Obviously, this would significantly dumb-down AI decision-making, but I had to at least get something working. The map worked fine, but, again, after days of tweaking and planning and modifying, I couldn't even get the enemy tank to follow the path. I used a rather strange approach with lots of vector math and collision detection, which probably wasn't necessary. The algorithm (in pseudo-code) looked something like this:

Spawn:
    Target = FindNearestPath
    End

Update:
    If reached Target
        Target = FindNextTile
        If no Target
            Turn 3 degrees
    Else
        Adjust angle and drive.
    End

FindNearestPath:
    Find tile with minimum distance to entity.
    Return tile

FindNextTile:
    For each tile in AI map
        If tile collides with line-of-sight
          and tile not collides with entity
            Return tile
    Return no tile