the pendulum has swung back to graphics
I’ve realized recently that Scourge’s client has many layers, and jotting down a list of those layers helps maintain my perspective of the work yet to do. Distinguishing layers is an easy activity – if System A connects to System B, and A can invalidate B, but B cannot invalidate A, then A is a layer above B. That’s helped me identify the following layers, each of which has its own problem space:
- Server connection – are we online? (HTTP/UDP comms)
- Session – are we authenticated and up to date? (user object)
- Battle – who are we fighting and what actions can we take? (the ROPES)
- UI Narrative – what action/data is the player considering? (narrative tree nodes)
- View – What does each button and view component look like? (view objects)
- Renderer – What does each particle look like? (glyph objects, model arrays)
- Graphics – What does each pixel look like? (GPU buffers, write-only)
And that’s only the stuff that’s going into the demo! I left out the AI stuff from this list, because like the state of the human player, it interacts with this layer cake at some level. It’s just a higher level than the one human beings interact with.
Anyway, while I’ve spent a lot of time focusing on the Session and Battle layers in the past, for this post I will focus on the Renderer and Graphics layers, which I’ve been working on for the past three months. They’re only a piece of the puzzle, and currently the Graphics layer is dependent on Flash and Stage3D, but that’s a small price to pay for the ability to work this stuff out while I wait for Haxe 3, H3D and NME’s OpenGL features to stabilize. (As I’ve previously mentioned, Scourge should be able to target every platform that one can target through NME, which is quite a lot. The graphics pipeline just needs to mature a little. I will color your pixels!)
Dicing the “GLobject”
Understandably, all OpenGL-ish APIs that I’m targeting have commonalities. One of them is that the API is centered around an object called a context, which has tons of low-level functionality. In programmer’s lingo, it’s an extreme example of the God Object anti-pattern– piling unrelated functions onto one object. There are good reasons for doing this in OpenGL’s case (I get the sense that there’s one context per GPU core or something), but it makes working with these contexts kind of awkward.
See, engines that use APIs like OpenGL are intended to expose higher level functionality to their users, such as subdivision surfaces, bones and lights. These objects have an internal state that the renderer uploads to the GPU. Often this internal state consists of types that can only be created using methods on the GL context. Because the state is internal, you’ll have to pass the context to the initializer of each of these objects so they can compose themselves! And remember, that context contains all kinds of methods on it.
This is ridiculous! I mean, if you were driving a car with your friend in the passenger seat, and all the radio controls were on the steering wheel, your friend couldn’t adjust the radio without grabbing the steering wheel, could she? Moreover, if a cop pulls you over and sees you and your friend sharing the wheel, how can he be sure who’s the passenger and who’s the driver? How can he be confident that he knows at all what the fuck is going on in this automobile?
Here’s what we can do instead; we can dice up the API. We start by sorting the methods on the GL context into piles, where each pile represents a different kind of activity you might do– creating textures, creating buffers, creating and setting up programs, and rendering are the four I came up with, but each project can do this differently. Then, we create an object for each pile that exposes just those functions in their individual APIs–
DrawUtil, for example. Finally, we create an object that generates these utilities from a context. And we’re done for now.
See, a utility is a tool that uses the context in only a specific way. It’s just small enough to get the job done, and it has no state; it’s meant to be used over and over by anyone in any order, which means we can reuse the few utilities that we have. And by giving references to your utilities to higher level objects, they can manage their internal state without accessing the context directly. In Haxe, all the utility methods can be inlined, so there’s no runtime cost to doing this, but even if there was, its benefits outweigh those costs. After all, at some point you’ll come up with a more convenient way to handle low-level data in your engine, and your utilities will be the spot where those changes will go.
I can use these utilities (and a handful of other types, perhaps) as my interface between the Renderer and Graphics layers of Scourge. As I add support for other targets’ 3D APIs, my utility API will generalize, but it will conveniently encapsulate all the platform-specific code.
Now then, about quads
Ever heard of a particle system? A pile of of display elements, governed by some kind of “emitter”? That’s what I’m up to. Scourge’s first view is a barebones particle system. And that means I’m messing around a lot with quads.
Most 3D engines focus on drawing meshes of connected triangles to approximate continuous surfaces. Not so for particle systems– each particle is basically an image glued to a quad- also known as a billboard, composed of two right triangles- that faces the camera. My “emitters” resemble normal 3D models, dictating where each quad belongs in 3D space to visually approximate a surface.
My particles are text characters– in fact, I’ll be using them to render the game world and the onscreen text in my game. Therefore, I’ve given them the following individual features:
- 3D position
- text character – the letter, number or symbol that textures it
- inverse video – an old-school effect that helps important characters stand out on terminals
- pop – added to the 3D position to make it appear closer
- id – a vec3 that is unique for every particle
For a barebones particle system, this may sound like a lot. I’ll be putting it all to good use, though. For instance, I’ll use them to make buttons, which need to stand out and change appearance when the player interacts with them. (I can use those features to also make the game board seem to throb, as if parts of it are alive.)
The id feature is especially important, because my particles are interactive. That means I need to intermittently update a bitmap that indicates which parts of the screen, when under the mouse cursor, correspond to which particles. Interactive regions shouldn’t change on a frame-by-frame basis; even if a button is animated, its hit area should be static. So when I’m drawing this interactive map, I’m disregarding most of the features listed above and rendering each quad in its “normal” 3D position with its color values set to its id feature. No matter how animated my game is, its interactive regions will never budge for as long as they’re onscreen.
One final note: I’m experimenting with an interesting idea about 3D perspective. Most 3D engines have the concept of models– objects composed of particles or meshes, arranged in 3D space. To represent a model’s position, orientation and distortion, they usually have a
matrix property (similar to the
transform.matrix property on Flash’s
DisplayObjects). That includes a model’s perspective transformation – though usually that matrix is assigned to the “camera”, not the models, because the camera sees all models with the same field of view and so on.
However, in Scourge’s view there are interactive buttons alongside a 3D rotatable object. Is there any good reason why the buttons should have the same vanishing point as the 3D object? It comes down to an aesthetic decision. For that reason, my models may have their own perspective projections, so that buttons and other objects will appear to grow and shrink from their center. We’ll see sooner or later whether that makes any sense.
A picture’s worth a thousand words. Screenshots forthcoming.