Introduction
My demo starts by running a scripted demonstration of the various features supported, culminating in a Martian disco! After the script runs, buttons are revealed to allow the demo to be used as a sandbox. 2d and 3d effects can be turned on/off by the user. The user can also choose to run the script again.
The demo features an animated Marvin the Martian, loaded from MD2 format. In addition to rendering the demo features both 3D effects (such as spike, pulse, dissolve and character animation) and 2D image post-processing filter effects (such as gamma correction, brightness, bloom and blur). The effects are implemented as loosely coupled objects which decorate either a game object or a rasterizer.
Update (12 March 2009) – I have now fixed a few problems with the renderer. Specifically:
- It now interpolates properly between animation keyframes.
- The orbit camera now works.
Download
Executable – CPPSoftwareRenderer.zip (1.37MB)
Readme – Readme.pdf (87KB)
Features of the Software Renderer
• Vector and Matrix classes – implemented as fully functional data types with overloaded operators, copy constructors and accessors. Support add, subtract, scalar divide and multiply, cross and dot products, transpose etc. Also provide static matrix generation methods e.g. identity matrix, rotations, translations, scaling.
• Camera – The camera is able to change position.
• Rendering pipeline – The rendering pipeline is comprised in MyApp::Render(). In order for effects to be applied in the correct sequence (some need to be applied post-transform, pre-render) the pipeline has three stages. Render() is called after movement effects have been applied (DoMovement()). It distinguishes between the various render modes in use (e.g. there is no need to light the model if we are only drawing wireframe or outputting a texture for debugging). The pipeline then splits into TransformObject() and DrawObject(). The former deals with normal and lighting calculations, transforms to camera space, culling and sorting polys (if appropriate). The 3D effects manager is then called before DrawObject(), which handles perspective and screen transforms and renders the model. After this the pipeline deals with 2D post-processing effects and filters, before flushing the pixel buffer to the GDI+ bitmap, drawing text and framerate, and drawing the bitmap to the window.
• Rasterizer – As mentioned above, my rasterizer makes use of a pixel buffer for drawing. It supports rendering in the following modes:
o Wireframe
o Flat shaded (GDI+)
o Flat shaded (manual)
o Gouraud shaded
o Textured
o Textured and lit
o Textured, lit and gouraud shaded
o Texture debug
In addition it supports a number of 2D filters, namely:
o Inverse (negative)
o Grayscale
o Colorisation
o Brightness adjustment
o Gamma adjustment
o Blur
o Line scan
o Bloom
o Jitter
I have combined a number of these filters in my demo to create effects (e.g. a ‘retro’ filter comprising greyscale, color (sepia tone) and jitter).
• Z–Buffering – The rasterizer supports Z buffering through the ZBuffer class, allowing per-pixel sorting.
• Polygon culling and sorting – Depending on draw mode, various methods are used, such as: polygon sorting with std::sort and a predicate function; backface culling; whole polygon culling outside view port; line culling in wireframe mode. Clipping is not supported.
• Lighting Support – Lights are implemented as a base class (Light) and three derived classes (LightAmbient, LightDirectional and LightPoint). These are tested and functional and the scene contains one, two and one of each, respectively. MyApp contains arrays of each type of light, making it simple to add and remove lights as required, although it might be nicer to have a light manager to handle this.
• 3D Effects – My renderer includes an extensible effects framework using the decorator pattern. It was initially meant to handle 3D effects on objects, but I found it very handy for creating effects to apply to the rasterizer and the camera.
The abstract base class, Effect, provides an interface for effects, which decorate an Object. Derived effects simply implement the Update() and Undo() methods in addition to any code of their own.
In practice I found it very easy to create new effects using this framework, even filter effects, for which I simply used a dummy object and passed in a rasterizer. With hindsight it might have been better to further abstract the base class and build on three derived classes – EffectObject, EffectRasterizer and EffectCamera, rather than having a redundant object pointer.
o Effect Manager – The effect manager comprises a linked list of Effect*. With polymorphism, effects of any type can be passed in, and the manager then Update()s these every frame, deleting them when they have finished. In this way, effects can be created and then forgotten about.
o Spin – Applies a rotation about a specified axis for a duration.
o Spike – Smoothly transforms verts outwards into spikes and back again over a time period using Object::Spike. Customizable offset and magnitude.
o Pulse – Smoothly transforms verts outwards and inwards in a sinusoidal pattern. Customizable frequency and amplitude.
o Rainbow – Modifies the reflectance co-efficients of an object over time in a randomly fluctuating pattern to give a rainbow effect.
o Dissolve – Uses a predicate std::sort to order polygons by average Y value, then over a duration culls them from top to bottom, giving a disintegration effect.
o Animation – Applies an animation to a model over a period. When object is loaded the key animation frames are stored in an array of the KeyFrame class. Object::LoadFrame() simply adjusts the Object::_vertices pointer to point to the relevant KeyFrame::_vertices array.
• 2D post processing effects
o Color filter
o Grayscale
o Gamma
o Brightness
o Bloom
o Blur
o Lines
o Jitter
o Inverse
The Scene
• Direct camera manipulation
• Several objects being transformed in different ways
• Light sources
• User options
Optimization and performance
I kept track of the optimisations I performed on a spreadsheet.
• Pixel buffer – The primary optimisation made, which resulted in a framerate increase of 56fps in textured mode, was doing all drawing to a pixel buffer and then flushing this to the GDI+ bitmap once per frame, intead of using Image::SetPixel(). A side effect of this was that it made post-processing much easier and faster, as this was done directly on the buffer.
• Float to int conversion – Profiling revealed the biggest bottleneck was float to int conversions using __ftol(). I replaced this with some third party code (referenced in my code – float_cast.h) which resulted in an improvement of about 5fps in some cases. Time was also saved in some of the image filters.
Hi there, I’m really impressed by your c++ renderer, I would love to see the code and learn how to do the same thing some day.
Josh.
Hi,
Im really impressed by your renderer, Very nice job! Im currently trying to make one myself in C#, And it all works pretty well so far, except from the framerate. If I do all calculations, but no drawing, Im able to get 40-46FPS. But once I let it actualy draw the pixels, my FPS drops to ~15 when rendering an medium sized cube. Im using the Lock and Unlock methods and a pointer to the bitmap’s Scan0 to avoid the SetPixel method so I can draw on the bitmap as fast as possible. But it doesnt seem to be good enough. I read that you said something about flushing it from a buffer to a bitmap once per frame, Im wondering how you’re actualy doing that, Could you try to explain it to me? Im also wondering if you’ve got any methods for oclusion culling, because I cant think of how to do so. Im having trouble on implening point and spot lights too, my directional lights (and ambient ofcourse :) ) work fine though.
I’ve also impented Phong shading today, It gives a better look then gouraud shading, but I wont ever get that fast enough (For normally sized object, but tiny cubes work :) ).
Any help/explanation, would be greatly appreciated. Thanks in advance :)
Hi Martijn, thanks for your comment.
It sounds like your approach is the same as mine – to use a BitmapData object and point its Scan0 member at a local array of pixel data, then use LockBits/UnlockBits to get your pixel data into a Bitmap object, then Graphics::DrawImage(yourBitmap).
That gave me a big speed boost so I can only imagine what your framerate was like when you were using SetPixel! :)
It would be worth profiling your code to discover exactly where the bottleneck is. It’s not clear from your post whether the problem is the actual DrawImage() call or some part of the copy operation before that, which you might be able to do something about.
Good luck!
Hello again :)
It’s almost a year ago since my comment. I gave up on my rasterizer about a week after my comment, it was simply too slow, and a month ago I lost all my work in a hdd crash :(
But I’ve started working on a 3d engine again about 2 weeks ago and I thought it’d be cool to show how I’ve progressed. 10 months ago my problem was that it was to slow, 10 minutes ago I ran into a whole new problem. My rasterizer was running so fast that my mouse input couldn’t keep up somehow and so the camera moved very “jumpy”. It was going 400 fps with gouraud shading, 5000k bunny model, 3 dynamic point lights at 800×600 =) (I still have to find a way to limit it’s speed when neccesarry, for now Thread.Sleep(1) seems to slow it down to about 60, but you wouldn’t always want to slow it down). Realtime phong shading should be possible with these settings, but it wouldn’t work too well when the scene gets larger. I also made something really cool this week, my renderer can now support custom vertex formats and pixel shaders! (I use a vertex declaration, and with reflection I compile my dynamicly generated rasterization/interpolation MSIL, it was a hell trying to fix my errors though, it is over 1200 lines of opcodes, and you step through your code so I used exception as flags to see until what point it worked.) It has one flaw however, even for gouraud shading a pixelshader has to be executed to return the color, slowing it down quite a bit so I think it’d be better to use my hardcoded rasterizer.
Another thing is that I have no Z-buffer or whatsoever. My idea was to use a bsp-tree c-buffer for static geometry and write-only z-buffer, and then render the dynamic geomtry with z-buffering. I think however that a z-buffer for eveything might be better though, it saves me a lot of work and I think that with the currect speed it won’t be much slowever.
I’m still doubting about the lightning, my idea was to use lightmaps for static geomtry static lights, then add do the dynamic lights dynamicly, andfor the dynamic geometrys just dynamic lightnfir static dynamic lights. I’m sure that rendering both dynamicly well work fine too, but with lightmaps I can do all kinds of cool effects, I just don’t know if i can put 2 sets of texcoords in à .obj. But illhave to write a importer for another mesh format anyway to do animations. I know how to read .3ds and .x, what format do you use? And what format do you recommand?
I’m sorry for my long and messy post, all I meant to say was that my rasterizer now runs at 400fps. Sorry =)
Hi Martijn, that sounds cool, glad to hear you’re still programming. Maybe you could look into making your movement framerate-independent by scaling it based on the framerate. For the animations, my renderer used MD2 format and morph target animation, but you might want to look into skinned meshes instead.
Ill make a video soon and post it on YouTube, but I’d like to have most things finished first.
I also haven’t yet implented textures and no u/v interpolation, so that’ll slow it down a bit, especcialy bilinear filtering, so eventually my rasterizer won’t be that extremely fast.
Right now I’m writing it for a 3d game that I’m planning to make, so I won’t use my dynamic rasterizer, and no phong/wire rasterizers and no support for awesome effects. Just the bare essentials. ( I really love vertex/pixel shaders and post processing effects, but I’m afraid that it’ll cost a lot of performance :(, I’m using a 2.83ghz quad core, but not every pc has that.)
Hi Henry,
I remember reading about the MD2 file format, it seemed not to hard to parse. But, you recommanded skinning for animation. I’m not sure if that’s possible with MD2 (I’ll google it :)), if not, the .X format does, and .3ds does too I believe, and I already have some experience with both so I’ll make one for those.
For my rasterizer I currently have zbuffering texturing working. Here are some benchmarks:
Resolution # Models Fps1. Fps2
1600×1200 4. 50-60. 20-30
800×600. 4. 120 . ~60
1600×1200. 24. 20-30. 19-25
800×600. 24. slightly faster, can’t remember :p
model was a 4800 triangle bunny.
Fps1 is viewn(?) from a distance so that 4 bunnies fill most of the screen
On Fps2 at least 1 bunny is completely covering the screen
Bilinear filtering was turned off during the tests, otherwise the Fps’s woulve been lower.
I’m quite happy with these results =) but this scene isn’t very representative for a game(24×4800 = ~110k triangles, so maybe it is in triangle count ( at least I hope so.. :) ))
I’m using per-mesh front-to-back sorting zbuffering, but for the level mesh I might want to create something like an octree so that it’s sorted properly, for most other meshes this is unnecessary.
Last few days I’ve been writing an abstraction layer so that I can switch between directx/software. I’ve still got alot of work on eveything (software rendering, animation, resources (textures/meshes etc), lost devices in directx, and much more)
Right now I guess Im spamming a bit so I’ll shut up until I have some cool results ( a 3d first person shooter I hope xD, but will take a lot of time..) and i just took a look at the time, and It took me a year to write this somehow, because I was 15 when starting this comment xD ( i’m typing on my iPod, but I’m quite slow :p)
Tthanks for the advice!