From 57c6cf991b498aa19cc1cb3d736fec08d3643490 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Tue, 30 Dec 2025 17:24:42 -0800 Subject: Depth buffering and texture coords --- src/swgfx.c | 202 ++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 74 deletions(-) (limited to 'src/swgfx.c') diff --git a/src/swgfx.c b/src/swgfx.c index 54f7432..c298fb4 100644 --- a/src/swgfx.c +++ b/src/swgfx.c @@ -22,7 +22,6 @@ Coordinate systems: static constexpr sgVec3 Up3 = (sgVec3){0,1,0}; typedef struct sgViewport_t { int x0, y0, width, height; } sgViewport_t; -typedef struct sgTri2 { sgVec2 p0, p1, p2; } sgTri2; typedef struct sgAABB2 { sgVec2 pmin, pmax; } sgAABB2; // Column-major math, column-major storage. @@ -33,6 +32,7 @@ typedef struct sgMat4 { typedef struct swgfx { sgVec2i dims; // Colour buffer dimensions. sgPixel* colour; // Colour buffer. + R* depth; // Depth buffer. sgViewport_t viewport; sgMat4 model; // Model matrix. sgMat4 view; // View matrix. @@ -48,15 +48,25 @@ typedef struct swgfx { sgMat4 mvp; // Model-view-projection matrix. } swgfx; -static inline sgVec3 neg3(sgVec3 v) { return (sgVec3){-v.x, -v.y, -v.z}; } +static inline R rmin(R a, R b) { return (a <= b) ? a : b; } +static inline R rmax(R a, R b) { return (a >= b) ? a : b; } +static inline int imin(int a, int b) { return (a <= b) ? a : b; } +static inline int imax(int a, int b) { return (a >= b) ? a : b; } -static inline sgVec3 sub3(sgVec3 a, sgVec3 b) { - return (sgVec3){a.x - b.x, a.y - b.y, a.z - b.z}; -} +static inline sgVec2 min2(sgVec2 a, sgVec2 b) { return (sgVec2){.x = rmin(a.x, b.x), .y = rmin(a.y, b.y) }; } +static inline sgVec2 max2(sgVec2 a, sgVec2 b) { return (sgVec2){.x = rmax(a.x, b.x), .y = rmax(a.y, b.y) }; } +static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; } +static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; } -static inline R dot3(sgVec3 a, sgVec3 b) { - return a.x * b.x + a.y * b.y + a.z * b.z; -} +static inline sgVec2 add2(sgVec2 a, sgVec2 b) { return (sgVec2){a.x + b.x, a.y + b.y}; } +static inline sgVec2 scale2(sgVec2 v, R s) { return (sgVec2){v.x * s, v.y * s}; } + +static inline sgVec3 neg3(sgVec3 v) { return (sgVec3){-v.x, -v.y, -v.z}; } +static inline sgVec3 sub3(sgVec3 a, sgVec3 b) { return (sgVec3){a.x - b.x, a.y - b.y, a.z - b.z}; } +static inline sgVec3 div3(sgVec3 a, sgVec3 b) { return (sgVec3){a.x / b.x, a.y / b.y, a.z / b.z}; } +static inline R dot3(sgVec3 a, sgVec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } +static inline R normsq3(sgVec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } +static inline R norm3 (sgVec3 v) { return (R)sqrt(normsq3(v)); } static inline sgVec3 cross3(sgVec3 a, sgVec3 b) { return (sgVec3) { @@ -65,18 +75,12 @@ static inline sgVec3 cross3(sgVec3 a, sgVec3 b) { a.x * b.y - a.y * b.x}; } -static inline R normsq3(sgVec3 v) { return v.x * v.x + v.y * v.y + v.z * v.z; } -static inline R norm3 (sgVec3 v) { return (R)sqrt(normsq3(v)); } - static inline sgVec3 normalize3(sgVec3 v) { const R n = norm3(v); - assert(n > 0); - return (sgVec3){v.x / n, v.y / n, v.z / n}; + return (n > 0) ? (sgVec3){v.x / n, v.y / n, v.z / n} : (sgVec3){0, 0, 0}; } -static inline sgVec4 Vec4FromVec3(sgVec3 v, R w) { - return (sgVec4){v.x, v.y, v.z, w}; -} +static inline sgVec4 Vec4FromVec3(sgVec3 v, R w) { return (sgVec4){v.x, v.y, v.z, w}; } static inline sgMat4 Mat4( R m00, R m01, R m02, R m03, // v0.x v1.x v2.x v3.x @@ -231,37 +235,39 @@ static inline sgMat4 Mat4Perspective(R fovy, R aspect, R near, R far) { } #ifndef _NDEBUG -static bool InBounds(int width, int height, sgVec2i p) { - return (0 <= p.x) && (p.x < width) && - (0 <= p.y) && (p.y < height); +static bool InBounds(int width, int height, int x, int y) { + return (0 <= x) && (x < width) && + (0 <= y) && (y < height); } #endif // _NDEBUG -static inline sgPixel* Pixel(sgPixel* image, int width, int height, int x, int y) { - assert(InBounds(width, height, (sgVec2i){x,y})); - return image + (y * width) + x; +static inline sgPixel* Pixel(swgfx* gfx, int x, int y) { + assert(gfx); + assert(gfx->colour); + assert(InBounds(gfx->dims.x, gfx->dims.y, x, y)); + return gfx->colour + (y * gfx->dims.x) + x; } -static inline R rmin(R a, R b) { return (a <= b) ? a : b; } -static inline R rmax(R a, R b) { return (a >= b) ? a : b; } -static inline int imin(int a, int b) { return (a <= b) ? a : b; } -static inline int imax(int a, int b) { return (a >= b) ? a : b; } -static inline sgVec2 min2(sgVec2 a, sgVec2 b) { - return (sgVec2){.x = rmin(a.x, b.x), .y = rmin(a.y, b.y) }; -} -static inline sgVec2 max2(sgVec2 a, sgVec2 b) { - return (sgVec2){.x = rmax(a.x, b.x), .y = rmax(a.y, b.y) }; +static inline R* Depth(swgfx* gfx, int x, int y) { + assert(gfx); + assert(gfx->depth); + assert(InBounds(gfx->dims.x, gfx->dims.y, x, y)); + return gfx->depth + (y * gfx->dims.x) + x; } -static inline sgVec2i min2i(sgVec2i a, sgVec2i b) { - return (sgVec2i){.x = imin(a.x, b.x), .y = imin(a.y, b.y) }; + +void SetPixel(swgfx* gfx, const sgVec2i p, sgPixel colour) { + assert(gfx); + *Pixel(gfx, p.x, p.y) = colour; } -static inline sgVec2i max2i(sgVec2i a, sgVec2i b) { - return (sgVec2i){.x = imax(a.x, b.x), .y = imax(a.y, b.y) }; + +void SetDepth(swgfx* gfx, const sgVec2i p, R depth) { + assert(gfx); + *Depth(gfx, p.x, p.y) = depth; } -static inline sgAABB2 TriangleAabb2(const sgTri2 tri) { - return (sgAABB2){.pmin = min2(min2(tri.p0, tri.p1), tri.p2), - .pmax = max2(max2(tri.p0, tri.p1), tri.p2)}; +static inline sgAABB2 TriangleAabb2(sgVec2 p0, sgVec2 p1, sgVec2 p2) { + return (sgAABB2){.pmin = min2(min2(p0, p1), p2), + .pmax = max2(max2(p0, p1), p2)}; } static inline sgVec2i Clip(const swgfx* gfx, const sgVec2i p) { @@ -272,30 +278,45 @@ static inline sgVec2i Clip(const swgfx* gfx, const sgVec2i p) { return max2i(lower, min2i(upper, p)); } +static inline R BarycentricInterp1(sgVec3 bar, R a, R b, R c) { + return bar.x*a + bar.y*b + bar.z*c; +} +static inline sgVec2 BarycentricInterp2(sgVec3 bar, sgVec2 a, sgVec2 b, sgVec2 c) { + return add2(add2(scale2(a, bar.x), scale2(b, bar.y)), scale2(c, bar.z)); +} +static inline sgVec2 PerspectiveInterp2(sgVec3 bar, sgVec3 depths, R z, sgVec2 a, sgVec2 b, sgVec2 c) { + return scale2(BarycentricInterp2(div3(bar, depths), a, b, c), z); +} + static inline R f(sgVec2 a, sgVec2 b, sgVec2 p) { return (a.y - b.y)*p.x + (b.x - a.x)*p.y + a.x*b.y - b.x*a.y; } -static inline sgVec3 Barycentric(const sgTri2 tri, sgVec2 p) { +static inline sgVec3 Barycentric(sgVec2 p0, sgVec2 p1, sgVec2 p2, sgVec2 p) { // There is no need to compute the third coordinate explicitly: a + b + c = 1. // But this results in a worse rasterization of the triangle along one of the edges. // It seems we can patch it with a small epsilon, though. // --- // Division by zero is only possible if the triangle has zero area. /*return (sgVec3){ - f(tri.p1, tri.p2, p) / f(tri.p1, tri.p2, tri.p0), - f(tri.p2, tri.p0, p) / f(tri.p2, tri.p0, tri.p1), - f(tri.p0, tri.p1, p) / f(tri.p0, tri.p1, tri.p2)};*/ - const R b = f(tri.p0, tri.p2, p) / f(tri.p0, tri.p2, tri.p1); - const R c = f(tri.p0, tri.p1, p) / f(tri.p0, tri.p1, tri.p2); - const R a = /*f(tri.p1, tri.p2, p) / f(tri.p1, tri.p2, tri.p0);*/1.f - b - c - (R)1e-7; + f(p1, p2, p) / f(p1, p2, p0), + f(p2, p0, p) / f(p2, p0, p1), + f(p0, p1, p) / f(p0, p1, p2)};*/ + const R b = f(p0, p2, p) / f(p0, p2, p1); + const R c = f(p0, p1, p) / f(p0, p1, p2); + const R a = /*f(p1, p2, p) / f(p1, p2, p0);*/1.f - b - c - (R)1e-7; return (sgVec3){a,b,c}; } -static void DrawTriangle2(swgfx* gfx, const sgTri2* tri) { +// Input triangle in screen space. Retains Z for depth testing and vertex +// attribute interpolation. +static void DrawTriangle2(swgfx* gfx, const sgTri3* const tri) { assert(gfx); assert(tri); - const sgAABB2 bbox = TriangleAabb2(*tri); + const sgVec2 p0 = (sgVec2){tri->p0.pos.x, tri->p0.pos.y}; + const sgVec2 p1 = (sgVec2){tri->p1.pos.x, tri->p1.pos.y}; + const sgVec2 p2 = (sgVec2){tri->p2.pos.x, tri->p2.pos.y}; + const sgAABB2 bbox = TriangleAabb2(p0, p1, p2); // We consider (x,y) to be the pixel center. // Draw all pixels touched by the bounding box. TODO: Multi-sampling. sgVec2i pmin = (sgVec2i){(int)bbox.pmin.x, (int)bbox.pmin.y}; @@ -309,14 +330,23 @@ static void DrawTriangle2(swgfx* gfx, const sgTri2* tri) { const sgVec2 p = (sgVec2){(R)x, (R)y}; // TODO: there is an incremental optimization to computing barycentric coordinates; // read more about it. - const sgVec3 bar = Barycentric(*tri, p); + const sgVec3 bar = Barycentric(p0, p1, p2, p); // We need to check the third coordinate. // a + b + c = 1 // So, e.g., if a >= 0 and b >= 0, then we have c <= 1, but we could also have c <= 0. // In the case c <= 0, then point is outside the triangle. if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) { - const sgVec2i pi = (sgVec2i){(int)x, (int)y}; - sgPixels(gfx, 1, &pi, (sgPixel){255, 255, 255, 255}); + const R z = BarycentricInterp1(bar, tri->p0.pos.z, tri->p1.pos.z, tri->p2.pos.z); + R* depth = Depth(gfx, x, y); + if ((0.f <= z) && (z <= 1.f) && (z <= *depth)) { + *depth = z; + const sgVec3 depths = (sgVec3){tri->p0.pos.z, tri->p1.pos.z, tri->p2.pos.z}; + const sgVec2 uv = PerspectiveInterp2(bar, depths, z, tri->p0.uv, tri->p1.uv, tri->p2.uv); + const int r = (int)(uv.x * 255.f); + const int g = (int)(uv.y * 255.f); + const sgVec2i pix = (sgVec2i){(int)x, (int)y}; + SetPixel(gfx, pix, (sgPixel){r, g, 255, 255}); + } } } } @@ -327,17 +357,18 @@ static inline sgVec3 PerspDivide(sgVec4 v) { } // TODO: Compute a viewport matrix in sgViewport() instead. -static inline sgVec2 ViewportTransform(sgViewport_t vp, sgVec3 ndc) { - return (sgVec2){ +static inline sgVec3 ViewportTransform(sgViewport_t vp, sgVec3 ndc) { + return (sgVec3){ .x = (ndc.x+1.f) * ((R)vp.width/2.f) + (R)vp.x0, - .y = (ndc.y+1.f) * ((R)vp.height/2.f) + (R)vp.y0}; + .y = (ndc.y+1.f) * ((R)vp.height/2.f) + (R)vp.y0, + .z = ndc.z}; } -static inline sgVec2 ViewportToWindow(sgViewport_t vp, sgVec2 p) { - return (sgVec2){p.x, (R)vp.height - p.y}; +static inline sgVec3 ViewportToWindow(sgViewport_t vp, sgVec3 p) { + return (sgVec3){p.x, (R)vp.height - p.y, p.z}; } -static inline sgVec2 TransformPosition(const swgfx* gfx, sgVec3 p) { +static inline sgVec3 TransformPosition(const swgfx* gfx, sgVec3 p) { assert(gfx); // Model to clip space. const sgVec4 p_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(p, 1)); @@ -345,18 +376,24 @@ static inline sgVec2 TransformPosition(const swgfx* gfx, sgVec3 p) { // Perspective divide. const sgVec3 p_ndc = PerspDivide(p_clip); // TODO: Clip. - const sgVec2 p_vp = ViewportTransform(gfx->viewport, p_ndc); + const sgVec3 p_vp = ViewportTransform(gfx->viewport, p_ndc); return ViewportToWindow(gfx->viewport, p_vp); } -static void DrawTriangle3(swgfx* gfx, const sgTri3* tri) { +static void DrawTriangle3(swgfx* gfx, const sgTri3* const tri) { assert(gfx); assert(tri); - const sgVec2 p0 = TransformPosition(gfx, tri->p0); - const sgVec2 p1 = TransformPosition(gfx, tri->p1); - const sgVec2 p2 = TransformPosition(gfx, tri->p2); - const sgTri2 tri2 = (sgTri2){p0, p1, p2}; - DrawTriangle2(gfx, &tri2); + const sgVec3 p0 = TransformPosition(gfx, tri->p0.pos); + const sgVec3 p1 = TransformPosition(gfx, tri->p1.pos); + const sgVec3 p2 = TransformPosition(gfx, tri->p2.pos); + const sgVec2 uv0 = tri->p0.uv; + const sgVec2 uv1 = tri->p1.uv; + const sgVec2 uv2 = tri->p2.uv; + const sgTri3 tri_screen = (sgTri3){ + (sgVert3){p0, uv0}, + (sgVert3){p1, uv1}, + (sgVert3){p2, uv2}}; + DrawTriangle2(gfx, &tri_screen); } #define is_pow2_or_0(X) ((X & (X - 1)) == 0) @@ -389,6 +426,7 @@ static void* Alloc(void** ppMem, size_t count, size_t size) { size_t sgMem(int width, int height) { return Align(sizeof(swgfx)) + Align(width * height * sizeof(sgPixel)) + + Align(width * height * sizeof(R)) + (SG_ALIGN - 1); // To make room to align allocations within the buffer. } @@ -397,6 +435,7 @@ swgfx* sgNew(int width, int height, void* mem) { swgfx* gfx = SG_ALLOC(&aligned, 1, swgfx); gfx->dims = (sgVec2i){width, height}; gfx->colour = SG_ALLOC(&aligned, width * height, sgPixel); + gfx->depth = SG_ALLOC(&aligned, width * height, R); return gfx; } @@ -481,15 +520,17 @@ void sgViewport(swgfx* gfx, int x0, int y0, int width, int height) { void sgClear(swgfx* gfx) { assert(gfx); - memset(gfx->colour, 0, gfx->dims.x * gfx->dims.y * sizeof(sgPixel)); + const int N = gfx->dims.x * gfx->dims.y; + memset(gfx->colour, 0, N * sizeof(*gfx->colour)); + for (int i = 0; i < N; ++i) { + gfx->depth[i] = 1.0f; + } } void sgPixels(swgfx* gfx, size_t count, const sgVec2i* positions, sgPixel colour) { assert(gfx); -#define XY(X,Y) Pixel(gfx->colour, gfx->dims.x, gfx->dims.y, X, Y) for (size_t i = 0; i < count; ++i) { - const sgVec2i p = positions[i]; - *XY(p.x, p.y) = colour; + SetPixel(gfx, positions[i], colour); } } @@ -507,7 +548,12 @@ void sgPixels(swgfx* gfx, size_t count, const sgVec2i* positions, sgPixel colour void sgTriangles2(swgfx* gfx, size_t count, const sgTri2* tris) { assert(gfx); for (size_t i = 0; i < count; ++i) { - DrawTriangle2(gfx, &tris[i]); + const sgTri3 tri3 = (sgTri3) { + .p0 = (sgVert3){.pos = (sgVec3){tris[i].p0.pos.x, tris[i].p0.pos.y, 0}, .uv = tris[i].p0.uv}, + .p1 = (sgVert3){.pos = (sgVec3){tris[i].p1.pos.x, tris[i].p1.pos.y, 0}, .uv = tris[i].p1.uv}, + .p2 = (sgVert3){.pos = (sgVec3){tris[i].p2.pos.x, tris[i].p2.pos.y, 0}, .uv = tris[i].p2.uv}, + }; + DrawTriangle2(gfx, &tri3); } } @@ -520,10 +566,11 @@ void sgTriangles(swgfx* gfx, size_t count, const sgTri3* tris, const sgNormal*) } } -void sgTrianglesIndexed(swgfx* gfx, size_t numIndices, const sgIdx* indices, const sgVec3* positions) { +void sgTrianglesIndexed(swgfx* gfx, size_t numIndices, const sgIdx* indices, const sgVec3* positions, const sgVec2* texcoords) { assert(gfx); assert(indices); assert(positions); + assert(texcoords); for (size_t i = 0; i < numIndices; i+=3) { const sgIdx i0 = indices[i]; const sgIdx i1 = indices[i+1]; @@ -531,21 +578,28 @@ void sgTrianglesIndexed(swgfx* gfx, size_t numIndices, const sgIdx* indices, con const sgVec3 p0 = positions[i0]; const sgVec3 p1 = positions[i1]; const sgVec3 p2 = positions[i2]; - const sgTri3 tri = (sgTri3){p0, p1, p2}; + const sgVec2 uv0 = texcoords[i0]; + const sgVec2 uv1 = texcoords[i1]; + const sgVec2 uv2 = texcoords[i2]; + const sgTri3 tri = (sgTri3){ + (sgVert3){p0, uv0}, + (sgVert3){p1, uv1}, + (sgVert3){p2, uv2}}; DrawTriangle3(gfx, &tri); } } -void sgTrianglesIndexedNonUniform(swgfx* gfx, size_t numTris, const sgTriIdx* tris, const sgVec3* positions) { +void sgTrianglesIndexedNonUniform(swgfx* gfx, size_t numTris, const sgTriIdx* tris, const sgVec3* positions, const sgVec2* texcoords) { assert(gfx); assert(tris); assert(positions); + assert(texcoords); for (size_t t = 0; t < numTris; ++t) { const sgTriIdx* triIdx = &tris[t]; const sgTri3 tri = (sgTri3){ - positions[triIdx->v0.position], - positions[triIdx->v1.position], - positions[triIdx->v2.position]}; + (sgVert3){positions[triIdx->v0.pos], texcoords[triIdx->v0.uv]}, + (sgVert3){positions[triIdx->v1.pos], texcoords[triIdx->v1.uv]}, + (sgVert3){positions[triIdx->v2.pos], texcoords[triIdx->v2.uv]}}; DrawTriangle3(gfx, &tri); } } -- cgit v1.2.3