Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
lib3d/libmatrix: Way more efficient tri rasterization
Browse files Browse the repository at this point in the history
By calculating barycentric coordinates of the corner of the tri's bounding box plus step values for each pixel, we can avoid needing to do a ton of math and reduce most of it to float additions within the loop.
  • Loading branch information
byteduck committed Mar 8, 2024
1 parent 61271ac commit 6d682cd
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 123 deletions.
26 changes: 26 additions & 0 deletions libraries/lib3d/MatrixUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,32 @@ namespace Lib3D {
{0,0,0,1}};
}

Matrix4f constexpr scale(float x, float y, float z) {
return {
{x, 0, 0, 0},
{0, y, 0, 0},
{0, 0, z, 0},
{0, 0, 0, 1}
};
}

Matrix4f constexpr scale(Vec3f trans) {
return scale(trans.x(), trans.y(), trans.z());
}

Matrix4f constexpr translate(float x, float y, float z) {
return {
{1, 0, 0, x},
{0, 1, 0, y},
{0, 0, 1, z},
{0, 0, 0, 1}
};
}

Matrix4f constexpr translate(Vec3f trans) {
return translate(trans.x(), trans.y(), trans.z());
}

float constexpr det3f(Matrix3f mat) {
return mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) -
mat[0][1] * (mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) +
Expand Down
241 changes: 131 additions & 110 deletions libraries/lib3d/RenderContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,26 @@ void RenderContext::clear(Vec4f color) {

#define f2i(f) ((int) ((f)))

void RenderContext::line(Vec3f a, Vec3f b, Vec4f color) {
void RenderContext::line(Vertex a, Vertex b) {
bool steep = false;
if(abs(a.x() - b.x()) < abs(a.y() - b.y())) {
a = {a.y(), a.x(), a.z()};
b = {b.y(), b.x(), b.z()};
if(abs(a.pos.x() - b.pos.x()) < abs(a.pos.y() - b.pos.y())) {
a.pos = {a.pos.y(), a.pos.x(), a.pos.z()};
b.pos = {b.pos.y(), b.pos.x(), b.pos.z()};
steep = true;
}
if(a.x() > b.x())
if(a.pos.x() > b.pos.x())
std::swap(a, b);
Vec3f diff = b - a;
float derr = abs(diff.y() / (float) diff.x());
auto diff = b.pos - a.pos;
const float derr = abs(diff.y() / (float) diff.x());
float err = 0;
float y = a.y();
float z = a.z();
float zstep = diff.z() / diff.x();
for(float x = a.x(); x <= b.x(); x++) {
int xint = f2i(x);
int yint = f2i(y);
float y = a.pos.y();
float z = a.pos.z();
const float zstep = diff.z() / diff.x();
for(float x = a.pos.x(); x <= b.pos.x(); x++) {
const int xint = f2i(x);
const int yint = f2i(y);
const float lerp = (x - a.pos.x()) / diff.x();
const Vec4f color = a.color * (1.0f - lerp) + b.color * lerp;
if(steep) {
if (m_buffers.depth.get(yint, xint) < -z) {
m_buffers.color.set(yint, xint, color);
Expand All @@ -54,7 +56,7 @@ void RenderContext::line(Vec3f a, Vec3f b, Vec4f color) {
err += derr;
z += zstep;
if (err > 0.5f) {
y += (a.y() < b.y() ? 1 : -1);
y += (a.pos.y() < b.pos.y() ? 1.f : -1.f);
err -= 1.0f;
}
}
Expand All @@ -64,131 +66,150 @@ void RenderContext::tri(std::array<Vertex, 3> verts) {
tri_barycentric(verts);
}

void RenderContext::tri_simple(std::array<Vertex, 3> verts) {
Vec3f tri[3];
void RenderContext::tri_barycentric(std::array<Vertex, 3> verts) {
// Transform into world coords
std::array<Vec3f, 3> tri;
for(int i = 0; i < 3; i++) {
tri[i] = screenspace((m_projmat * m_modelmat * verts[i].pos).transpose()[0]);
}
Vec4f color = verts[0].color;

// Sort vertices by y
if(tri[0].y() > tri[1].y())
std::swap(tri[0], tri[1]);
if(tri[0].y() > tri[2].y())
std::swap(tri[0], tri[2]);
if(tri[1].y() > tri[2].y())
std::swap(tri[1], tri[2]);

float height = tri[2].y() - tri[0].y();
if(height == 0)
return;

// Top half
float part_height = tri[1].y() - tri[0].y();
if(part_height == 0)
goto bottom;
for(float y = tri[0].y(); y < tri[1].y(); y++) {
float part_pct = (y - tri[0].y()) / part_height;
float whole_pct = (y - tri[0].y()) / height;
Vec3f a = { tri[0].x() + (tri[1].x() - tri[0].x()) * part_pct, y, tri[0].z() + (tri[1].z() - tri[0].z()) * part_pct };
Vec3f b = { tri[0].x() + (tri[2].x() - tri[0].x()) * whole_pct, y, tri[0].z() + (tri[2].z() - tri[0].z()) * whole_pct };
line(a, b, color);
auto pt = project(verts[i].pos);
tri[i] = {pt.x(), pt.y(), pt.z()};
}

// Bottom half
bottom:
part_height = tri[2].y() - tri[1].y();
if(part_height == 0)
// Backface cull and lighting calculation
Vec3f norm = ((tri[2]-tri[0])^(tri[1]-tri[0])).normalize();
if (norm.z() < 0)
return;
for(float y = tri[1].y(); y <= tri[2].y(); y++) {
float part_pct = (y - tri[1].y()) / part_height;
float whole_pct = (y - tri[0].y()) / height;
Vec3f a = { tri[1].x() + (tri[2].x() - tri[1].x()) * part_pct, y, tri[1].z() + (tri[2].z() - tri[1].z()) * part_pct };
Vec3f b = { tri[0].x() + (tri[2].x() - tri[0].x()) * whole_pct, y, tri[0].z() + (tri[2].z() - tri[0].z()) * whole_pct };
line(a, b, color);
}
}
const float light = norm * Vec3f(0, 0, 1);

void RenderContext::tri_barycentric(std::array<Vertex, 3> verts) {
// First, transform into screenspace coordinates
// Then, transform into screenspace coordinates and calculate bounding box
std::array<Vec3f, 3> sstri;
for(int i = 0; i < 3; i++) {
sstri[i] = screenspace((m_projmat * m_modelmat * verts[i].pos).transpose()[0]);
}

Vec2i bbox_max = {0, 0};
Vec2i bbox_min = {m_viewport.width - 1, m_viewport.height - 1};
for(auto vert : sstri) {
bbox_min.x() = std::min(bbox_min.x(), f2i(vert.x()));
bbox_min.y() = std::min(bbox_min.y(), f2i(vert.y()));
bbox_max.x() = std::max(bbox_max.x(), f2i(vert.x()));
bbox_max.y() = std::max(bbox_max.y(), f2i(vert.y()));
for(int i = 0; i < 3; i++) {
sstri[i] = screenspace(tri[i]);
bbox_min.x() = std::min(bbox_min.x(), f2i(sstri[i].x()));
bbox_min.y() = std::min(bbox_min.y(), f2i(sstri[i].y()));
bbox_max.x() = std::max(bbox_max.x(), f2i(sstri[i].x()));
bbox_max.y() = std::max(bbox_max.y(), f2i(sstri[i].y()));
}
bbox_min.x() = std::max(bbox_min.x(), 0);
bbox_min.y() = std::max(bbox_min.y(), 0);
bbox_max.x() = std::min(bbox_max.x(), m_viewport.width - 1);
bbox_max.y() = std::min(bbox_max.y(), m_viewport.height - 1);

// Whether the tri takes up just one pixel
const bool pixel = bbox_max.x() == bbox_min.x() && bbox_max.y() == bbox_min.y();

const Vec3f a {sstri[2].x() - sstri[0].x(), sstri[1].x() - sstri[0].x(), sstri[0].x()};
const Vec3f b {sstri[2].y() - sstri[0].y(), sstri[1].y() - sstri[0].y(), sstri[0].y()};
const float abz = a.x() * b.y() - a.y() * b.x(); // z component of a^b

// If the triangle isn't facing the screen, don't bother
if(!pixel && std::abs(abz) < 1)
return;
// Calculate the barycentric coordinates of the top-left of our bounding box
const Vec3f a {sstri[2].x() - sstri[0].x(), sstri[1].x() - sstri[0].x(), sstri[0].x() - (float) bbox_min.x()};
const Vec3f b {sstri[2].y() - sstri[0].y(), sstri[1].y() - sstri[0].y(), sstri[0].y() - (float) bbox_min.y()};
const Vec3f u = a^b;
Vec3f bary = {
1.0f - (u.x() + u.y()) / u.z(),
u.y() / u.z(),
u.x() / u.z()
};

// Calculate x and y steps for barycentric coordinates
const Vec3f barystep_x = {
-(b.y() + -b.x()) / u.z(),
-b.x() / u.z(),
b.y() / u.z()
};
const Vec3f barystep_y = {
-(a.x() - a.y()) / u.z(),
a.x() / u.z(),
-a.y() / u.z()
};

// Calculate x and y step for tex coords, color, and depth
Vec2f tex;
Vec2f texstep_x;
Vec2f texstep_y;

Vec4f color;
Vec4f colorstep_x;
Vec4f colorstep_y;

float z = 0;
float zstep_x = 0;
float zstep_y = 0;

for (int i = 0; i < 3; i++) {
tex += verts[i].tex * bary[i];
texstep_x += verts[i].tex * barystep_x[i];
texstep_y += verts[i].tex * barystep_y[i];

color += {
verts[i].color[0] * bary[i] * light,
verts[i].color[1] * bary[i] * light,
verts[i].color[2] * bary[i] * light,
verts[i].color[3] * bary[i]
};
colorstep_x += verts[i].color * light * barystep_x[i];
colorstep_y += verts[i].color * light * barystep_y[i];

z += sstri[i].z() * bary[i];
zstep_x += sstri[i].z() * barystep_x[i];
zstep_y += sstri[i].z() * barystep_y[i];
}

for(int y = bbox_min.y(); y <= bbox_max.y(); y++) {
const float bz = b.z() - y;
const float ax_x_bz = a.x() * bz;
const float ay_x_bz = a.y() * bz;
for(int x = bbox_min.x(); x <= bbox_max.x(); x++) {
// a^b, but without unnecessarily recalculating the z component
const float az = a.z() - x;
const Vec3f u = {
ay_x_bz - az * b.y(),
az * b.x() - ax_x_bz,
abz
};

// Calculate barycentric coordinates in triangle
const Vec3f bary = {1.0f - (u.x() + u.y()) / u.z(), u.y() / u.z(), u.x() / u.z()};
if (!pixel && (bary.x() < 0 || bary.y() < 0 || bary.z() < 0))
continue;
// Save our barycentric coords, texcoords, and color at the start of the line
const auto obary = bary;
const auto otex = tex;
const auto ocolor = color;
const auto oz = z;
bool was_inside = false;

float z = 0;
Vec4f color;
Vec2f tex;
for (int i = 0; i < 3; i++) {
auto baryi = bary[i];
z += sstri[i].z() * baryi;
color += {
verts[i].color[0] * baryi,
verts[i].color[1] * baryi,
verts[i].color[2] * baryi,
verts[i].color[3] * baryi
};
tex += {
verts[i].tex[0] * baryi,
verts[i].tex[1] * baryi
};
for(int x = bbox_min.x(); x <= bbox_max.x(); x++) {
if (!pixel && (bary.x() < 0 || bary.y() < 0 || bary.z() < 0)) {
// If we were previously inside the triangle on this line, we're done and can skip to the next line
if (was_inside)
break;
goto done;
}
was_inside = true;

if (m_buffers.depth.at(x, y) >= z)
continue;

if (m_bound_texture) {
Vec4f texcol = m_bound_texture->buffer().get(tex.x() * m_bound_texture->buffer().width(), tex.y() * m_bound_texture->buffer().height());
color = {
const Vec4f texcol = m_bound_texture->buffer().get(tex.x() * m_bound_texture->buffer().width(), tex.y() * m_bound_texture->buffer().height());
m_buffers.color.at(x, y) = {
color[0] * texcol[0],
color[1] * texcol[1],
color[2] * texcol[2],
color[3] * texcol[3]
};
}

if (m_buffers.depth.at(x, y) < z) {
} else {
m_buffers.color.at(x, y) = color;
m_buffers.depth.at(x, y) = z;
}

m_buffers.depth.at(x, y) = z;

done:
tex += texstep_x;
bary += barystep_x;
color += colorstep_x;
z += zstep_x;
}

// Step bary, tex, color by y step
bary = obary + barystep_y;
tex = otex + texstep_y;
color = ocolor + colorstep_y;
z = oz + zstep_y;
}
}

void RenderContext::tri_wireframe(std::array<Vertex, 3> verts) {
// Transform into world coords
for(int i = 0; i < 3; i++) {
auto pos = screenspace(project(verts[i].pos));
verts[i].pos = {pos.x(), pos.y(), pos.z(), verts[i].pos.w()};
}

line(verts[0], verts[1]);
line(verts[1], verts[2]);
line(verts[2], verts[0]);
}
18 changes: 11 additions & 7 deletions libraries/lib3d/RenderContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,31 @@ namespace Lib3D {

/// Projection and Matrices
Vec4f constexpr project(Vec4f point) {
return (m_projmat * m_modelmat * point).transpose()[0];
return (m_premultmat * point).col(0);
}

Vec3f constexpr screenspace(Vec4f point) {
template<typename T, size_t N>
Vec3f constexpr screenspace(Vec<T, N> point) {
return {(point.x() + 1.0f) * 0.5f * m_viewport.width,
(-point.y() + 1.0f) * 0.5f * m_viewport.height,
point.z()};
}

void set_modelmat(Matrix4f modelmat) {
m_modelmat = modelmat;
m_premultmat = m_projmat * m_modelmat;
}

void set_projmat(Matrix4f projmat) {
m_projmat = projmat;
m_premultmat = m_projmat * m_modelmat;
}

/// Various Stuff
void clear(Vec4f color);

/// Drawing
void line(Vec3f a, Vec3f b, Vec4f color);
void line(Vertex a, Vertex b);
void tri(std::array<Vertex, 3> verts);

/// Textures
Expand All @@ -54,13 +57,14 @@ namespace Lib3D {
private:
explicit RenderContext(Gfx::Dimensions dimensions);

void tri_simple(std::array<Vertex, 3> verts);
void tri_barycentric(std::array<Vertex, 3> verts);
void tri_wireframe(std::array<Vertex, 3> verts);

Matrix4f m_modelmat = identity<float, 4>();
Matrix4f m_projmat = ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
Matrix4f m_modelmat = identity<float, 4>();
Matrix4f m_projmat = ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
Matrix4f m_premultmat = m_projmat * m_modelmat;
Gfx::Rect m_viewport;
BufferSet m_buffers;
Texture* m_bound_texture;
Texture* m_bound_texture = nullptr;
};
}
Loading

0 comments on commit 6d682cd

Please sign in to comment.