1
0
Fork 0
2018-soft-3d-renderer/src/geometry.cpp

221 lines
7.2 KiB
C++

#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;
}
}
}
}
}
}