# Perspective-Correct Texture Mapping

I am working on a small rendering engine for a personal project and I am having issues with the texture mapping part of it.

It seems to work for some cases, but not for others. For example, when one of the vertices is behind the camera, the texture is stretched.

Seemingly Correct case Incorrect case

I am guessing that it has something to do with the texture mapping not being perspective-correct. I have tried various changes mainly involving the z-distance to the camera, but I could not find any quick fix to my code.

Here is my code for the perspective projection:

public double[] project(double x, double y, double z) {
double tx = x - camera.x;
double ty = z - camera.z;
double tz = y - camera.y;

double cx = Math.cos(camera.pitch);
double cy = Math.cos(camera.yaw);
double cz = Math.cos(camera.roll);

double sx = Math.sin(camera.pitch);
double sy = Math.sin(camera.yaw);
double sz = Math.sin(camera.roll);

double dx = cy * (sz * ty + cz * tx) - sy * tz;
double dy = sx * (cy * tz + sy * (sz * ty + cz * tx)) + cx * (cz * ty - sz * tx);
double dz = cx * (cy * tz + sy * (sz * ty + cz * tx)) - sx * (cz * ty - sz * tx);

double ez = 1.0 / Math.tan(FOV / 2.0);

double bx = ez / dz * dx;
double by = ez / dz * dy;

if (dz < 0.0) {
bx = -bx;
by = -by;
}

int px = (int) (width + bx * height) / 2;
int py = (int) (height + by * height) / 2;

return new double[] { px, py, dz };
}


and here my code for the texture mapping:

public double[] map(double x, double y, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) {
double A = (x0 - x) * (y0 - y2) - (y0 - y) * (x0 - x2);
double B = ((x0 - x) * (y1 - y3) - (y0 - y) * (x1 - x3) + (x1 - x) * (y0 - y2) - (y1 - y) * (x0 - x2)) / 2.0;
double C = (x1 - x) * (y1 - y3) - (y1 - y) * (x1 - x3);

double det = A - 2.0 * B + C;

double u;
if (det == 0.0) {
u = A / (A - C);
if (Double.isNaN(u) || u < 0.0 || u > 1.0)
return null;
} else {
double u1 = ((A - B) + Math.sqrt(B * B - A * C)) / det;
boolean u1valid = !Double.isNaN(u1) && u1 >= 0.0 && 1.0 >= u1;

double u2 = ((A - B) - Math.sqrt(B * B - A * C)) / det;
boolean u2valid = !Double.isNaN(u2) && u2 >= 0.0 && 1.0 >= u2;

if (u1valid && u2valid)
u = u1 < u2 ? u2 : u1;
else if (u1valid)
u = u1;
else if (u2valid)
u = u2;
else
return null;
}

double v1 = ((1.0 - u) * (x0 - x) + u * (x1 - x)) / ((1.0 - u) * (x0 - x2) + u * (x1 - x3));
boolean v1valid = !Double.isNaN(v1) && v1 >= 0.0 && 1.0 >= v1;

double v2 = ((1.0 - u) * (y0 - y) + u * (y1 - y)) / ((1.0 - u) * (y0 - y2) + u * (y1 - y3));
boolean v2valid = !Double.isNaN(v2) && v2 >= 0.0 && 1.0 >= v2;

double v;
if (v1valid && v2valid)
v = v1 < v2 ? v2 : v1;
else if (v1valid)
v = v1;
else if (v2valid)
v = v2;
else
return null;

return new double[] { u, v };
}


and here is my quad drawing code:

public void renderFace(Screen screen, int x0, int y0, int z0, int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) {
boolean render = true;

double[] p0 = screen.project(x0, y0, z0);
int px0 = (int) p0[0], py0 = (int) p0[1];
render |= p0[2] >= ZCLIP && px0 >= 0 && px0 < screen.width && py0 >= 0 && py0 < screen.height;

double[] p1 = screen.project(x1, y1, z1);
int px1 = (int) p1[0], py1 = (int) p1[1];
render |= p1[2] >= ZCLIP && px1 >= 0 && px1 < screen.width && py1 >= 0 && py1 < screen.height;

double[] p2 = screen.project(x2, y2, z2);
int px2 = (int) p2[0], py2 = (int) p2[1];
render |= p2[2] >= ZCLIP && px2 >= 0 && px2 < screen.width && py2 >= 0 && py2 < screen.height;

double[] p3 = screen.project(x3, y3, z3);
int px3 = (int) p3[0], py3 = (int) p3[1];
render |= p3[2] >= ZCLIP && px3 >= 0 && px3 < screen.width && py3 >= 0 && py3 < screen.height;

if (!render)
return;

int minX = Math.min(Math.min(px0, px1), Math.min(px2, px3));
if (minX < 0)
minX = 0;
if (minX > screen.width)
minX = screen.width;

int minY = Math.min(Math.min(py0, py1), Math.min(py2, py3));
if (minY < 0)
minY = 0;
if (minY > screen.height)
minY = screen.height;

int maxX = Math.max(Math.max(px0, px1), Math.max(px2, px3));
if (maxX < 0)
maxX = 0;
if (maxX > screen.width)
maxX = screen.width;

int maxY = Math.max(Math.max(py0, py1), Math.max(py2, py3));
if (maxY < 0)
maxY = 0;
if (maxY > screen.height)
maxY = screen.height;

if (minX == maxX || minY == maxY)
return;

for (int py = minY; py < maxY; ++py)
for (int px = minX; px < maxX; ++px) {
double[] uv = screen.map(px + 0.5, py + 0.5, px0, py0, px1, py1, px2, py2, px3, py3);
if (uv == null)
continue;
double u = uv[0], v = uv[1];

double pz = (1 - u) * ((1 - v) * p0[2] + v * p2[2]) + u * ((1 - v) * p1[2] + v * p3[2]);
if (pz < ZCLIP)
continue;

int texX = 15 - Math.min(15, (int) (16 * u));
int texY = 15 - Math.min(15, (int) (16 * v));
screen.setPixel(px, py, pz, Art.WALLS.getPixel(texX, texY) * BRICKS);
}
}


Can anyone point out what I am doing wrong? I am not very experienced as this is my first try at implementing a game engine.

Thank you for any insight.

• I didn't read your code because I saw this: "For example, when one of the vertices is behind the camera, the texture is stretched." You have to clip your polygons to a Z=constant plane in front of the camera or else all sorts of chaos will ensue :-) May 10, 2016 at 15:47
• @SimonF I am already clipping on the Z axis, see ZCLIP in the 3rd code.
– neat
May 10, 2016 at 16:32
• My apologies! I saw that opening paragraph and it "raised a red flag". So given "I am guessing that it has something to do with the texture mapping not being perspective-correct." .. I tried to understand your "map" function but got lost. For example, I don't understand why you need a square root. IIRC there's an initial set up from triangle coords to create 9 params, a,b,c d e f p q r, then @ pixel x,y, U= (a x +by + c)/(px+qy+r) and V = (d x +ey + f)/(px+qy+r). To determine the abc..pqr requires adjoint of a simplified "3x3" matrix (i.e. inverse w/out det) and mat muls. May 10, 2016 at 16:55
• @SimonF My map() function is adapted from this StackoverFlow answer. I thought it was perspective-correct, but I guess it isn't. Does all perspective-correct texture mapping techniques require to break down the polygon into triangles? Is there any formula that is not in a matricial form? Thanks!
– neat
May 10, 2016 at 17:49
• Why on earth do people want to avoid the matrix calculations. Triangles make it easy to transform the coordinates. May 11, 2016 at 10:22