Hey everyone,
i was going to write a technical post-mortem on our shooter, Sine Mora, but i just can't motivate myself enough to learn powerpoint, so i thought, hey, why not just post it on beyond3d, maybe someone will find it useful/interesting... so here it is.
Sine Mora is basically a shoot'em'up, in the tradition of Einhander, or R-Type. From a technical point of view, there were two things i was trying to make sure that make it into the game: solid 60fps and good image quality. The final game manages to maintain 60fps all the time except two occassions, unfortunately - we fell victim to regression and nobody noticed it until it was late - sorry (some cutscenes also drop frames, but they weren't considered important from a gameplay point of view)
The game is using forward rendering, mainly because i didn't want to give up hardware MSAA, and i just couldn't figure out a way to do it deferred in the 16ms allowed. Each objects has one main light (spot or directional) and a maximum of 8 point lights attached. Main lights also use a pretty standard PCF shadowmap - no big tricks here, directional shadows are frustum focused, with a distance of 800 units, fading out in the domain of 600-800. There are about 5-6 different materials in the game, each using an average of 2-3 textures, all of them DXT1/5. Distant objects switch to a simpler material with no crossfade - usually they're far enough for it to be completely unnoticable.
Sine Mora is rendered at 1280x544x4AA, with black bars on the top/bottom of the screen. This was a design decision to make it more cinematic. Aniso4x is used on most textures. Rendering order is something like this:
- Shadows are rendered into a 1024x1024 shadowmap.
- Reflections are rendered into a 640x272x0AA target, using oblique frustum clipping.
- The main opaque scene is rendered into a 1280x544x4AA target, the destination alpha is used to indicate pixels which are "important". Important pixels are not blurred when slomo is on.
- Transparent objects are rendered, still using the render target from the previous step.
- The zbuffer gets downscaled, storing its min/max values into a 640x272 target.
- Half-res (or quarter res, depends on how you look at it ) particles are rendered into a 640x272 target, using the downscaled min/max zbuffer to better approximate the high-res target. Fading is calculated for both values, and then averaged.
- Color & zbuffer gets reloaded into a 1280x544x0AA target, and the half res soft-particle buffer is upscaled on top of it, using FXAA. FXAA was much faster and better looking, then any bilateral upscale i've tried.. maybe i just didn't try hard enough
- Foreground transparencies are rendered, now this is an interesting step, because we've already lost AA at this point, but there are so few foreground draw calls, apart from bullets, that it's really not that noticable.
- The final target is resolved into a half-res buffer, using the alias-as-msaa hack, and then blurred. This blurred buffer is then used by the compositing shader for either a simple bloom, or to separate the background in slomo/boss intro modes. As a bonus, if the art filter is on, two volume textures are used to transform the final colour values into the first and second best matching entries of the C64 palette. (and no, i didn't use the vice palette as a reference )
- And finally, the GUI is rendered.
Average triangle count is 400.000-500.000, which is not a very relevant metric, but sounds good In stereoscopic 3D, some compromises had to be made to maintain the holy 60fps AA is reduced to 2x, and the horizontal resolution is halved. This does NOT apply to the GUI, which is still rendered in full resolution, even in 3D.
Because the game has a strong reliance on time manipulation (read: slooowmoo), all particle movements are scaled by current game speed. This also means, that animated textures (which we use a LOT) have to be blended too - all texture anim particles in the game fetch two texels (current frame vs next frame) and blend between them. Rewinding time required us to store game state every 4 frames, including particle data, and we play it back by restoring the newest frame and advancing the game by N frames, where N is 0..3. (to avoid jerky rewind - you can still spot some objects not rewinding as smoothly!)
So there it is... i hope some of you will find it an interesting read.
i was going to write a technical post-mortem on our shooter, Sine Mora, but i just can't motivate myself enough to learn powerpoint, so i thought, hey, why not just post it on beyond3d, maybe someone will find it useful/interesting... so here it is.
Sine Mora is basically a shoot'em'up, in the tradition of Einhander, or R-Type. From a technical point of view, there were two things i was trying to make sure that make it into the game: solid 60fps and good image quality. The final game manages to maintain 60fps all the time except two occassions, unfortunately - we fell victim to regression and nobody noticed it until it was late - sorry (some cutscenes also drop frames, but they weren't considered important from a gameplay point of view)
The game is using forward rendering, mainly because i didn't want to give up hardware MSAA, and i just couldn't figure out a way to do it deferred in the 16ms allowed. Each objects has one main light (spot or directional) and a maximum of 8 point lights attached. Main lights also use a pretty standard PCF shadowmap - no big tricks here, directional shadows are frustum focused, with a distance of 800 units, fading out in the domain of 600-800. There are about 5-6 different materials in the game, each using an average of 2-3 textures, all of them DXT1/5. Distant objects switch to a simpler material with no crossfade - usually they're far enough for it to be completely unnoticable.
Sine Mora is rendered at 1280x544x4AA, with black bars on the top/bottom of the screen. This was a design decision to make it more cinematic. Aniso4x is used on most textures. Rendering order is something like this:
- Shadows are rendered into a 1024x1024 shadowmap.
- Reflections are rendered into a 640x272x0AA target, using oblique frustum clipping.
- The main opaque scene is rendered into a 1280x544x4AA target, the destination alpha is used to indicate pixels which are "important". Important pixels are not blurred when slomo is on.
- Transparent objects are rendered, still using the render target from the previous step.
- The zbuffer gets downscaled, storing its min/max values into a 640x272 target.
- Half-res (or quarter res, depends on how you look at it ) particles are rendered into a 640x272 target, using the downscaled min/max zbuffer to better approximate the high-res target. Fading is calculated for both values, and then averaged.
- Color & zbuffer gets reloaded into a 1280x544x0AA target, and the half res soft-particle buffer is upscaled on top of it, using FXAA. FXAA was much faster and better looking, then any bilateral upscale i've tried.. maybe i just didn't try hard enough
- Foreground transparencies are rendered, now this is an interesting step, because we've already lost AA at this point, but there are so few foreground draw calls, apart from bullets, that it's really not that noticable.
- The final target is resolved into a half-res buffer, using the alias-as-msaa hack, and then blurred. This blurred buffer is then used by the compositing shader for either a simple bloom, or to separate the background in slomo/boss intro modes. As a bonus, if the art filter is on, two volume textures are used to transform the final colour values into the first and second best matching entries of the C64 palette. (and no, i didn't use the vice palette as a reference )
- And finally, the GUI is rendered.
Average triangle count is 400.000-500.000, which is not a very relevant metric, but sounds good In stereoscopic 3D, some compromises had to be made to maintain the holy 60fps AA is reduced to 2x, and the horizontal resolution is halved. This does NOT apply to the GUI, which is still rendered in full resolution, even in 3D.
Because the game has a strong reliance on time manipulation (read: slooowmoo), all particle movements are scaled by current game speed. This also means, that animated textures (which we use a LOT) have to be blended too - all texture anim particles in the game fetch two texels (current frame vs next frame) and blend between them. Rewinding time required us to store game state every 4 frames, including particle data, and we play it back by restoring the newest frame and advancing the game by N frames, where N is 0..3. (to avoid jerky rewind - you can still spot some objects not rewinding as smoothly!)
So there it is... i hope some of you will find it an interesting read.