summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kon/konmodel.c1
-rw-r--r--kon/konsofren.h150
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;