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.