A Simple 2D Graphics Engine Powered by OpenGL/GLUT.

In the course of coding up my ideas on the Voroni Graph, it became apparent that a CLI wasn't going to cut it. I needed some sort of graphical library, both for the final visualization, and for debugging. For this, I've chosen to use OpenGL with my own wrappers on top. This is a log of what I've been working on for the past few evenings.

This is, without the slightest doubt, overkill for what I'm doing right now (2D, very few colors, order of 100 different positions.). Screw that, I'm using OpenGL anyways. Why? I want the experience, and I want to write my own graphics engines that won't be limited by the underlying platform.

In any case, the process of getting to where I am now has been fairly frustrating. The documentation for all sorts of things involving OpenGL on the web is scant and often contradictory. Some things didn't work, other important aspects of a simple program had been depreciated. Getting even a blank screen to compile was a significant achievement.

In any case, this was all difficult enough to warrant a post and some preliminary code. Below is a sort of black box on the whole process, written as I worked through everything. For this reason, please excuse the weird tenses.


The install

I'm running Ubuntu 11.10 Oneiric Ocelot. If you're running anything else, you're pretty much on your own. Sorry, but like I said, this seems to be complicated.

You'll need build-essentials / g++ or a substitute.

sudo apt-get install build-essentials

You'll need glut and gl dev versions.

sudo apt-get install freeglut3 freeglut3-dev

If you're running a new version of Ubuntu ( >= 11.10 apparently, when the linking problem this fixes went into effect ) you'll also need to get the package below.

sudo apt-get install binutils-gold

After all this, I had no further problems. If you run into errors further down the road, Google is your friend (or, in the case of OpenGL, your somewhat grudging acquaintance).




At this point, I sat down and started to write out a list of things I'd want out of my Engine, in an ideal world.
  • I want to use this engine to do visualizations of algorithms, many of which may very well not be real time.
  • I don't want to have to structure my program around the visualization.
  • I want to write something that can work equally well in console or GUI.
  • Ideally, I want the guts of the engine to run in it's own process, and not depend on the rest of the program at all.
  • I want a high level interface to my Geometry classes, and I don't want to have to modify these classes to use a GUI.
  • I want to be able to transfer a lot of the framework from this 2D engine to 3D when the time comes.
From these, we get a few hints about our design.
  • The engine should be a module, possibly in it's own namespace, possibly (shudder) a Singleton. This will make porting it over to it's own process later much easier.
  • The engine will need to keep track of all the objects it's rendering at a given time, so that it can render asynchronously from whatever process is populating it.
  • The engine needs to rely on the geometry module, possibly pretty heavily. However, beyond high level design, the converse should not be true. The geometry module should NOT in any way require the graphics engine.
With these in mind, it's now possible to start laying out a preliminary structure in code. We'll have a GraphicsEngine class, which to begin with, I'll treat like a Singleton by instantiating a single global instance (which will be a pointer).


From there, I proceed by my favorite method: namely coding by re-factoring. I start with the 2D example above, and start to move code around, making sure everything still works every couple of minutes. Pretty early on, I imported the Vector class I wrote for making Lines ( Vector represents points in R^2. It is not related to stl::vector, which I also use quite a bit. ), and switch everything over to that. I made a Color class that holds RGB values, and switch color related code over to that. I made naive structs for triangles, line segments, and polygons, and switch over to those. I keep in mind that all these structs will eventually be classes in Geometry, and I should not specialize them for graphics in any way.

Now, everything I've worked on got wrapped into GraphicsEngine as a class, and I put in a quick global GraphicsEngine* refrence for testing. Drawing a cyan triangle looked like this:
GRAPHICS->setColor( CYAN );
[Initiate Triangle* triangle]
GRAPHICS->renderTriangle( triangle );
A few things of note. Of course, this code is completely synchronous. The triangle is remade each time the screen refreshes. Also, the color is set independently of the triangle. Most of this code creates the triangle, and that won't stay constant at all, so take it with a grain of salt.

After this, I took a slight break from the graphics engine to flesh out LineSegment into a proper class, and I changed my test scene slightly based on the new LineSegment class and a few other factors not worth mentioning. Mostly my own personal sense of what looked neat. LineSegment also got it's own file (linesegment.h).

Then, I moved tons of code internal to GraphicsEngine, and made everything static. Importantly, the render function is now a private member function of GraphicsEngine. 

I think, in an ideal world, I really ought to be using a namespace at this point instead of a giant static class. Nonetheless I like to think of the GraphicsEngine as an object, not just a collection of functions. Also, more practically, I want to have private members of GraphicsEngines, and I don't think that there's a way to do that with namespaces. Correct me if I'm wrong.

Notably, I do not know how to do anything besides refresh forever at this time. I'm going to work on that after implementing a few more basic features.

I then proceeded to add a GraphicsObject class private to the Graphics Engine. This class contains a void* to the object data, and an enum (GraphicsType, also internal and private.) which indicates the type. A static private member GraphicsEngine::objects was added to hold various GraphicsObject instances. The render function was also updated to render objects from GraphicsEngine::objects, render functions were changed to private scope, and a number of overloaded (and public) add( Object ) functions were added.

Next, a camera Transform was added. This transform is applied to every point when it is drawn. As a proof of concept, this transform was set to cause a slow rotation ( 0.001 radians per rendered frame ).

 Test scene with regular polygons and points.

Random graph with 100 points and 50 edges.

Right now, I'm working on rescaling the graphics as window sizes change. Also, I'm preparing to push everything to a GitHub project and link that here. In the meantime, if you notice me doing something stupid, let me know!


No comments:

Post a Comment