Local host - I Piss Excellence

streambabySoftware and s/w Development

Dec 14, 2013 (8 years and 1 month ago)


Postmortem: Let’s render

Simple GDI Software Renderer

For my ED2 project I created a basic 3D software renderer. This project only uses the GDI

function to draw vertical scan lines with a height of 1px. My goal for the project was to better
understand 3D math and the 3D pipeline. It took me a week and a half to create the demo in the project,
there are two features I started and failed on, and on
e feature that I ran out of time while working on.
These three features are outlined below.


Starting out the project I expected to build a software renderer that could mimic the fixed
function OpenGL pipeline. I wanted to be able to render 3
D models, apply textures, lights and
implement a shading language. I was also expecting to render a full 500 poly model loaded in from an
OBJ file. Sadly, rendering 16 triangles pulls the core I’m running on up to a 100% workload. I wanted to
be clever and

use my own vert structure rather than the array pointer OpenGl implements. This was not
a complete failure, but it did lead to a LOT of problems.

What went

This project was an intense learning experience. A lot of things went wrong and a lot of le
were learned. Let’s start with my lack of matrices. I’m not 100% up to par on my matrix math. To be
clever I wanted to implement all game rotation using “Rotation Objects” which basically moved points
around in 3D space using sin / cos calculations.
This was a bad idea, without the matrix math my
rotations are broken (I’m assuming this has to

do with floating point error). Textures also failed me. I
tried to do my texture calculations by projecting the x

y location of the pixel I’m texturing onto th
e u

v coordinate of the texture. This should have resulted in a 1 to 1 pixel to texel ratio in an optimal
situation and should have caused my textures to repeat. The GDI bitmap color lookup was mad
expensive. Rendering one 300 x 300 quad would cause MAJO
R slowdown. Also the rendering was way
off. While each pixel was looked up for some reason the entire quad got one pixel of the texture map.
I’m still trying to figure out what went wrong with it. Debugging this function turned out to be very
difficult, I
could not locate the point of failure. Because of time constraints I decided to drop
Lighting was another beast of its own. At first I planned to implement texturing and lighting both. Once
texturing was dropped I hoped to implement at least the
lights. Because of the way I formatted my data
lighting turned out to be impossible to implement. I should have taken a hint from OpenGL and used
arrays for me verts instead of a bit vector of custom structs. In order to implement lighting I would have

to rewrite my whole implementation. Instead I decided to scratch the lighting and texturing and
focus on making a vertex shading language. At first I wanted both vertex and pixel / fragment shaders,
however with texturing and lighting being dropped as wel
l as the sheer amount of CPU power it would
have taken to loop trough each pixel I decided to only implement a vertex shader. This seems possible
and my struct based verts would work just fine for it. I ran out of time and could not implement the
vertex sh
ader system.

What went right:

Well I have a demo up and running and it renders 16 triangles just fine. Back face culling was a
simple feature that I managed to implement without any difficulties. Translating my objects in 3D space
using “Translation Ob
jects” went smoothly (This is why I decided not to use matrices).

The entire
renderer is object oriented, because of this the failed features where isolated enough that dropping
them from the project didn’t cause any setbacks time wise; I didn’t spend much

time removing and
restoring my project to a previous state. After the project was built using a standard Win32 project I
migrated the finished product into a DLL and build a C# interface to use it. Calling the renderer from C#
turned out to be a lot easie
r than I thought it would be. The GUI’s reset function was also easy to
implement. To my surprise I didn’t need to implement any input code on the C# side, the GETKEYDOWN
macro I used in the DLL turned out to still work even when the active handle was insi
de a C# window.

Things I would do different:

If I had a chance to redo this project from scratch I would have used DirectX’s putpixel or
drawline function instead of the GDI calls. The hardware acceleration behind DirectX should speed up
the rendering. I
would also like to split the render window n
ways depending on the available processor
cores and give each core an equal amount of work to do. I’d also like to rethink my data representation
to work more like OpenGL and use array pointers. I would also spe
nd a few days getting up to par on my
3D math and use rotation matrices. If I had matrix support my whole projection system should be faster.

I would also tackle lighting
before texturing.

Important things I learned:

After this project I decided to read u
p a lot more on 3D math, I now have a much better grasp on
vectors and matrices. I also have a new understanding (and appreciation) for the 3D pipeline. Even
though my renderer didn’t implement a “proper” 3D pipeline I ended up doing a lot of research on t
topic and if I ever re
tackle the software rendering issue I will definitely implement a proper pipeline.