diff options
| -rw-r--r-- | kon/konmodel.c | 1 | ||||
| -rw-r--r-- | kon/konsofren.h | 150 |
2 files changed, 141 insertions, 10 deletions
diff --git a/kon/konmodel.c b/kon/konmodel.c index 78995c8..000e401 100644 --- a/kon/konmodel.c +++ b/kon/konmodel.c @@ -574,6 +574,7 @@ void kon_modelDraw(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4_t material.texture = source->image; material.color = source->color; material.filter = source->filter; + material.mips = NULL; draw.indices = mesh->indices; draw.triangleCount = mesh->triangleCount; draw.vertices = mesh->vertices; diff --git a/kon/konsofren.h b/kon/konsofren.h index 498c79b..9b71a64 100644 --- a/kon/konsofren.h +++ b/kon/konsofren.h @@ -28,6 +28,7 @@ #include <stdint.h> #include <stdlib.h> +#include <string.h> #include <math.h> #include "konmath.h" @@ -112,11 +113,20 @@ typedef struct kon_vertex { kon_vec2_t uv; } kon_vertex_t; -/* texture is used when it isn't NULL, otherwise color */ +/* smaller copies of a texture, each half the size of the one before, down to 1 pixel. level 0 is the texture itself. + far away surfaces use the small ones so they stop shimmering */ +typedef struct kon_mipmaps { + int levels; + kon_image *image[16]; /* image[0] is not owned, the rest are */ +} kon_mipmaps_t; + +/* texture is used when it isn't NULL, otherwise color. mips can stay NULL, the texture is used at full size. + with mips the texture still has to be the one they were made from */ typedef struct kon_material { const kon_image *texture; uint32_t color; kon_filter_t filter; + const kon_mipmaps_t *mips; } kon_material_t; /* one light from far away, like the sun, plus a base light that reaches everything */ @@ -125,6 +135,9 @@ typedef struct kon_light { uint32_t color; /* 0xFFRRGGBB, alpha is ignored */ float intensity; /* strength of the directional part, 0 to 1 */ float ambient; /* strength of the base light, 0 to 1 */ + uint32_t fogColor; /* what far away things fade into, usually the sky */ + float fogStart; /* distance where the fade begins */ + float fogEnd; /* distance where it is all fog, 0 turns fog off */ } kon_light_t; /* front faces are counter-clockwise, indices hold three entries per triangle */ @@ -207,6 +220,10 @@ void kon_drawWireMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4 kon_material_t kon_materialColor(uint32_t color); kon_material_t kon_materialTexture(const kon_image *texture); + +/* NULL if there is no memory. free it with kon_freeMipmaps, the texture has to outlive it */ +kon_mipmaps_t *kon_createMipmaps(const kon_image *texture); +void kon_freeMipmaps(kon_mipmaps_t *mips); kon_light_t kon_lightDefault(void); /* light can be NULL for no lighting. the triangle's positions and normals are in world space */ @@ -1077,12 +1094,12 @@ void kon_drawWireMesh(kon_framebuffer_t *fb, kon_mat4_t viewProjection, kon_mat4 } kon_material_t kon_materialColor(uint32_t color) { - kon_material_t material = { NULL, color, KON_FILTER_NEAREST }; + kon_material_t material = { NULL, color, KON_FILTER_NEAREST, NULL }; return material; } kon_material_t kon_materialTexture(const kon_image *texture) { - kon_material_t material = { texture, 0xFFFFFFFF, KON_FILTER_NEAREST }; + kon_material_t material = { texture, 0xFFFFFFFF, KON_FILTER_NEAREST, NULL }; return material; } @@ -1092,9 +1109,63 @@ kon_light_t kon_lightDefault(void) { light.color = 0xFFFFFFFF; light.intensity = 0.8f; light.ambient = 0.3f; + light.fogColor = 0xFF000000; + light.fogStart = 0.0f; + light.fogEnd = 0.0f; return light; } +kon_mipmaps_t *kon_createMipmaps(const kon_image *texture) { + if (!texture || texture->width < 1 || texture->height < 1) return NULL; + + kon_mipmaps_t *mips = calloc(1, sizeof(kon_mipmaps_t)); + if (!mips) return NULL; + + mips->image[0] = (kon_image *)texture; + mips->levels = 1; + + while (mips->levels < 16) { + const kon_image *from = mips->image[mips->levels - 1]; + if (from->width == 1 && from->height == 1) break; + + int w = from->width > 1 ? from->width / 2 : 1; + int h = from->height > 1 ? from->height / 2 : 1; + kon_image *to = kon_createFramebuffer(w, h); + if (!to) { + kon_freeMipmaps(mips); + return NULL; + } + + for (int y = 0; y < h; y++) { + int y0 = y * 2, y1 = y0 + 1 < from->height ? y0 + 1 : y0; + + for (int x = 0; x < w; x++) { + int x0 = x * 2, x1 = x0 + 1 < from->width ? x0 + 1 : x0; + uint32_t p[4] = { + from->data[(size_t)y0 * (size_t)from->width + (size_t)x0], from->data[(size_t)y0 * (size_t)from->width + (size_t)x1], + from->data[(size_t)y1 * (size_t)from->width + (size_t)x0], from->data[(size_t)y1 * (size_t)from->width + (size_t)x1] + }; + uint32_t out = 0; + + for (int shift = 0; shift < 32; shift += 8) { + uint32_t sum = ((p[0] >> shift) & 0xFF) + ((p[1] >> shift) & 0xFF) + ((p[2] >> shift) & 0xFF) + ((p[3] >> shift) & 0xFF); + out |= ((sum + 2) >> 2) << shift; + } + to->data[(size_t)y * (size_t)w + (size_t)x] = out; + } + } + mips->image[mips->levels++] = to; + } + + return mips; +} + +void kon_freeMipmaps(kon_mipmaps_t *mips) { + if (!mips) return; + for (int i = 1; i < mips->levels; i++) kon_freeFramebuffer(mips->image[i]); + free(mips); +} + /* how bright a surface with this normal is, 0 to 1. toLight points at the light and has length 1 */ static double kon_lightFactor_(const kon_light_t *light, kon_vec3_t toLight, kon_vec3_t normal) { double diffuse = (double)kon_vec3Dot(kon_vec3Normalize(normal), toLight); @@ -1166,6 +1237,28 @@ static inline double kon_fraction_(double x) { return x < 0.0 ? x + 1.0 : x; } +/* mixes the color toward fog, amount 0 is none and 256 is all fog. alpha stays as it was */ +static inline uint32_t kon_applyFog_(uint32_t color, uint32_t fog, uint32_t amount) { + uint32_t keep = 256 - amount; + uint32_t r = ((((color >> 16) & 0xFF) * keep) + (((fog >> 16) & 0xFF) * amount) + 128) >> 8; + uint32_t g = ((((color >> 8) & 0xFF) * keep) + (((fog >> 8) & 0xFF) * amount) + 128) >> 8; + uint32_t b = (((color & 0xFF) * keep) + ((fog & 0xFF) * amount) + 128) >> 8; + + return (color & 0xFF000000u) | (r << 16) | (g << 8) | b; +} + +/* which mip level fits a pixel that covers rho2 texels squared. the exponent of the float is the log2, level 1 starts at 2 texels */ +static inline int kon_mipLevel_(double rho2, int levels) { + float f = (float)rho2; + uint32_t bits; + + memcpy(&bits, &f, sizeof(bits)); + int e = (int)((bits >> 23) & 0xFF) - 127; + int level = e < 0 ? 0 : (e + 1) >> 1; + + return level < levels ? level : levels - 1; +} + static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex_t *a, const kon_rasterVertex_t *b, const kon_rasterVertex_t *c, const kon_material_t *material, const kon_light_t *light, kon_renderFlags_t flags) { const kon_rasterVertex_t *in[3] = {a, b, c}; @@ -1233,6 +1326,23 @@ static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex int useDepth = (flags & KON_RENDER_DEPTH) != 0; const kon_image *texture = material->texture; + /* how fast u and v change across the screen, only worked out when there are mips to pick from */ + const kon_mipmaps_t *mips = (texture && material->mips && material->mips->levels > 1) ? material->mips : NULL; + double dInvWx = 0.0, dInvWy = 0.0, dUx = 0.0, dUy = 0.0, dVx = 0.0, dVy = 0.0; + if (mips) { + dInvWx = (invW[0] * e0Dx + invW[1] * e1Dx + invW[2] * e2Dx) * invArea; + dInvWy = (invW[0] * e0Dy + invW[1] * e1Dy + invW[2] * e2Dy) * invArea; + dUx = (uw[0] * e0Dx + uw[1] * e1Dx + uw[2] * e2Dx) * invArea; + dUy = (uw[0] * e0Dy + uw[1] * e1Dy + uw[2] * e2Dy) * invArea; + dVx = (vw[0] * e0Dx + vw[1] * e1Dx + vw[2] * e2Dx) * invArea; + dVy = (vw[0] * e0Dy + vw[1] * e1Dy + vw[2] * e2Dy) * invArea; + } + + int useFog = light && light->fogEnd > light->fogStart; + double fogStart = useFog ? (double)light->fogStart : 0.0; + double fogScale = useFog ? 1.0 / ((double)light->fogEnd - (double)light->fogStart) : 0.0; + uint32_t fogColor = useFog ? light->fogColor : 0; + double lightRgb[3] = {1.0, 1.0, 1.0}; if (light) { lightRgb[0] = (double)((light->color >> 16) & 0xFF) / 255.0; @@ -1242,6 +1352,7 @@ static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex for (int y = y0; y <= y1; y++) { double e0 = e0Row, e1 = e1Row, e2 = e2Row; + int mipPick = -1; for (int x = x0; x <= x1; x++) { if (e0 <= 0.0 && e1 <= 0.0 && e2 <= 0.0) { @@ -1259,20 +1370,33 @@ static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex /* uv / w is what varies linearly on screen, dividing by 1 / w again undoes the perspective */ double u = (l0 * uw[0] + l1 * uw[1] + l2 * uw[2]) * w; double v = (l0 * vw[0] + l1 * vw[1] + l2 * vw[2]) * w; + const kon_image *level = texture; + + if (mips) { + /* the level hardly changes from one pixel to the next, so it is only worked out every fourth one */ + if (mipPick < 0 || (x & 3) == 0) { + double ux = (dUx - u * dInvWx) * w * texture->width, uy = (dUy - u * dInvWy) * w * texture->width; + double vx = (dVx - v * dInvWx) * w * texture->height, vy = (dVy - v * dInvWy) * w * texture->height; + double rx = ux * ux + vx * vx, ry = uy * uy + vy * vy; + + mipPick = kon_mipLevel_(rx > ry ? rx : ry, mips->levels); + } + level = mips->image[mipPick]; + } u = kon_fraction_(u); v = kon_fraction_(v); if (material->filter == KON_FILTER_LINEAR) { - texel = kon_sampleLinear_(texture, KON_RECT(0, 0, texture->width, texture->height), - (float)(u * texture->width), (float)(v * texture->height), 1); + texel = kon_sampleLinear_(level, KON_RECT(0, 0, level->width, level->height), + (float)(u * level->width), (float)(v * level->height), 1); } else { - int tx = (int)(u * texture->width); - int ty = (int)(v * texture->height); - if (tx > texture->width - 1) tx = texture->width - 1; - if (ty > texture->height - 1) ty = texture->height - 1; + int tx = (int)(u * level->width); + int ty = (int)(v * level->height); + if (tx > level->width - 1) tx = level->width - 1; + if (ty > level->height - 1) ty = level->height - 1; - texel = texture->data[(size_t)ty * (size_t)texture->width + (size_t)tx]; + texel = level->data[(size_t)ty * (size_t)level->width + (size_t)tx]; } } @@ -1280,6 +1404,12 @@ static void kon_rasterizeTriangle_(kon_framebuffer_t *fb, const kon_rasterVertex texel = kon_applyLight_(texel, (l0 * lw[0] + l1 * lw[1] + l2 * lw[2]) * w, lightRgb); } + if (useFog && w > fogStart) { + double f = (w - fogStart) * fogScale; + + texel = kon_applyFog_(texel, fogColor, f >= 1.0 ? 256u : (uint32_t)(f * 256.0)); + } + uint32_t alpha = texel >> 24; if (alpha == 0xFF) { fb->data[index] = texel; |
