Software Renderer over and done with

Posted by Nopykon on Aug. 1, 2016, 7:39 p.m.

Maybe this will turn out to be a premature postmortem blog, but at least my Jani Frequency pretty low since I don't write that often. Well, in this moment, I believe I will shelf the project. Might dig it up for a micro jam-game or something. Or take if off the shelf I mean. Anyhow.

It's about the software renderer I started for Goofy4Digits. I was thinking I could do something else with it after the compo, but now I'm dropping it. I decided I should blog about it. Droppin'n'Bloggin. If you count only the code-base, the project started with the compo, but I've been interested in software rendering for a long time and experimented with it now and then since last year. I've been itching to write a renderer in C.

I decided to not use floats. Not <math.h> either because it uses floats. I wanted to play around with integers and bit fiddling, I seldom get to do that. And it would be more true to the retro vibe. A lot of stuff like this:

//let 4096 (or 1<<12) represent 1.0
//a * b would be
a * b >> 12 
//for division
(a << 12 ) / b 

But this is not a real deal DOS game or anything. I use win32 + openGL for scaling up the framebuffer to fullscreen. 320x200 gets annoyingly small on a modern display. Nor am I using asm, which would probably be necessary for making it fast enough for a 90s PC.

^ Exquisite texture work.

The greffix pipeline goes like this

1. Have a triangle, three vertices of some kind. {x,y,z, u,v, r,g,b}

2. Transform verts by inverse camera matrix, do lighting calcs per vertex.

3. Clip triangle by frustum planes.

4. Perspective divide.

4.5 (optional) Store primitive (type info, texture id, verts) in a z-ordered list and draw later.

5.a Rasterize. It's different from the method I used in MAG64 where I tested each pixel inside the rectangle

formed around the triangle. This time the triangle edges are traced, and for each scan line, you get two new vertices which are mixed versions of the top and bottom verts on each edge. For perspective correctness, their depth difference must be taken into account.

5.b The same thing goes on each pixel on the scan line (between the two new verts), t must be adjusted.

	//PSUEDO, pretend everything is float for readability 

	//how far we've come on the scanline between two verts
	t = current_pixel / (end - start); // unadjusted, some number between 0. and 1.

	//divide t on both sides of the pixel by z
	w0 = (1.-t) / A.z;
	w1 = t / B.z;

	t = w1 / ( w0 + w1 );
	//depth correct interpolated vertex values ( uv's, rgb's)
	vertex = interpolate ( A, B, t );			   
	//our "shader program"		
	framebuffer[ current pixel ] = sample_texture( vertex.s, vertex.t );

I'm not going to write more detailed than this. I'll upload the src to github if anyone is interested.


16-bit color framebuffer, RGBA_5551. The alpha bit is used as a mask when drawing the "sky-box".

PS1-style matrix dithering.

4 function shading modes:

Solid color. The fastest.

Gouraud. Interpolate colors between vertices in linearly 2D.

Perspective correct Gouraud. Interpolate colors between vertices, with regards to depth of each vertex.

Perspective correct Gouraud Textured. As above, but with texture sampling. (With only one hard coded texture available to choose from and no system for multiple texture of any sort.)

Oh god, oh god, I spent a month on this??? This could have been made in Unity in one hour. But, I'm not feeling too bad. I've spent a good time of the evenings and weekends away from the computer enjoying the summer. :)

It would've been possible to take one or two days and just make a goofy little game like a simple space shooter or frogger or something by the end of the compo. The engine was good enough at that point (terrible, but capable). Tho, I'd risk that feared second place.

I'm feeling pretty happy about leaving this thing now. I've done enough to see what the fuzz is about, I can happily go back to more practical things. Like gl. Or maybe something entirely different. I'm a little sick of programming atm. Maybe do more art. Or watch watch 700 episodes of One Piece. (YOU decide in the comments!!)

Inspiration: Mechwarrior 2, Star Fox, Doom, and the usual PS1 stuff.


Astryl 7 years, 10 months ago

I suddenly feel like trying this myself again, just for the fun of it. :D

Maybe do more art. Or watch watch 700 episodes of One Piece.
Do more art while watching 700 episodes of One Piece!

Jani_Nykanen 7 years, 10 months ago

I decided to not use floats.
My first thought: oh, you are going to use doubles then? But integers, dear lord!

Writing a software renderer had been one of my "dreams" for a while, but I think doing that is still too hard if not impossible to me.

Or watch watch 700 episodes of One Piece.
Or eat 700 pieces of one cake. For no reason.

Nopykon 7 years, 10 months ago

Quote: Mega
Do more art while watching 700 episodes of One Piece!
I wish I could do that. Maybe after some lobotomy, I'd be able to process both things simultaneously. Hopefully that will let me control each eye separately as well.

Quote: Jani
Or eat 700 pieces of one cake. For no reason.
Piece of cake. Just cut a cake into 700 pieces, ~half a degree each, or put a piece of a cake in a blender. Thousands of pieces.