From b0544549c551dfa0b52e7c685580f954861240ba Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 13 Feb 2026 14:21:37 -0800 Subject: Emit normals --- include/swgfx.h | 12 ++++--- src/swgfx.c | 100 ++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/include/swgfx.h b/include/swgfx.h index b3f9150..452847c 100644 --- a/include/swgfx.h +++ b/include/swgfx.h @@ -37,8 +37,8 @@ typedef sgVec3 sgNormal; typedef struct sgVert2i { sgVec2i pos; sgVec2 uv; } sgVert2i; typedef struct sgVert2 { sgVec2 pos; sgVec2 uv; } sgVert2; -typedef struct sgVert3 { sgVec3 pos; sgVec2 uv; } sgVert3; -typedef struct sgVert4 { sgVec4 pos; sgVec2 uv; } sgVert4; +typedef struct sgVert3 { sgVec3 pos; sgVec2 uv; sgVec3 normal; } sgVert3; +typedef struct sgVert4 { sgVec4 pos; sgVec2 uv; sgVec3 normal; } sgVert4; typedef struct sgQuadi { sgVert2i p0, p1; } sgQuadi; typedef struct sgQuad { sgVert2 p0, p1; } sgQuad; @@ -108,11 +108,15 @@ void sgTriangles2 (swgfx*, size_t count, const sgTri2*); void sgTriangleStrip2(swgfx*, size_t count, const sgVec2*); void sgTriangles (swgfx*, size_t count, const sgTri3*, const sgNormal*); void sgTriangleStrip (swgfx*, size_t count, const sgVec3*, const sgNormal*); -void sgTrianglesIndexed(swgfx*, size_t numIndices, const sgIdx* indices, const sgVec3* positions, const sgVec2* texcoords); -void sgTrianglesIndexedNonUniform(swgfx*, size_t numTris, const sgTriIdx* tris, const sgVec3* positions, const sgVec2* texcoords); +void sgTrianglesIndexed(swgfx*, size_t numIndices, const sgIdx* indices, const sgVec3* positions, const sgVec2* texcoords, const sgVec3* normals); +void sgTrianglesIndexedNonUniform(swgfx*, size_t numTris, const sgTriIdx* tris, const sgVec3* positions, const sgVec2* texcoords, const sgVec3* normals); void sgLighting(swgfx*); void sgAmbient(swgfx*, sgVec3 ambient); +// TODO: Implement directional lights. + +void sgDepth(swgfx*); +void sgNormals(swgfx*); void sgGamma (swgfx*, sgPixel*, int width, int height); void sgGammaInv(swgfx*, sgPixel*, int width, int height); diff --git a/src/swgfx.c b/src/swgfx.c index ddd64f7..6074eac 100644 --- a/src/swgfx.c +++ b/src/swgfx.c @@ -24,7 +24,8 @@ static constexpr size_t SWGFX_TEXTURE_REGISTER_SIZE = SWGFX_MAX_TEXTURES + 1; static constexpr R DepthClearValue = 1.0f; -static constexpr sgVec3 Up3 = (sgVec3){0,1,0}; +static constexpr sgVec3 Up3 = (sgVec3){0,1,0}; +static constexpr sgVec3 Zero3 = (sgVec3){0,0,0}; typedef struct sgViewport_t { int x0, y0, width, height; } sgViewport_t; typedef struct sgAABB2 { sgVec2 pmin, pmax; } sgAABB2; @@ -46,6 +47,7 @@ typedef struct swgfx { sgTextureId* texture; // Texture ID buffer. sgVec2* texcoords; // Texture coords buffer. sgPixel* albedo; // Albedo buffer. + sgVec3* normals; // Normals buffer. sgViewport_t viewport; sgMat4 model; // Model matrix. sgMat4 view; // View matrix. @@ -87,12 +89,14 @@ static inline sgVec2 frac2(sgVec2 v) { return (sgVec2){frac(v.x), frac(v.y)}; } static inline sgVec2 lerp2(sgVec2 a, sgVec2 b, R t) { return add2(a, scale2(sub2(b,a), t)); } static inline sgVec2 mod2(sgVec2 v, R m) { return (sgVec2){mod1(v.x, m), mod1(v.y, m)}; } +static inline sgVec3 max3(sgVec3 a, sgVec3 b) { return (sgVec3){fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z)}; } static inline sgVec3 add3(sgVec3 a, sgVec3 b) { return (sgVec3){a.x + b.x, a.y + b.y, a.z + b.z}; } 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 mul3(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 sgVec3 scale3(sgVec3 v, R s) { return (sgVec3){v.x * s, v.y * s, v.z * s}; } +static inline sgVec3 lerp3(sgVec3 a, sgVec3 b, R t) { return add3(a, scale3(sub3(b,a), t)); } static inline sgVec3 exp3(sgVec3 v, R exp) { return (sgVec3){powf(v.x, exp), powf(v.y, exp), powf(v.z, exp)};} 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; } @@ -326,11 +330,13 @@ static inline void SetPixelColour(swgfx* gfx, const sgVec2i p, sgPixel colour) { #endif // SWGFX_PROFILING } -static inline void SetPixelDeferred(swgfx* gfx, const sgVec2i p, R depth, sgTextureId texid, sgVec2 uv) { +static inline void SetPixelDeferred(swgfx* gfx, const sgVec2i p, R depth, sgTextureId texid, sgVec2 uv, sgVec3 normal) { assert(gfx); - gfx->depth[(p.y * gfx->dims.x) + p.x] = depth; - gfx->texture[(p.y * gfx->dims.x) + p.x] = texid; - gfx->texcoords[(p.y * gfx->dims.x) + p.x] = uv; + const int i = (p.y * gfx->dims.x) + p.x; + gfx->depth[i] = depth; + gfx->texture[i] = texid; + gfx->texcoords[i] = uv; + gfx->normals[i] = normalize3(normal); // Non-unit after interpolation. #if SWGFX_PROFILING gfx->counters.pixels++; #endif // SWGFX_PROFILING @@ -514,7 +520,7 @@ static inline R IntersectSegmentPlane(R near, const sgVec3* const a, const sgVec return t; } -/// Interpolate depth and vertex attributes at the in/out vertex 'out'. +/// Interpolate vertex attributes at the in/out vertex 'out'. static void InterpolateAttributes(const sgVert4* const a, const sgVert4* const b, R t, sgVert4* out) { assert(a); assert(b); @@ -522,8 +528,9 @@ static void InterpolateAttributes(const sgVert4* const a, const sgVert4* const b assert(t >= 0.f); assert(t <= 1.f); const sgVec4 d = sub4(b->pos, a->pos); // Line direction. - out->pos = add4(a->pos, scale4(d, t)); - out->uv = lerp2(a->uv, b->uv, t); + out->pos = add4(a->pos, scale4(d, t)); + out->uv = lerp2(a->uv, b->uv, t); + out->normal = lerp3(a->normal, b->normal, t); } /// Clip a triangle, vertices in clip space. Return the number of output @@ -597,10 +604,18 @@ static inline int TransformTri(const swgfx* gfx, const sgTri3* const tri, sgTri4 const sgVec4 p0_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(tri->p0.pos, 1)); const sgVec4 p1_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(tri->p1.pos, 1)); const sgVec4 p2_clip = Mat4MulVec4(gfx->mvp, Vec4FromVec3(tri->p2.pos, 1)); + // Model to world space for normals. + // This assumes the model matrix does not have non-uniform scaling. + // It seems more convenient to put the normals in world space instead of view + // space right now so that we don't have to transform lights to view space + // when lighting. But we might want to revisit this later. + const sgVec3 n0_view = Mat4MulVec3(gfx->model, tri->p0.normal, 0.f); + const sgVec3 n1_view = Mat4MulVec3(gfx->model, tri->p1.normal, 0.f); + const sgVec3 n2_view = Mat4MulVec3(gfx->model, tri->p2.normal, 0.f); const sgTri4 tri_clip = { - (sgVert4){ p0_clip, tri->p0.uv }, - (sgVert4){ p1_clip, tri->p1.uv }, - (sgVert4){ p2_clip, tri->p2.uv }}; + (sgVert4){ p0_clip, tri->p0.uv, n0_view }, + (sgVert4){ p1_clip, tri->p1.uv, n1_view }, + (sgVert4){ p2_clip, tri->p2.uv, n2_view }}; // Clip. // Our perspective matrix maps the near plane to z=-1 in clip space. constexpr R near_clip = -1.f; @@ -673,20 +688,22 @@ static void DrawTriangle3PostClip(swgfx* gfx, const sgTri4* const tri) { if ((bar.x >= 0) && (bar.y >= 0) && (bar.z >= 0)) { assert((bar.x + bar.y + bar.z - 1e7) <= 1.f); const R p_one_over_z = dot3(bar, one_over_zs); - const R p_u_over_z = dot3(bar, u_over_zs); - const R p_v_over_z = dot3(bar, v_over_zs); const R p_depth = dot3(bar, depths); const R z = 1.f / p_one_over_z; - const sgVec2 uv = (sgVec2){p_u_over_z * z, p_v_over_z * z}; const R* depth = Depth(gfx, x, y); if ((0.f <= p_depth) && (p_depth <= 1.f) && (p_depth <= *depth)) { - // TODO: When doing lighting, need to tone-map here and apply inverse - // gamma here. + const R p_u_over_z = dot3(bar, u_over_zs); + const R p_v_over_z = dot3(bar, v_over_zs); + const sgVec2 uv = (sgVec2){p_u_over_z * z, p_v_over_z * z}; + const sgVec3 n0 = scale3(tri->p0.normal, bar.x * one_over_zs.x); + const sgVec3 n1 = scale3(tri->p1.normal, bar.y * one_over_zs.y); + const sgVec3 n2 = scale3(tri->p2.normal, bar.z * one_over_zs.z); + const sgVec3 normal = add3(add3(n0,n1),n2); //const sgPixel colour = {(uint8_t)(bar.x*255.f), (uint8_t)(bar.y*255.f), (uint8_t)(bar.z*255.f), 255}; //const sgPixel colour = {(int)(z*255.f), (int)(z*255.f), (int)(z*255.f), 255}; //const sgPixel colour = {255, 0, 255, 255}; //const sgPixel colour = {(int)(uv.x * 255.f), (int)(uv.y * 255.f), 255, 255}; - SetPixelDeferred(gfx, (sgVec2i){x,y}, p_depth, gfx->activeTexture, uv); + SetPixelDeferred(gfx, (sgVec2i){x,y}, p_depth, gfx->activeTexture, uv, normal); } } } @@ -742,6 +759,7 @@ size_t sgMem(int width, int height) { Align(N * sizeof(sgTextureId)) + // Texture ID buffer. Align(N * sizeof(sgVec2)) + // Texture coords buffer. Align(N * sizeof(sgPixel)) + // Albedo buffer. + Align(N * sizeof(sgVec3)) + // Normals buffer. Align(SWGFX_TEXTURE_REGISTER_SIZE * sizeof(sgTexture)) + // Texture register. (SG_ALIGN - 1); // To make room to align allocations within the buffer. } @@ -756,6 +774,7 @@ swgfx* sgNew(int width, int height, void* mem) { gfx->texture = SG_ALLOC(&aligned, N, sgTextureId); gfx->texcoords = SG_ALLOC(&aligned, N, sgVec2); gfx->albedo = SG_ALLOC(&aligned, N, sgPixel); + gfx->normals = SG_ALLOC(&aligned, N, sgVec3); gfx->textureRegister = SG_ALLOC(&aligned, SWGFX_TEXTURE_REGISTER_SIZE, sgTexture); gfx->activeTexture = DefaultTextureId; gfx->defaultPixel = (sgPixel){255, 255, 255, 255}; @@ -922,11 +941,12 @@ 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, const sgVec2* texcoords) { +void sgTrianglesIndexed(swgfx* gfx, size_t numIndices, const sgIdx* indices, const sgVec3* positions, const sgVec2* texcoords, const sgVec3* normals) { assert(gfx); assert(indices); assert(positions); assert(texcoords); + assert(normals); for (size_t i = 0; i < numIndices; i+=3) { const sgIdx i0 = indices[i]; const sgIdx i1 = indices[i+1]; @@ -937,25 +957,29 @@ void sgTrianglesIndexed(swgfx* gfx, size_t numIndices, const sgIdx* indices, con const sgVec2 uv0 = texcoords[i0]; const sgVec2 uv1 = texcoords[i1]; const sgVec2 uv2 = texcoords[i2]; + const sgVec3 n0 = normals[i0]; + const sgVec3 n1 = normals[i1]; + const sgVec3 n2 = normals[i2]; const sgTri3 tri = (sgTri3){ - (sgVert3){p0, uv0}, - (sgVert3){p1, uv1}, - (sgVert3){p2, uv2}}; + (sgVert3){p0, uv0, n0}, + (sgVert3){p1, uv1, n1}, + (sgVert3){p2, uv2, n2}}; DrawTriangle3(gfx, &tri); } } -void sgTrianglesIndexedNonUniform(swgfx* gfx, size_t numTris, const sgTriIdx* tris, const sgVec3* positions, const sgVec2* texcoords) { +void sgTrianglesIndexedNonUniform(swgfx* gfx, size_t numTris, const sgTriIdx* tris, const sgVec3* positions, const sgVec2* texcoords, const sgVec3* normals) { assert(gfx); assert(tris); assert(positions); assert(texcoords); + assert(normals); for (size_t t = 0; t < numTris; ++t) { const sgTriIdx* triIdx = &tris[t]; const sgTri3 tri = (sgTri3){ - (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]}}; + (sgVert3){positions[triIdx->v0.pos], texcoords[triIdx->v0.uv], normals[triIdx->v0.normal]}, + (sgVert3){positions[triIdx->v1.pos], texcoords[triIdx->v1.uv], normals[triIdx->v1.normal]}, + (sgVert3){positions[triIdx->v2.pos], texcoords[triIdx->v2.uv], normals[triIdx->v2.normal]}}; DrawTriangle3(gfx, &tri); } } @@ -996,6 +1020,32 @@ void sgAmbient(swgfx* gfx, sgVec3 ambient) { } } +void sgDepth(swgfx* gfx) { + assert(gfx); + const int N = gfx->dims.x * gfx->dims.y; + for (int i = 0; i < N; ++i) { + const R depth = gfx->depth[i]; + if (depth != DepthClearValue) { + sgPixel* colour = &gfx->colour[i]; + const R d = gfx->depth[i]; + *colour = Vec3ToPixel((sgVec3){d,d,d}, 1.f); + } + } +} + +void sgNormals(swgfx* gfx) { + assert(gfx); + const int N = gfx->dims.x * gfx->dims.y; + for (int i = 0; i < N; ++i) { + const R depth = gfx->depth[i]; + if (depth != DepthClearValue) { + sgPixel* colour = &gfx->colour[i]; + const sgVec3* normal = &gfx->normals[i]; + *colour = Vec3ToPixel(max3(Zero3, *normal), 1.f); + } + } +} + void sgGamma(swgfx* gfx, sgPixel* pixels, int width, int height) { assert(gfx); assert(pixels); -- cgit v1.2.3