/* * 3D Engine * triangle.c: * Based on pikuma.com 3D software renderer in C * Copyright (c) 2021 986-Studio. All rights reserved. * * Created by Manoƫl Trapier on 04/03/2021. */ #include #include #include #include #include bool doPerpectiveCorrection = true; static vec3_t barycentricWeights(vec2_t a, vec2_t b, vec2_t c, vec2_t p) { vec2_t ab = vec2SubVectors(b, a); vec2_t bc = vec2SubVectors(c, b); vec2_t ac = vec2SubVectors(c, a); vec2_t ap = vec2SubVectors(p, a); vec2_t bp = vec2SubVectors(p, b); vec3_t ret; double areaTriangleABC = (ab.x * ac.y) - (ab.y * ac.x); double alpha = ((bc.x * bp.y) - (bp.x * bc.y)) / areaTriangleABC; double beta = ((ap.x * ac.y) - (ac.x * ap.y)) / areaTriangleABC; double gamma = 1 - alpha - beta; ret.x = alpha; ret.y = beta; ret.z = gamma; return ret; } void drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, colour_t colour) { drawLine(x0, y0, x1, y1, colour); drawLine(x1, y1, x2, y2, colour); drawLine(x2, y2, x0, y0, colour); } /* ----------------------------------- Filled triangles ----------------------------------- */ static void drawZPixel(int32_t x, int32_t y, vec4_t a, vec4_t b, vec4_t c, colour_t colour) { vec2_t pointP = { x, y }; vec2_t a2 = vec2FromVec4(a); vec2_t b2 = vec2FromVec4(b); vec2_t c2 = vec2FromVec4(c); if ((x < 0) || (y < 0) || (x > windowWidth) ||(y > windowHeight)) { return; } vec3_t weights = barycentricWeights(a2, b2, c2, pointP); double alpha = weights.x; double beta = weights.y; double gamma = weights.z; double interpolatedReciprocalW; interpolatedReciprocalW = (1 / a.w) * alpha + (1 / b.w) * beta + (1 / c.w) * gamma; /* Inverse to get logical W invrementing going further to us */ interpolatedReciprocalW = 1 - interpolatedReciprocalW; if (interpolatedReciprocalW < zBuffer[(windowWidth * y) + x]) { drawPixel(x, y, colour); /* Update Z Buffer */ zBuffer[(windowWidth * y) + x] = interpolatedReciprocalW; } } void drawFilledTriangle(struct triangle_t *t) { int32_t x, y; if (t->points[0].y > t->points[1].y) { doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y); doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w); } if (t->points[1].y > t->points[2].y) { doubleSwap(&t->points[1].x, &t->points[2].x); doubleSwap(&t->points[1].y, &t->points[2].y); doubleSwap(&t->points[1].z, &t->points[2].z); doubleSwap(&t->points[1].w, &t->points[2].w); } if (t->points[0].y > t->points[1].y) { doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y); doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w); } vec4_t a = t->points[0]; vec4_t b = t->points[1]; vec4_t c = t->points[2]; /* Render the top part */ double inverseSlope1 = 0; double inverseSlope2 = 0; int32_t x0 = a.x, y0 = a.y; int32_t x1 = b.x, y1 = b.y; int32_t x2 = c.x, y2 = c.y; if ((y1 - y0) != 0) { inverseSlope1 = (double)(x1 - x0) / abs(y1 - y0); } if ((y2 - y0) != 0) { inverseSlope2 = (double)(x2 - x0) / abs(y2 - y0); } if ((y1 - y0) != 0) { for (y = y0 ; y <= y1 ; y++) { int32_t xStart = x1 + (y - y1) * inverseSlope1; int32_t xEnd = x0 + (y - y0) * inverseSlope2; if (doZBuffer) { if (xEnd < xStart) { intSwap(&xStart, &xEnd); } for (x = xStart ; x <= xEnd ; x++) { drawZPixel(x, y, a, b, c,t->colour); } } else { drawHLine(xStart, y, xEnd, t->colour); } } } /* Render the bottom part */ inverseSlope1 = 0; if ((y2 - y1) != 0) { inverseSlope1 = (double)(x2 - x1) / abs(y2 - y1); } if ((y2 - y1) != 0) { for (y = y1 ; y <= y2 ; y++) { int32_t xStart = x2 + (y - y2) * inverseSlope1; int32_t xEnd = x0 + (y - y0) * inverseSlope2; if (doZBuffer) { if (xEnd < xStart) { intSwap(&xStart, &xEnd); } for (x = xStart ; x <= xEnd ; x++) { drawZPixel(x, y, a, b, c,t->colour); } } else { drawHLine(xStart, y, xEnd, t->colour); } } } } /* ----------------------------------- Textured triangles ----------------------------------- */ static void drawTexel(int32_t x, int32_t y, vec4_t a, vec4_t b, vec4_t c, tex2_t ta, tex2_t tb, tex2_t tc, colour_t *texture) { vec2_t pointP = { x, y }; vec2_t a2 = vec2FromVec4(a); vec2_t b2 = vec2FromVec4(b); vec2_t c2 = vec2FromVec4(c); if ((x < 0) || (y < 0) || (x > windowWidth) ||(y > windowHeight)) { return; } vec3_t weights = barycentricWeights(a2, b2, c2, pointP); double alpha = weights.x; double beta = weights.y; double gamma = weights.z; double interpolatedU, interpolatedV, interpolatedReciprocalW; int32_t texX, texY; interpolatedReciprocalW = (1 / a.w) * alpha + (1 / b.w) * beta + (1 / c.w) * gamma; if (doPerpectiveCorrection) { interpolatedU = (ta.u / a.w) * alpha + (tb.u / b.w) * beta + (tc.u / c.w) * gamma; interpolatedV = ((1 - ta.v) / a.w) * alpha + ((1 - tb.v) / b.w) * beta + ((1 - tc.v) / c.w) * gamma; interpolatedU /= interpolatedReciprocalW; interpolatedV /= interpolatedReciprocalW; } else { interpolatedU = ta.u * alpha + tb.u * beta + tc.u * gamma; interpolatedV = (1 - ta.v) * alpha + (1 - tb.v) * beta + (1 - tc.v) * gamma; } texX = abs((int32_t)(interpolatedU * textureWidth)); texY = abs((int32_t)(interpolatedV * textureHeight)); texX = texX % textureWidth; texY = texY % textureWidth; if (doZBuffer) { /* Inverse to get logical W invrementing going further to us */ interpolatedReciprocalW = 1 - interpolatedReciprocalW; if (interpolatedReciprocalW < zBuffer[(windowWidth * y) + x]) { drawPixel(x, y, texture[(texY * textureWidth) + texX]); /* Update Z Buffer */ zBuffer[(windowWidth * y) + x] = interpolatedReciprocalW; } } else { drawPixel(x, y, texture[(texY * textureWidth) + texX]); } } void drawTextureTriangle(struct triangle_t *t) { int32_t x, y; if (t->points[0].y > t->points[1].y) { doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y); doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w); doubleSwap(&t->textureCoordinates[0].u, &t->textureCoordinates[1].u); doubleSwap(&t->textureCoordinates[0].v, &t->textureCoordinates[1].v); } if (t->points[1].y > t->points[2].y) { doubleSwap(&t->points[1].x, &t->points[2].x); doubleSwap(&t->points[1].y, &t->points[2].y); doubleSwap(&t->points[1].z, &t->points[2].z); doubleSwap(&t->points[1].w, &t->points[2].w); doubleSwap(&t->textureCoordinates[1].u, &t->textureCoordinates[2].u); doubleSwap(&t->textureCoordinates[1].v, &t->textureCoordinates[2].v); } if (t->points[0].y > t->points[1].y) { doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y); doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w); doubleSwap(&t->textureCoordinates[0].u, &t->textureCoordinates[1].u); doubleSwap(&t->textureCoordinates[0].v, &t->textureCoordinates[1].v); } vec4_t a = t->points[0]; vec4_t b = t->points[1]; vec4_t c = t->points[2]; /* Render the top part */ double inverseSlope1 = 0; double inverseSlope2 = 0; int32_t x0 = a.x, y0 = a.y; int32_t x1 = b.x, y1 = b.y; int32_t x2 = c.x, y2 = c.y; if ((y1 - y0) != 0) { inverseSlope1 = (double)(x1 - x0) / abs(y1 - y0); } if ((y2 - y0) != 0) { inverseSlope2 = (double)(x2 - x0) / abs(y2 - y0); } if ((y1 - y0) != 0) { for (y = y0 ; y <= y1 ; y++) { int32_t xStart = x1 + (y - y1) * inverseSlope1; int32_t xEnd = x0 + (y - y0) * inverseSlope2; if (xEnd < xStart) { intSwap(&xStart, &xEnd); } for (x = xStart ; x <= xEnd ; x++) { drawTexel(x, y, a, b, c, t->textureCoordinates[0], t->textureCoordinates[1], t->textureCoordinates[2], t->texture); } } } /* Render the bottom part */ inverseSlope1 = 0; if ((y2 - y1) != 0) { inverseSlope1 = (double)(x2 - x1) / abs(y2 - y1); } if ((y2 - y1) != 0) { for (y = y1 ; y <= y2 ; y++) { int32_t xStart = x2 + (y - y2) * inverseSlope1; int32_t xEnd = x0 + (y - y0) * inverseSlope2; if (xEnd < xStart) { intSwap(&xStart, &xEnd); } for (x = xStart ; x <= xEnd ; x++) { drawTexel(x, y, a, b, c, t->textureCoordinates[0], t->textureCoordinates[1], t->textureCoordinates[2], t->texture); } } } } /* ---- Utility ---- */ int compareTrianglesZOrder(const void *p1, const void *p2) { triangle_t *t1 = (struct triangle_t *)p1; triangle_t *t2 = (struct triangle_t *)p2; if (t1->averageDepth > t2->averageDepth) { return -1; } else if (t1->averageDepth < t2->averageDepth) { return 1; } return 0; }