#include "color.h" #include "engine.h" #include "geometry.h" #include "util.h" // MACROS #define DrawPixel(buffer, width, color, x, y)\ {\ buffer[width * y + x] = color;\ } // STRUCTURES struct BoundingBox { BoundingBox(Point &v0, Point &v1, Point &v2) { yMin = MIN(v0.y, MIN(v1.y, v2.y)); yMax = MAX(v0.y, MAX(v1.y, v2.y)); xMin = MIN(v0.x, MIN(v1.x, v2.x)); xMax = MAX(v0.x, MAX(v1.x, v2.x)); } float yMin, yMax; float xMin, xMax; }; // PUBLIC FUNCTIONS void ClipAndCull( VertexList &verts, FaceList &localFaces, FaceList &transFaces, Point &camPosition) { int faceIndex = 0; for (size_t f = 0; f < localFaces.size; ++f) { Face &face = localFaces.data[f]; Point &p0 = verts.data[face.vertIndex[0]].point; Point &p1 = verts.data[face.vertIndex[1]].point; Point &p2 = verts.data[face.vertIndex[2]].point; // Ignore this face if its Z is outside the Z clip planes if ( (p0.z < -p0.w) || (p0.z > p0.w) || (p1.z < -p1.w) || (p1.z > p1.w) || (p2.z < -p2.w) || (p2.z > p2.w)) { continue; } // Calculate the face's normal (inverted for Blender-compatibility) Vector v01 = p1 - p0; Vector v02 = p2 - p0; Vector normal = -Vector::Cross(v01, v02); // Eye vector to viewport Vector view = camPosition - p0; float dot = Vector::Dot(normal, view); // Not a backface; add it to the list if (dot < EPSILON_E3) { transFaces.data[faceIndex] = face; ++faceIndex; transFaces.size = (size_t)faceIndex; } } } void RenderMesh( Engine_Buffer &buffer, FaceList &faces, VertexList &verts, UVList &uvs, TextureList &textures) { for(size_t f = 0; f < faces.size; ++f) { Face &face = faces.data[f]; Point &p0 = verts.data[face.vertIndex[0]].point; Point &p1 = verts.data[face.vertIndex[1]].point; Point &p2 = verts.data[face.vertIndex[2]].point; // Bounding box for barycentric calculations (top-left fill convention) BoundingBox box(p0, p1, p2); int yMin = (int)MAX(ceilf(box.yMin), 0); int yMax = (int)MIN(ceilf(box.yMax) - 1, buffer.height - 1); int xMin = (int)MAX(ceilf(box.xMin), 0); int xMax = (int)MIN(ceilf(box.xMax) - 1, buffer.width - 1); // Constants for this triangle used for barycentric calculations Vector v01 = p1 - p0; Vector v02 = p2 - p0; float dot0101 = Vector::Dot(v01, v01); float dot0102 = Vector::Dot(v01, v02); float dot0202 = Vector::Dot(v02, v02); // Iterate over the bounding box and determine if each point is in the triangle for (int y = yMin; y <= yMax; ++y) { for (int x = xMin; x <= xMax; ++x) { // Calculate the barycentric coordinate of this point Point p(x, y, 1.0f); Vector v0P = p - p0; float dot0P01 = Vector::Dot(v0P, v01); float dot0P02 = Vector::Dot(v0P, v02); float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102)); float barycenter[3]; barycenter[1] = (dot0202 * dot0P01 - dot0102 * dot0P02) * denomInv; barycenter[2] = (dot0101 * dot0P02 - dot0102 * dot0P01) * denomInv; barycenter[0] = 1.0f - barycenter[1] - barycenter[2]; // Point is inside the triangle if ( (barycenter[0] >= 0.0f) && (barycenter[1] >= 0.0f) && (barycenter[2] >= 0.0f)) { // Interpolate U and V of the texture for this point Texture &texture = textures.data[faces.data[f].materialIndex]; UV &uv0 = uvs.data[face.uvIndex[0]]; UV &uv1 = uvs.data[face.uvIndex[1]]; UV &uv2 = uvs.data[face.uvIndex[2]]; float a = barycenter[0] * p1.w * p2.w; float b = barycenter[1] * p0.w * p2.w; float c = barycenter[2] * p0.w * p1.w; float abc = 1.0f / (a + b + c); float uInterp = ((a * uv0.u) + (b * uv1.u) + (c * uv2.u)) * abc * texture.width; float vInterp = ((a * uv0.v) + (b * uv1.v) + (c * uv2.v)) * abc * texture.height; // Bilinear filtering unsigned int u = (unsigned int)uInterp; unsigned int v = (unsigned int)vInterp; float du = uInterp - u; float dv = vInterp - v; float duDiff = 1 - du; float dvDiff = 1 - dv; ColorU32 color; color.b = (uint8_t) (duDiff * dvDiff * texture.texels[v][u].b + du * dvDiff * texture.texels[v][u+1].b + du * dv * texture.texels[v+1][u+1].b + duDiff * dv * texture.texels[v+1][u].b); color.g = (uint8_t) (duDiff * dvDiff * texture.texels[v][u].g + du * dvDiff * texture.texels[v][u+1].g + du * dv * texture.texels[v+1][u+1].g + duDiff * dv * texture.texels[v+1][u].g); color.r = (uint8_t) (duDiff * dvDiff * texture.texels[v][u].r + du * dvDiff * texture.texels[v][u+1].r + du * dv * texture.texels[v+1][u+1].r + duDiff * dv * texture.texels[v+1][u].r); // Perform Gouraud shading on the texture ColorF32 &c0 = verts.data[face.vertIndex[0]].color; ColorF32 &c1 = verts.data[face.vertIndex[1]].color; ColorF32 &c2 = verts.data[face.vertIndex[2]].color; ColorF32 shading = (barycenter[0] * c0) + (barycenter[1] * c1) + (barycenter[2] * c2); // Ensure no color channel exceeds 1.0 ScaleColor(shading); // Light the texture color.b *= shading.b; color.g *= shading.g; color.r *= shading.r; // Interpolate 1/z for the z-buffer float zInv = 1.0f / ((barycenter[0] * p0.w) + (barycenter[1] * p1.w) + (barycenter[2] * p2.w)); // Draw the pixel if it's closer than what's in the z-buffer if (zInv > buffer.zbuffer[y][x]) { DrawPixel(buffer.buffer, buffer.width, color.u32, x, y); buffer.zbuffer[y][x] = zInv; } } } } } }