Software Textured Triangle Rasterization

I’m having an issue I suspect has to do with the accuracy of a fixed-int, and that I’m not recalculating, I’m just stepping.

64x64 bitmap at 800x600 (more severe when rendered at a different ratio)

http://puu.sh/q535m/0fd7bfd7cb.png

100x75 (1/8th size of screen) bitmap at 800x600 (only affects second triangle, possibly has to do with rotation?)

http://puu.sh/q53M6/999bd1b04f.png

This is how I’m rendering it:

s.drawBitmap(rainbow, 0, 0, getWidth(),getHeight());

public void drawBitmap(Bitmap src, int x, int y, int w, int h) {
		drawBitmap(src, x, y, x + w, y + h, 0, 0, src.width, src.height);
	}

		public void drawBitmap(Bitmap src, int dx0, int dy0, int dx1, int dy1, int sx0, int sy0, int sx1, int sy1) {
		drawBitmapTriangle(src,
				dx0, dy0, sx0, sy0,	// x0, y0, u0, v0
				dx1, dy0, sx1, sy0,	// x1, y1, u1, v1
				dx0, dy1, sx0, sy1	// x2, y2, u2, v2
		);

		drawBitmapTriangle(src,
				dx1, dy1, sx1, sy1,
				dx1, dy0, sx1, sy0,
				dx0, dy1, sx0, sy1
		);
	}

	public void drawBitmapTriangle(Bitmap src, int x0, int y0, int u0, int v0, int x1, int y1, int u1, int v1, int x2, int y2, int u2, int v2) {
		// C above A
		if (y2 < y0) {
			int tmp = x0;
			x0 = x2;
			x2 = tmp;

			tmp = y0;
			y0 = y2;
			y2 = tmp;

			tmp = u0;
			u0 = u2;
			u2 = tmp;

			tmp = v0;
			v0 = v2;
			v2 = tmp;
		}

		// B above A
		if (y1 < y0) {
			int tmp = x0;
			x0 = x1;
			x1 = tmp;

			tmp = y0;
			y0 = y1;
			y1 = tmp;

			tmp = u0;
			u0 = u1;
			u1 = tmp;

			tmp = v0;
			v0 = v1;
			v1 = tmp;
		}

		// C above B
		if (y2 < y1) {
			int tmp = x1;
			x1 = x2;
			x2 = tmp;

			tmp = y1;
			y1 = y2;
			y2 = tmp;

			tmp = u1;
			u1 = u2;
			u2 = tmp;

			tmp = v1;
			v1 = v2;
			v2 = tmp;
		}

		// A is below our boundaries, so we know the entire triangle is not visible.
		if (y0 >= maxY) {
			return;
		}

		// Our slope values
		int mx0 = 0, mx1 = 0, mx2 = 0;
		int mu0 = 0, mu1 = 0, mu2 = 0;
		int mv0 = 0, mv1 = 0, mv2 = 0;

		// A to B
		if (y0 != y1) {
			int d = y1 - y0;
			mx0 = ((x1 - x0) << 16) / d;
			mu0 = ((u1 - u0) << 16) / d;
			mv0 = ((v1 - v0) << 16) / d;
		}

		// B to C
		if (y1 != y2) {
			int d = y2 - y1;
			mx1 = ((x2 - x1) << 16) / d;
			mu1 = ((u2 - u1) << 16) / d;
			mv1 = ((v2 - v1) << 16) / d;
		}

		// A to C
		if (y0 != y2) {
			int d = y2 - y0;
			mx2 = ((x2 - x0) << 16) / d;
			mu2 = ((u2 - u0) << 16) / d;
			mv2 = ((v2 - v0) << 16) / d;
		}

		// To 16.16 fixed
		x0 <<= 16;
		u0 <<= 16;
		v0 <<= 16;

		// Start C at B
		x2 = x1 << 16;
		u2 = u1 << 16;
		v2 = v1 << 16;

		// A (16.16)
		x1 = x0;
		u1 = u0;
		v1 = v0;

		// A is above the minimum boundary
		if (y0 < minY) {
			// how far we need to step vertically to get back into bounds
			int step = minY - y0;

			// Step A toward C
			x0 += mx2 * step;
			u0 += mu2 * step;
			v0 += mv2 * step;

			// Step A toward B
			x1 += mx0 * step;
			u1 += mu0 * step;
			v1 += mv0 * step;

			y0 = 0;
		}

		// B is above minimum boundary
		if (y1 < minY) {
			int step = minY - y1;

			// Step B toward C
			x2 += mx1 * step;
			u2 += mu1 * step;
			v2 += mv1 * step;

			y1 = 0;
		}

		// The vertical distance between B and A
		int remaining = y1 - y0;
		int offset = y0 * bitmap.width;

		// The first segment
		while (remaining-- > 0) {
			// stop if we leave the boundaries
			if (++y0 > maxY) {
				return;
			}

			drawBitmapScanline(src, offset,
					x0 >> 16, u0, v0,
					x1 >> 16, u1, v1
			);

			// step toward C
			x0 += mx2;
			u0 += mu2;
			v0 += mv2;

			// step toward B
			x1 += mx0;
			u1 += mu0;
			v1 += mv0;

			// step one row down
			offset += bitmap.width;
		}

		// The vertical distance between B and C
		remaining = y2 - y1;

		// The second segment
		while (remaining-- > 0) {
			// stop if we leave the boundaries
			if (++y0 > maxY) {
				return;
			}

			drawBitmapScanline(src, offset,
					x0 >> 16, u0, v0,
					x2 >> 16, u2, v2
			);

			// step toward C
			x0 += mx2;
			u0 += mu2;
			v0 += mv2;

			// step toward C
			x2 += mx1;
			u2 += mu1;
			v2 += mv1;

			// step one row down
			offset += bitmap.width;
		}
	}

	private void drawBitmapScanline(Bitmap src, int offset, int x0, int u0, int v0, int x1, int u1, int v1) {
		// nothing to draw
		if (x0 == x1) {
			return;
		}

		// flip
		if (x0 > x1) {
			int tmp = x0;
			x0 = x1;
			x1 = tmp;

			tmp = u0;
			u0 = u1;
			u1 = tmp;

			tmp = v0;
			v0 = v1;
			v1 = tmp;
		}

		int length = x1 - x0;

		// slopes
		int mu = (u1 - u0) / length;
		int mv = (v1 - v0) / length;

		// keep within bounds
		if (x0 < minX) {
			int step = minX - x0;
			u0 += step * mu;
			v0 += step * mv;
			x0 = minX;
		}

		if (x1 > maxX) {
			x1 = maxX;
		}

		length = x1 - x0;
		offset += x0;

		if (alpha == 256) {
			while (length-- > 0) {
				int x = (u0 >> 16);
				int y = (v0 >> 16);

				// clamp
				if (x < 0) x = 0;
				if (y < 0) y = 0;
				if (x >= src.width) x = src.width - 1;
				if (y >= src.height) y = src.height - 1;

				bitmap.data[offset++] = src.data[x + src.offsetY[y]];

				u0 += mu;
				v0 += mv;
			}
		} else {
			int beta = 256 - alpha;
			while (length-- > 0) {
				int x = u0 >> 16;
				int y = v0 >> 16;

				// clamp
				if (x < 0) x = 0;
				if (y < 0) y = 0;
				if (x >= src.width) x = src.width - 1;
				if (y >= src.height) y = src.height - 1;

				bitmap.data[offset] = mix(bitmap.data[offset++], src.data[x + src.offsetY[y]], alpha, beta);

				u0 += mu;
				v0 += mv;
			}
		}
	}

Well think about it, look through your rasterization code and see how the second triangle is treated differently. See what’s flipped and reversed.

That’s the thing, is I’m having trouble pinpointing the problem.

I’ve tried using floats and doubles instead. But the problem actually gets worse. So it’s probably the way I’m stepping. I’ve tried pre-stepping half a pixel but that’s no use.

I’m pretty sure this works, i havent tested it though.


	public static void fillTriangleAffine(int x1, int y1, int x2, int y2, int x3, int y3, int u1, int v1, int u2, int v2, int u3, int v3) {
		int dx = x1 - x2;
		if (Math.abs(dx) >= maxX) {
			return;
		}
		dx = x3 - x2;
		if (Math.abs(dx) >= maxX) {
			return;
		}

		if (y1 > y2) {
			temp = x1;
			x1 = x2;
			x2 = temp;
			temp = y1;
			y1 = y2;
			y2 = temp;
			temp = u1;
			u1 = u2;
			u2 = temp;
			temp = v1;
			v1 = v2;
			v2 = temp;
		}

		if (y2 > y3) {
			temp = x2;
			x2 = x3;
			x3 = temp;
			temp = y2;
			y2 = y3;
			y3 = temp;
			temp = u2;
			u2 = u3;
			u3 = temp;
			temp = v2;
			v2 = v3;
			v3 = temp;
		}

		if (y1 > y2) {
			temp = x1;
			x1 = x2;
			x2 = temp;
			temp = y1;
			y1 = y2;
			y2 = temp;
			temp = u1;
			u1 = u2;
			u2 = temp;
			temp = v1;
			v1 = v2;
			v2 = temp;
		}

		float steps12 = 65536.0f / Math.abs(y2 - y1);
		float steps23 = 65536.0f / Math.abs(y3 - y2);
		float steps13 = 65536.0f / Math.abs(y3 - y1);

		int xSlope12 = (int) ((x2 - x1) * steps12);
		int xSlope23 = (int) ((x3 - x2) * steps23);
		int xSlope13 = (int) ((x3 - x1) * steps13);

		int uSlope12 = (int) ((u2 - u1) * steps12);
		int uSlope23 = (int) ((u3 - u2) * steps23);
		int uSlope13 = (int) ((u3 - u1) * steps13);

		int vSlope12 = (int) ((v2 - v1) * steps12);
		int vSlope23 = (int) ((v3 - v2) * steps23);
		int vSlope13 = (int) ((v3 - v1) * steps13);

		int xA = x1 << 16;
		int xB = x1 << 16;
		int uA = u1 << 16;
		int uB = u1 << 16;
		int vA = v1 << 16;
		int vB = v1 << 16;

		while (y1 < y2) {
			drawScanLineAffine(target, y1, xA, xB, uA, uB, vA, vB);
			xA += xSlope12;
			xB += xSlope13;
			uA += uSlope12;
			uB += uSlope13;
			vA += vSlope12;
			vB += vSlope13;
			y1++;
		}

		xA = x2 << 16;
		uA = u2 << 16;
		vA = v2 << 16;

		while (y2 < y3) {
			drawScanLineAffine(target, y2, xA, xB, uA, uB, vA, vB);
			xA += xSlope23;
			xB += xSlope13;
			uA += uSlope23;
			uB += uSlope13;
			vA += vSlope23;
			vB += vSlope13;
			y2++;
		}
	}

	public static void drawScanLineAffine(int[] target, int y, int xA, int xB, int uA, int uB, int vA, int vB) {
		if (y < 0 || y > maxY) {
			return;
		}

		xA >>= 16;
		xB >>= 16;

		if ((xB - xA) == 0) {
			return;
		}

		if (xA > xB) {
			temp = xA;
			xA = xB;
			xB = temp;
			temp = uA;
			uA = uB;
			uB = temp;
			temp = vA;
			vA = vB;
			vB = temp;
		}

		int dx = xB - xA;
		uSlope = (uB - uA) / dx;
		vSlope = (vB - vA) / dx;

		if (xA < 0 && xB > maxX) {
			return;
		}

		if (Math.abs(dx) >= maxX) {
			return;
		}

		if (xB > maxX) {
			xB = maxX;
		}

		if (xA < 0) {
			uA -= xA * uSlope;
			vA -= xA * vSlope;
			xA = 0;
		}

		if (xA >= xB) {
			return;
		}

		xA += yOffsetTable[y];
		xB += yOffsetTable[y];

		while (xA < xB) {
			int color24 = texture.getColor(uA >> 16, vA >> 16);
			if (color24 != 0) {
				target[xA] = color_palette[color24];
			}
			uA += uSlope;
			vA += vSlope;
			xA++;
		}
	}