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

215 lines
7.1 KiB
C++

#include "color.h"
#include "engine.h"
#include "geometry.h"
#include "util.h"
#include <cstdio>
// 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 CullBackfaces(
Mesh_LocalData &local, Mesh_TransformedData &transformed,
Point &camPosition)
{
transformed.faces.clear();
for (size_t f = 0; f < local.faces.size(); ++f)
{
unsigned int v0 = local.faces[f].vertIndex[0];
unsigned int v1 = local.faces[f].vertIndex[1];
unsigned int v2 = local.faces[f].vertIndex[2];
Vector v01 = transformed.verts[v1].point - transformed.verts[v0].point;
Vector v02 = transformed.verts[v2].point - transformed.verts[v0].point;
Vector normal = Vector::Cross(v01, v02);
// Store normal for flat shading
local.faces[f].normal = normal;
local.faces[f].normal.Normalize();
// Invert for Blender-compatibility
normal = -normal;
// Eye vector to viewport
Vector view = camPosition - transformed.verts[v0].point;
float dot = Vector::Dot(normal, view);
if (dot < EPSILON_E3)
{
transformed.faces.push_back(local.faces[f]);
}
}
}
void RenderMesh(
Engine_Buffer &buffer, Mesh_TransformedData &mesh,
Texture &texture, std::vector<TextureCoord> &uvs)
{
for(size_t f = 0; f < mesh.faces.size(); ++f)
{
// The vertices of this face
unsigned int vIndex0 = mesh.faces[f].vertIndex[0];
unsigned int vIndex1 = mesh.faces[f].vertIndex[1];
unsigned int vIndex2 = mesh.faces[f].vertIndex[2];
Vertex &v0 = mesh.verts[vIndex0];
Vertex &v1 = mesh.verts[vIndex1];
Vertex &v2 = mesh.verts[vIndex2];
// The UVs of this face's vertices
unsigned int tIndex0 = mesh.faces[f].textureIndex[0];
unsigned int tIndex1 = mesh.faces[f].textureIndex[1];
unsigned int tIndex2 = mesh.faces[f].textureIndex[2];
TextureCoord &t0 = uvs[tIndex0];
TextureCoord &t1 = uvs[tIndex1];
TextureCoord &t2 = uvs[tIndex2];
// Bounding box for barycentric calculations (top-left fill convention)
BoundingBox box(v0.point, v1.point, v2.point);
int yMin = (int)ceilf(box.yMin);
int yMax = (int)ceilf(box.yMax) - 1;
int xMin = (int)ceilf(box.xMin);
int xMax = (int)ceilf(box.xMax) - 1;
// Constants for this triangle used for barycentric calculations
Vector v01 = v1.point - v0.point;
Vector v02 = v2.point - v0.point;
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)
{
// Constant terms used for barycentric calculation
Point p(x, y, 1.0f);
Vector v0P = p - v0.point;
float dot0P01 = Vector::Dot(v0P, v01);
float dot0P02 = Vector::Dot(v0P, v02);
float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102));
// Calculate the barycentric coordinate of this point
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))
{
// Constant terms used in the U and V interpolation
float a = barycenter[0] * v1.point.w * v2.point.w;
float b = barycenter[1] * v0.point.w * v2.point.w;
float c = barycenter[2] * v0.point.w * v1.point.w;
float abc = 1.0f / (a + b + c);
// Interpolate U and V
float uInterp = ((a * t0.u) + (b * t1.u) + (c * t2.u)) * abc;
float vInterp = ((a * t0.v) + (b * t1.v) + (c * t2.v)) * abc;
// Convert U and V to pixels in the texture image
uInterp *= texture.width;
vInterp *= texture.height;
unsigned int u = (unsigned int)uInterp;
unsigned int v = (unsigned int)vInterp;
// Gouraud shading
ColorF32 shading =
(barycenter[0] * v0.color)
+ (barycenter[1] * v1.color)
+ (barycenter[2] * v2.color);
// Bilinear filtering
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);
// Shade the texture with the light calculations
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] * v0.point.w)
+ (barycenter[1] * v1.point.w)
+ (barycenter[2] * v2.point.w));
// Draw the pixel if it's closer than what's in the z-buffer
int pixel = (y * buffer.width + x);
if (zInv > buffer.zbuffer[pixel])
{
DrawPixel(buffer.buffer, buffer.width, color.u32, x, y);
buffer.zbuffer[pixel] = zInv;
}
}
}
}
}
}