Years ago, for a university project, I made a minecraft-like game in C with some already-very-dated OpenGL toolkits and versions. It was a color-by-numbers affair: the professor would give us a big code dump and we’d hack on it and add some features, then the next assignment the professor would give us the same code as before but with more in it, and we’d hack on it and add some features. Everyone ended up with something that roughly looked and felt like minecraft. And for the record, the new code we were given each time around was atrociously bad.
I plopped my project up on github and left it behind in ~2011, briefly returning in ~2018 or so out of curiosity. It was in a state where it rendered ~5 frames per second, motion looked laggy and stilted, digging meant a random block somewhere around the user might disappear, the lighting as the player moved around or the sun moved through the sky constantly broke and flickered, mobs (monster objects) under the earth didn’t appear correctly at all, and multiple users could connect to a server over the network, but they could not see each other or interact in any real way. Also, the mouse would jump around wildly when you first loaded the game or if you minimized it. And the mouse doesn’t capture in the window. And there were no textures, and all the code was randomly formatted.
So, it was a bit of a fixer-upper! It did still mostly compile, and it roughly looked like Minecraft, in that it was a bunch of grass-colored cubes shaped by Perlin noise, with an underground cave system.
During a recent extended stint of free time, known colloquially as funemployment, I decided to pick this old project back up and see what I could do with it. So far, I’ve:
- fixed the framerate issues (turns out we weren’t culling right)
- fixed the lighting flickers (something with normals. I pretty much just turned off specular light.)
- learned enough about raycasting from javidx9 to fix digging, so players dig the blocks they’re looking at
- added a simple super hacky crosshair (a few quads in an orthographic matrix view!)
- made networked players able to see each other as they move around, and fixed a lot of little networking bits around e.g. what happens when players come and go from a server
- formatted all the code using an automated tool, AStyle (which takes some hacks to make work on Arm64 macs in sublime!)
- made motion and falling much smoother, based around acceleration instead of per-update direct changes, invoked upon each keyboard-polling function call, at whatever speed the OS registers repeated key-presses and ONLY when the OS has registered keypresses
- mobs are now actually visible, rendering in the right space, and wander around in the caves bumping off of walls
- fixed up some major, wacky inconsistencies in the co-ordinate system like some co-ords being stored as negative values and some others as positive (this is sometimes known as “the camera problem” in graphics — to move the “player”/camera X units in a direction through the world, the programmer really moves the world itself X units past the player in the opposite direction. This lends itself to storing the camera’s position as negative co-ordinates in many naive setups, and it can make for a lot of confusion!
Fixing all of this in the past month has been a good bit of fun. The largest remaining issue is to bring in textures, along with better-looking lighting, and that has necessitated a journey. Once I got the “C Minecraft Clone” project to the moderately acceptable state above, I left it temporarily, to work through the Learn OpenGL tutorial series, with the end intent of returning to rewrite my C Minecraft Clone using modern OpenGL!
Who, or what, is Modern OpenGL?
My existing project is written using Ye Olde OpenGL, roughly Version 2.1, which uses a flavour of OpenGL API referred to as “immediate mode.” This mode includes many built-in concepts of lighting, materials, and matrix manipulation, which can reduce the amount of boilerplate required to get the basics up and running. This API usually has calls to functions like
glLightModeli, and other similar beasts. Because it does a lot of work for you, there’s a lot of magic going on under the hood that makes it hard to use correctly, as in both efficiently and with nice looking scenes. And there’s unfortunately not a lot of good resources out there updated in the last decade for understanding it.
Modern OpenGL, circa Version 3.1+ (I’m using 4.1), does a lot less for the user out of the box, but gives (er, you might say “foists upon”) the programmer a lot more fine-grained control. It exposed what is referred to as the “core mode” API, in which you must define buffers, set modes, make glDraw calls yourself, and write shaders to render everything, using the C-ish shading language, GLSL. You need to at least write a Vertex Shader and a Fragment Shader, which handle steps in the rendering pipeline to transform your abstract vertices into rasterized pixels. All of the Learn OpenGL tutorial series linked above teaches how to write and think in “core mode” OpenGL, and it gives a great introduction to writing shaders — so working through these to understand how to use it has become my recent project.
For the record, OpenGL 3.1 came out in 2009, while OpenGL 2.1 is from 2006! Neither is a spring chicken. But the code as currently written is using stuff that was deprecated nearly 15 years ago, while OpenGL 3.1+ is still pretty much “how OpenGL is used today”. A further tricky aspect of this landscape is that there isn’t necessarily one line in a program that says what version of OpenGL we’re using. There is in some programs, but not all! When you don’t have a clear answer, you’ve got to check what OpenGL version your Operating System and Drivers have access to, which is what the program will use when run.
Even then, you could have OpenGL 4.1 installed, but if your program uses immediate mode APIs it will run in an odd OpenGL 2.1ish compatibility mode. You pretty much need to look at what sort of API the program is using — the functions from the first paragraph above are telltale signs of some very dang old graphics code.
“Wait, isn’t OpenGL over? Shouldn’t you be using Vulkan?”
Yeah, well that’s, hey listen buddy — I’m not some kind of magician over here! I’m just learning things, alright? Sure, I found the Vulkan tutorial, and I might look at that down the road too, but it’s even lower level than core mode OpenGL. There’s also ongoing weirdness where (of course) Apple has thought different, and created Metal, which is its own whole world, and because I’m using an M1 mac, the Vulkan vs. Metal story might be a saga.
So for now, I just want to take some baby steps to a game that looks a little better. That brings us to the problem: I have a big unwieldy tutorial project with its shaders and vertex buffers and all the goodies, and I have a big old dinosaur Minecraft project which is written totally differently, and — oh right, GLUT vs GLFW! That’s one of the biggest differences, and…
GLUT and GLFW are wrappers around operating-system level concerns like creating or disposing of a basic window, and receiving mouse and keyboard input. They’re platform-independent APIs that aren’t meant to do “the OpenGL parts” for you, moreso “the messy OS stuff,” and using something like them as a framework to initialize an OpenGL program is widely regarded as A Good Idea.
But GLUT is deprecated. GLUT is so deprecated that the GLUT Website literally just says, “We direct you to use FreeGLUT found on SourceForge: http://freeglut.sourceforge.net/. The original GLUT has been unsupported for 20 years.” SourceForge always sets off my spidey-sense, but of course my 2010-era webpage is written using GLUT. At least I got it to compile!
GLFW is what the Learn OpenGL tutorials I’ve mentioned so far use, and it’s also what the Vulkan tutorials use. It has been a lot nicer on the eyes and the mind than GLUT is, so I’m sticking with it. It was pretty easy to install, has recent-ish docs around, and modern amenities abound. There are alternatives, but it does really seem close to the consensus framework. Unfortunately, the two systems are pretty different:
- GLUT uses callback functions extensively, but relies on global variables for communicating with them
- GLUT and GLFW each have distinct sets of functions to create and set up a window
- GLUT pushes toward using its keydown function (and later releases added a keyup function), in which the end user is likely to directly respond to key events and would have to build their own keymap abstraction, while GLFW provides an extensive keymap abstraction that can be polled at any time
- their mouse handling seems overall pretty similar
- glut includes some utilities closer to the opengl drawing layer that glfw doesn’t go near, for example, a main loop callback
Where were we… Right! Because of all this, the game and tutorial have very different shapes, which makes it hard to make any incremental steps here.
A Tale of Two Plans
Of course I have all of my stuff stored in git, so I’ll be working on a branch and trying things out with low risk, but I’d like to nail down a direction for work. I sat down today with all my new OpenGL knowledge and re-read my C Minecraft Clone code, and I’ve come up with two overall plans of attack, but I’m undecided on which to take. So we’ll do it live. They boil down to “rewrite the original with new OpenGL hotness” vs. “port the unique bits of the original into a new modern OpenGL project”.
Here’s a rough sequence of discrete steps I could follow to “rewrite the original”:
- Remove all lighting from the C Minecraft Clone, because it’s complex and gonna change completely.
- Swap from GLUT to GLFW for initialization and drawing, and drop all the keyboard and mouse controls.
- Re-add keyboard and mouse and window-resizing controls using GLFW.
- Rewrite the core display function to use a new cube vertex list with texture and normal co-ordinates and some basic shaders, and just rgb-diffuse-color materials.
- Rewrite the player and mob display functions similarly.
- Figure out how to port the frustum culling over.
- Add textures and lighting back in, using the new OpenGL shaders.
Meanwhile, the “port everything over to a modern project” rewrite could go like the following:
- Set up a GLFW project with basic shaders, abstractions, and FPS-like controls, taking heavily from my tutorial project.
- Port over the world-generation and rendering of cubes to form a map.
- In turn port over controls, digging, mobs, and networking.
- Figure out how to port the frustum culling over.
- Add lights and fancy shading where it fits.
The first path feels to me like it will be harder. But, despite the very ship-of-theseus nature the project has, it feels like a more pure idea of “rewrite of the project using modern opengl”, which is the goal I have felt an emotional draw to all along. It rips the OpenGL bits out of the existing project and upgrades them, rather than just starting with a new project and reimplementing everything that made the project unique (via porting it all over).
So, which direction will I go on this? At the moment, I’m a little torn. I’ll continue to mull it over this evening, and some whim will take me forward to try soon.
Were this a “work project”, I think I would definitely take the second path — I can pretty much get a working “modern OpenGL” project running immediately, and then each porting step is fairly small and self-contained and they continuously build the project up. By comparison, in the first path I need to rip out working code to reduce its capabilities and then restore them on each of Steps 1, 2, 3, 4, and 5. But it isn’t the work I thought I was planning / preparing to do — isn’t that a funny concern? I think the best path is becoming pretty clear.
Just for a reference, here’s roughly what my OpenGL tutorial project looks like today — the textures are mostly supplied by the tutorial itself, and the big “advance” is the lighting (plus having textures at all):
Thanks for reading. I hope you’ll join me soon for my next post, “How I failed to make any progress rewriting my old school project in OpenGL!”