The line on screen is still 2D, so you can keep what you have. (Although you want subpixel accuracy for stable results under motion, but this becomes more meaningful once you start with triangles/polygons)
For depth the problem is that it isn't linear, so you can not interpolate it along the start and end points of the line without a divide.
The common solution is to interpolate 1/depth instead, which works.
Ofc. you also need to include depth value support to your projection math, which i do not remember much about.
Here is a function i used to draw a textured scanline for a polygon or triangle with depth test:
void rastScanlineHTexF (int y, rast_paramsF &rp0, rast_paramsF &rp1, // scanline params
unsigned int* scr, unsigned int* scr_z, int scr_w, // color buffer, bepth buffer, screen width
texture32 *texture)
{
int x = (int)rp0.s;
int x2 = (int)rp1.s;
unsigned int *cbufL = scr + y*scr_w;
unsigned int *cbuf = cbufL + x;
unsigned int *cbufEnd = cbufL + x2;
if (cbufEnd<=cbuf) return; // not sure what this is; probably some hack to deal with errors
float inv = 1.0f / (rp1.s-rp0.s);
float sub = 1.0f-(rp0.s - (float)x); // sub pixel accuracy (adds complexity, but otherwise jitters like Playstation 1)
float slopeZ = inv * (rp1.z-rp0.z);
float z = rp0.z + slopeZ * sub;
unsigned int *zbuf = cbuf + (scr_z-scr);
float slopeU = (rp1.u-rp0.u) * inv;
float slopeV = (rp1.v-rp0.v) * inv;
float u = rp0.u + slopeU * sub;
float v = rp0.v + slopeV * sub;
while (cbuf < cbufEnd)
{
float real_z = 1.0f / z; // that's the division i have mentioned to turn interpolted 1/z back to z. Quake only did this once for 4 pixels, but i did not mind performance here. It gives us perspective correct textrue mapping.
float real_u = u * real_z;
float real_v = v * real_z;
unsigned int zI = ((unsigned int)toFP (z))<<1;
if (zI > *zbuf) // depth test
rastPixelFTex32 (cbuf, zbuf, zI, real_u, real_v, texture);
zbuf++; cbuf++;
z += slopeZ;
u += slopeU;
v += slopeV;
}
}
That's already a port from older renderer where i had used fixed point integers instead floats. The Z buffer is still fixed point, and toFP() only handles the conversation.
Here comments to explain some params:
struct rast_paramsF
{
float u; // u&v must be first, to allow swapped indexing
float v; //
float s; // screen x
float z; // screen z
Nothing special about the pixel draw:
inline void rastPixelFTex32 (unsigned int* cbuf, unsigned int* zbuf, int z,
float u, float v, texture32 *texture)
{
*zbuf = z | 0x1; // set Z and Alpha
int uindex = int (u);
int vindex = int (v);
int index = uindex + vindex * texture->width;
int col32 = texture->bits[index];
*cbuf = col32;
}
Not sure how much this really helps.
Triangle/polygon setup is way more complicated, and clipping as well.
Basic rasterization is harder than basic ray tracing. : )