I thought I would teach myself some OpenGL during this holiday week. For the uninitiated, OpenGL is the common core 3D rendering that all recent video cards support. Unless you’re doing game development or scientific visualizations, you typically don’t need to mess with it. (And even game developers frequently use higher-level tools to hide the picky details of OpenGL.) I don’t have a specific project or purpose in mind — I have no desire to make a video game, for instance — but I do 3D printing and 3D modeling, so thought it might be fun to add another 3D-thing to the list.
There are quite a few OpenGL “tutorials” out there. As best as I can tell, they’re nearly all crap. Many of them are built outright for a single OS (often Windows), despite OpenGL being cross-platform, especially given it’s usually paired with the glew and glfw libraries to make cross-platform development easier. Some of them imply they’re cross-platform, but the author never had the resources to test on the Mac, so they’re broken. Some of them are tied to a specific GPU, either due to OpenGL version requirements or non-obvious variations such as
glGenVertexArraysAPPLE(). And most are just badly written: little more than a source code dump with a light sprinkling of explanation of the underlying concepts.
Initially, I juggled three different tutorials. One was good on explanation, but the code didn’t work for me. The other two had mostly, but not entirely, functional code. I had to graft together most of the examples from multiple tutorials.
It finally occurred to me to check Lynda.com. They have been recently advertising a lot on many of the podcasts I listen to. After verifying they actually have a course on OpenGL, I used the referral code from the most recent ad I heard (on Isometric, for what it’s worth, which is kind of fitting for OpenGL) and signed up.
After spinning my wheels for several days on my own, I zoomed through the Lynda course in about a day. Your mileage may vary; I’ve had experience writing GPU-based code and my recent struggles with the self-taught tutorials primed me for the basic concepts. I could “skim” the opening chapters at 1.5x speed, although the later stuff I played at normal rate and took lots of breaks to experiment with the code and go off on tangents.
Today I put some of it to use. Although I don’t (yet?) have a specific end goal, I did have a few things to try. Sometimes I do 3D modeling in OpenSCAD. It’s good for technical CAD designs. I thought I’d draft up a simple shape there, import it into an OpenGL program, then render it on the GPU. My OpenSCAD “model program” looked a bit like this:
cylinder(r1=5, r2=2, h=10, $fn = 6);
This makes a sort of “truncated cone,” 10 units high, 10 units across at the base, and 4 units across at the cut-off top. The $fn parameter is for smoothing. Because these kinds of 3D models use straight lines and circles aren’t straight, this value indicates how many “slices” to make. A higher value gives you a smoother circle. Because I didn’t want to get lost in vertices and indices, I stuck to the rough “6 smooth” variant to keep it simple.
Because I didn’t (yet) want to mess with the STL file format, I opened the 3D file in a text editor and extracted the points to put into a
const array in the code. Okay, technically, I wrote a small Ruby program to extract them into C constants for me. Actual STL file parsing (plus optimizations like de-duping points) will come later.
It took a good chunk of the day to figure out problems with color, lighting, and to work through a matrix operation issue with multiple rotations (the Z axis differs between STL and OpenGL), but I was finally able to render to screen and disk. Converting the frames into a small animated gif (with crappy color dithering) results in this:
I’m still really fuzzy on the details of lighting. The course hand-waved over it, so I’m mostly at a “monkey see / monkey do” place with that. I’m also not as well-versed in shaders as I probably could be, although I could successfully debug a few problems with mine (e.g. I forgot to send the normal matrix across to the GPU). And while I understand textures on relatively flat surfaces (walls or waving flags), I’m less sure about how they map onto more complex manifold shapes (such as an octocat).
The next steps are to natively read the STL file, collapse duplicated points (and reference them by index), scale so that models of any size will nicely fit the viewport, and make the colors less Christmas-like. And probably clean up the code enough so that I’m not embarrassed to publish it.