200 lines
6.0 KiB
C++
200 lines
6.0 KiB
C++
#include "Color.hpp"
|
|
#include "Engine.hpp"
|
|
#include "Geometry.hpp"
|
|
#include "Render.hpp"
|
|
|
|
|
|
class BoundingBox
|
|
{
|
|
public:
|
|
BoundingBox(Point const& v0, Point const& v1, Point const& v2)
|
|
{
|
|
yMin = std::min(v0.y, std::min(v1.y, v2.y));
|
|
yMax = std::max(v0.y, std::max(v1.y, v2.y));
|
|
|
|
xMin = std::min(v0.x, std::min(v1.x, v2.x));
|
|
xMax = std::max(v0.x, std::max(v1.x, v2.x));
|
|
}
|
|
|
|
float yMin, yMax;
|
|
float xMin, xMax;
|
|
};
|
|
|
|
|
|
void Render(EngineBuffer& buffer, EngineMemory& memory)
|
|
{
|
|
FaceList const& faces = memory.transFaces;
|
|
VertexList const& verts = memory.transVerts;
|
|
TextureList const& textures = memory.textures;
|
|
UVList const& uvs = memory.uvs;
|
|
|
|
for (size_t f = 0; f < faces.size; ++f)
|
|
{
|
|
Face const& face = faces.data[f];
|
|
|
|
Point const& p0 = verts.data[face.vertIndex[0]].position;
|
|
Point const& p1 = verts.data[face.vertIndex[1]].position;
|
|
Point const& p2 = verts.data[face.vertIndex[2]].position;
|
|
|
|
|
|
// Bounding box for barycentric calculations (top-left fill convention)
|
|
BoundingBox box(p0, p1, p2);
|
|
int yMin = static_cast<int>(std::max<float>(ceilf(box.yMin), 0));
|
|
int yMax = static_cast<int>(std::min<float>(ceilf(box.yMax) - 1, buffer.height - 1));
|
|
int xMin = static_cast<int>(std::max<float>(ceilf(box.xMin), 0));
|
|
int xMax = static_cast<int>(std::min<float>(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 1/z for the z-buffer
|
|
float zInv =
|
|
1.0f /
|
|
((barycenter[0] * p0.w)
|
|
+ (barycenter[1] * p1.w)
|
|
+ (barycenter[2] * p2.w));
|
|
|
|
|
|
// Compute pixel color if it's closer than what's in the z-buffer
|
|
if (zInv > memory.zbuffer[y][x])
|
|
{
|
|
// Update the depth buffer
|
|
memory.zbuffer[y][x] = zInv;
|
|
|
|
|
|
// Interpolate U and V of the texture for this point
|
|
Texture const& texture = textures.data[faces.data[f].materialIndex];
|
|
UV const& uv0 = uvs.data[face.uvIndex[0]];
|
|
UV const& uv1 = uvs.data[face.uvIndex[1]];
|
|
UV const& 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 = static_cast<unsigned int>(uInterp);
|
|
unsigned int v = static_cast<unsigned int>(vInterp);
|
|
float du = uInterp - u;
|
|
float dv = vInterp - v;
|
|
float duDiff = 1 - du;
|
|
float dvDiff = 1 - dv;
|
|
|
|
ColorU32 color(
|
|
static_cast<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)),
|
|
|
|
static_cast<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)),
|
|
|
|
static_cast<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)),
|
|
|
|
static_cast<uint8_t>(
|
|
(duDiff * dvDiff * texture.texels[v][u].a
|
|
+ du * dvDiff * texture.texels[v][u + 1].a
|
|
+ du * dv * texture.texels[v + 1][u + 1].a
|
|
+ duDiff * dv * texture.texels[v + 1][u].a)));
|
|
|
|
|
|
// Perform Gouraud shading on the texture
|
|
ColorF32 const& c0 = verts.data[face.vertIndex[0]].color;
|
|
ColorF32 const& c1 = verts.data[face.vertIndex[1]].color;
|
|
ColorF32 const& c2 = verts.data[face.vertIndex[2]].color;
|
|
|
|
ColorF32 shading =
|
|
(c0 * barycenter[0])
|
|
+ (c1 * barycenter[1])
|
|
+ (c2 * barycenter[2]);
|
|
|
|
|
|
// Ensure no color channel exceeds max
|
|
shading.Scale();
|
|
|
|
|
|
// Light the texture
|
|
color.b *= shading.b;
|
|
color.g *= shading.g;
|
|
color.r *= shading.r;
|
|
|
|
|
|
// Alpha blend the pixel
|
|
ColorU32* pixel = reinterpret_cast<ColorU32*>(&buffer.buffer[y * buffer.width + x]);
|
|
float alpha = static_cast<float>(color.a) / 255.0f;
|
|
float alphaDiff = 1.0f - alpha;
|
|
|
|
ColorU32 blended =
|
|
{
|
|
static_cast<uint8_t>(((alpha * static_cast<float>(color.b)) +
|
|
(alphaDiff * static_cast<float>(pixel->b)))),
|
|
|
|
static_cast<uint8_t>(((alpha * static_cast<float>(color.g)) +
|
|
(alphaDiff * static_cast<float>(pixel->g)))),
|
|
|
|
static_cast<uint8_t>(((alpha * static_cast<float>(color.r)) +
|
|
(alphaDiff * static_cast<float>(pixel->r)))),
|
|
|
|
static_cast<uint8_t>(((alpha * static_cast<float>(color.a)) +
|
|
(alphaDiff * static_cast<float>(pixel->a))))
|
|
};
|
|
|
|
|
|
// Draw
|
|
buffer.buffer[buffer.width * y + x] = blended.u32;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|