Monday, June 18, 2012

New world rendering scheme

So I said I'd talk about how I reworked my Scene class (which manages the world and all objects in it) to allow a lot of flexibility in drawing. Previously, drawing the Scene was as simple as calling Scene::DrawScene(). This would draw the world, then call Draw() on all GameObjects. This was really simple and intuitive, and someone using my engine could get stuff on the screen quickly. Little is more frustrating than not being able to even get started with something because you can't figure out the basics. Unfortunately, this puts a lot of control out of the user's hands; once they call the function they're limited to what Scene is hardcoded to do.

New features in my engine often come out of want or need while I'm developing ProjectFPS, and this time was no different. I was looking to implement toon shading, and wanted to try the post-process version. In this version, you render normal and depth information to separate buffers, then render the scene normally. Finally, you use the normal and depth info to detect edges and add the black outlines. However, my Scene class didn't support this at all. While it was possible to put a post-process effect on the final color image, there was no way I was getting normal or depth info.

There are a couple solutions to this problem. The simplest method is to modify Scene code, maybe through adding a bool called m_bToonShading or something that specifies whether you want your scene toon-shaded. But now I'm bringing game-specific stuff into a general purpose engine, and that's no good. Plus, you're still stuck if you wanted a different post-process effect, or even a different toon-shading algorithm. And if you try to add support for multiple special effects this way, the Scene code could get messy fast.

Another is to allow Scene::DrawScene() to be overloaded (i.e. make it a virtual function). This solution certainly gives you all the power over rendering you'd need. Doing so, however, would be very complicated. You're asking the user, who did not design nor program the engine, to delve into all the messy details of how Scene does what it does. As you can imagine, it is not a small class. Rendering a scene efficiently takes a lot of steps, and if the user forgets any when writing their own function, they get a black screen with little indication of what went wrong. And if they plan to support multiple special effects, they either have to overload the function multiple times (which is frustrating and time-consuming), or be faced with the messy code branching problems Solution 1 had.

Solution 3 is to tell Scene what kind of extra rendering information you want when rendering the Scene. The actual post-process part, however, is not done by Scene. So if you wanted color, normal, and depth info rendered, you might say:

m_Scene.DrawScene(SCENEDRAW_COLOR | SCENEDRAW_NORMALS | SCENEDRAW_DEPTH);

This is a fairly good solution. It gives you info you need without assuming what you intend to do with it, giving the developer flexibility without making things needlessly complicated. This is not perfect, however. Maybe you wanted to exclude transparent objects from showing up in the normal buffer. Maybe you wanted linearly-distributed depth, as opposed to standard non-linear. Maybe you wanted your own special type of data that Scene doesn't natively support. Still, this is a great step in the right direction.

The solution I settled on was to give high-level rendering control to the Camera class. You call Scene::DrawScene() as normal. Scene then iterates over all currently enabled cameras and calls Camera::DrawScene() on them, a virtual function that can be overloaded in derived camera classes. Scene then provides a couple of high-level functions (such as DrawLevelGeometry() or DrawOpaqueObjects()) that Camera can call at will. In between, Camera can switch render targets or techniques or set parameters or whatever it needs to do. This lets the developer only render what they want, how they want, while sparing them from all the gritty details.

To give the user further control, Camera can set tags that have special meaning to the application. For example, you could define a tag that, when set, excludes all objects that are not players. You set this tag to the camera and call Scene::DrawOpaqueObjects(). Scene will then call GameObject::DrawOpaque() on all GameObjects. The GameObjects will check for the tag, and react appropriately.

If you need to switch the special effect you're using during gameplay, you could configure the camera, or you could just derive a different class and swap cameras instead. Doing the latter would be much less daunting a task than overloading Scene::DrawScene() multiple times would be. Plus, having Scene just pass control to all enabled cameras makes doing effects like picture-in-picture and "arena cam" simple to implement.

The base Camera class provides a default implementation, so for those who just want to get up and running, they can just call Scene::DrawScene() like before. In this way, the simplicity and intuitiveness of the original design is maintained.

This solution is still not perfect. Currently, my main issue is a lack of precise control over how exactly level geometry is rendered. I can swap techniques and set parameters, but that requires knowledge of all shaders used by level geometry. And if I set a parameter on a shader, it will apply to all level geometry that uses that shader. But maybe that's not what I want. Maybe I want wall A to have a different parameter than wall B. Level geometry can't check tags, either.

I intend to solve these problems, but I'm holding off until the level format I use is more finalized and robust. Currently, I'm using a level editor called GILES that comes with courses at a site called the Game Institute. This editor has a number of limitations, however, that prevent me from fully committing to it. They're working on a new level editor that's going to be released alongside their Carbon game engine. I expect huge data format changes, so when that comes out, I'm going to take a look at it and revisit these issues.

Saturday, May 12, 2012

Finally, some screenshots.

A lot has happened in these past five months for ProjectFPS. I'm not even sure where to begin recounting the changes. I apologize for not giving more updates as I've done it. On the bright side, I am now going to actually show some screenshots. So I'm going to show those first, then rattle off all the changes to the engine itself that I can remember off the top of my head. Note that none of the art is final - it's just one of many things on my to-do list.


You'll notice that I've gone for a toon-shaded look. I feel this gives the game a nice visual appeal without straining my (non-existent) artistic skills. It also sets a less serious tone, which I like; I may have mentioned this before, but this game is going to be designed such that people of various skill levels can all play together in a more friendly sort of competition. As such, there's less focus on hardcore competition and more focus on just having a good time. Going for a non-realistic look helps set that tone.




The gun you're currently looking at is called the Flame Pillar Gun. It shoots projectiles that burst into a jet of flame upon contact with the level. This jet lasts for a few seconds before disappearing. It's good for covering your escape or trapping your enemies. Skilled players can also use it to hit areas from otherwise impossible angles.




This is the Magnet Gun. Again, the gun model is temporary - I'm actually thinking of replacing it with a glove. Anyway, the left mouse button shoots out that big spiky ball you see there. You can then use the right mouse button to push or pull it towards you (it toggles between the two). The color of the glow indicates which way it's going - blue is pull, red is push. The balls will bounce when they collide with something.




You can have multiple of those spiky balls out at once, and they will all get pushed or pulled simultaneously. That's when the real fun starts, especially when two or more players get in a Magnet Gun fight.

There's also the Rocket Launcher, but since I'm in the middle of some big engine-side changes to the way objects are rendered, that gun's not showing up correctly.




I also got a cool menu system going. This runs on top of a GUI system I developed for the engine.

As for the engine-side changes... where do I start...

- The aforementioned GUI system. It's extendable and skinnable, too!
- Major overhaul to the way the scene is rendered. This topic is a post by itself, and will come later.
- Dynamic lighting! Supports directional, point, and spot lights. No shadows yet, though. It's amazing how much even a little lighting can improve your game's look.
- Improved netcode! I overhauled this, too. It still uses the NetRep component, and the concepts mentioned in my post back in August hold, but now you can tell NetRep exactly what variables you'd like to send over the network. In addition, the whole thing is delta compressed. This means that the server only sends what changed, cutting down on bandwidth requirements.
- Shaders! I've written a lot of code designed to make using the programmable pipeline as easy as possible. You can use semantics recognized by the engine to tag variables, and they will automatically be filled with the data you need.

There's a lot of other stuff too, but that's the most exciting to me, anyways. So where am I going from here?

- More maps and weapons, and improved art, of course. As far as this goes, the engine is in enough of a finished state to make some good progress here.
- Lightmap support. Dynamic lights are expensive, so it's better to go static wherever you can.
- Shadowing support.
- Physics component overhaul. I don't mean Source engine style physics, but reworking it such that objects have easier access to collision information and can set up complex collision volumes. It also needs to play nicer with my Transform component.

And I'll try to be more punctual with my updates next time.