From f20d32eb3a264f8704adc5f2525b442642ebf237 Mon Sep 17 00:00:00 2001 From: mid <> Date: Sat, 10 May 2025 18:17:33 +0300 Subject: [PATCH] Cascaded shadow mapping support --- src/k3.c | 239 +++++++++++++++++++++++++++++++++++-------------------- src/k3.h | 1 + 2 files changed, 153 insertions(+), 87 deletions(-) diff --git a/src/k3.c b/src/k3.c index 21dcd32..d0f31ab 100644 --- a/src/k3.c +++ b/src/k3.c @@ -584,6 +584,16 @@ static struct k3Light *Lights; void k3SetLights(size_t count, struct k3Light *lightsNew) { LightCount = count; Lights = lightsNew; + + for(int i = 0; i < count; i++) { + if(Lights[i].type == k3_DIRECTIONAL) { + if(Lights[i].dir.cascadeCount == 0) { + Lights[i].dir.cascadeCount = 1; + } else if(Lights[i].dir.cascadeCount > 3) { + Lights[i].dir.cascadeCount = 3; + } + } + } } struct k3Light *k3GetLights(size_t *a) { @@ -617,15 +627,18 @@ static mat4 CamMat; static mat4 ProjMat; struct LightShadow { - mat4 vp; + uint8_t vpCount; + mat4 vp[6]; + vec4 atlasSegment; + #ifdef k3_IRREGULAR_SHADOWS GLuint multimapEls; // buffer GLuint multimapHeads; // image #endif } __attribute__((aligned(16))); -static size_t LightShadowsCount, LightShadowsCapacity; static struct LightShadow *LightShadows; +static size_t LightShadowsCount = 0, LightShadowsCapacity = 0; static struct k3Offscreen *ShadowAtlas; static bool LightShadowIrregularMode = false; @@ -818,17 +831,22 @@ static void setup_glsl_lighting_uniforms(GLuint bound, int lightsStart, int ligh lightsCount = LightCount - lightsStart; } - vec4 settings1[4]; - vec4 settings2[4]; + if(lightsCount) { + // raise(SIGINT); + } - vec4 colors[4]; - memset(colors, 0, sizeof(colors)); + vec4 settings1[4] = {}; + vec4 settings2[4] = {}; + + vec4 colors[4] = {}; for(int i = 0; i < lightsCount; i++) { struct k3Light *l = &Lights[i + lightsStart]; if(l->type == k3_DIRECTIONAL) { glm_vec4_copy(l->dir.direction, settings1[i]); + + settings2[i][0] = l->dir.cascadeCount; } else if(l->type == k3_SPOT) { glm_vec4_copy(l->spot.position, settings1[i]); @@ -879,20 +897,26 @@ static void setup_glsl_shadow_uniforms(GLuint bound, int atlasUnit, int lightsSt k3Log(k3_ERR, "Max 4 lights per pass"); } - if(!LightShadows) return; + if(!ShadowAtlas) return; if(LightShadowIrregularMode) { assert(k3IsCore); glUniform1i(glGetUniformLocation(bound, "u_pixelsinshadow"), 0); } else { - mat4 m[4]; + size_t vpi = 0; + mat4 m[6]; memset(m, 0, sizeof(m)); vec4 seg[4]; for(int i = 0; i < lightsCount; i++) { - glm_mat4_copy(LightShadows[i + lightsStart].vp, m[i]); + for(int z = 0; z < LightShadows[i + lightsStart].vpCount; z++) { + glm_mat4_copy(LightShadows[i + lightsStart].vp[z], m[vpi]); + + vpi++; + } + glm_vec4_copy(LightShadows[i + lightsStart].atlasSegment, seg[i]); } @@ -905,12 +929,12 @@ static void setup_glsl_shadow_uniforms(GLuint bound, int atlasUnit, int lightsSt } if(!k3IsCore) { - glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_shadows0vp"), 4, GL_FALSE, (float*) m); + glUniformMatrix4fvARB(glGetUniformLocationARB(bound, "u_shadows0vp"), vpi, GL_FALSE, (float*) m); glUniform4fvARB(glGetUniformLocationARB(bound, "u_shadows0seg"), 4, (float*) seg); glUniform1iARB(glGetUniformLocationARB(bound, "u_shadows0atlas"), atlasUnit); } else { - glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_shadows0vp"), 4, GL_FALSE, (float*) m); + glUniformMatrix4fv(glGetUniformLocationARB(bound, "u_shadows0vp"), vpi, GL_FALSE, (float*) m); glUniform4fv(glGetUniformLocationARB(bound, "u_shadows0seg"), 4, (float*) seg); glUniform1i(glGetUniformLocationARB(bound, "u_shadows0atlas"), atlasUnit); @@ -1659,76 +1683,109 @@ void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull) { glFrontFace(GL_CCW); } -static size_t compute_light_views(mat4 **_projs, mat4 **_cams, char **_isCube) { - mat4 *projs = _mm_malloc(sizeof(*projs) * LightCount * 6, 16); - mat4 *cams = _mm_malloc(sizeof(*cams) * LightCount * 6, 16); - char *isCube = _mm_malloc(sizeof(*isCube) * LightCount * 6, 1); +static void split_frustum(mat4 proj, int cascades, mat4 croppeds[]) { + float fovy = glm_persp_fovy(proj); + float aspect = glm_persp_aspect(proj); - size_t s = 0; + float near, far; + glm_persp_decomp_near(proj, &near); + glm_persp_decomp_far(proj, &far); + + float depthChunk = (far - near) / cascades; + + for(int c = 0; c < cascades; c++) { + glm_perspective(fovy, aspect, near + c * depthChunk, near + (c + 1) * depthChunk, croppeds[c]); + } +} + +struct LightView { + uint8_t cams; + uint8_t projs; + mat4 c[6]; + mat4 p[3]; +}; + +static size_t compute_light_views(struct LightView lv[]) { + size_t totalTilesUsed = 0; for(int i = 0; i < LightCount; i++) { - isCube[s] = 0; - struct k3Light *l = &Lights[i]; - /*if(!l->castShadow) { - continue; - }*/ - if(l->type == k3_DIRECTIONAL) { - glm_ortho(-60, 60, -60, 60, -60, 60, projs[s]); + size_t CASCADE_COUNT = l->dir.cascadeCount; - mat4 invmainproj; - glm_mat4_inv_fast(ProjMat, invmainproj); + lv[i].projs = CASCADE_COUNT; + lv[i].cams = CASCADE_COUNT; - mat4 frustummat; - glm_mat4_mul(CamMat, invmainproj, frustummat); + mat4 croppeds[CASCADE_COUNT]; + split_frustum(ProjMat, CASCADE_COUNT, croppeds); - vec4 corners[8]; - glm_frustum_corners(frustummat, corners); - - vec4 viewcenter; - glm_frustum_center(corners, viewcenter); - - mat4 view; - glm_look_anyup(viewcenter, l->dir.direction, view); - - glm_mat4_inv_fast(view, cams[s]); - - s++; + for(int cascade = 0; cascade < CASCADE_COUNT; cascade++) { + mat4 invmainproj; + glm_mat4_inv_fast(croppeds[cascade], invmainproj); + + mat4 frustummat; + glm_mat4_mul(CamMat, invmainproj, frustummat); + + vec4 corners[8]; + glm_frustum_corners(frustummat, corners); + + vec4 viewcenter; + glm_frustum_center(corners, viewcenter); + + mat4 lightview; + glm_look_anyup(viewcenter, l->dir.direction, lightview); + + vec4 minaabb = {+HUGE_VALF, +HUGE_VALF, +HUGE_VALF}; + vec4 maxaabb = {-HUGE_VALF, -HUGE_VALF, -HUGE_VALF}; + + for(int c = 0; c < 8; c++) { + vec4 lightspaceCorner; + glm_mat4_mulv(lightview, corners[c], lightspaceCorner); + + glm_vec4_minv(minaabb, lightspaceCorner, minaabb); + glm_vec4_maxv(maxaabb, lightspaceCorner, maxaabb); + } + + glm_ortho(minaabb[0], maxaabb[0], minaabb[1], maxaabb[1], minaabb[2] - 50, maxaabb[2], lv[i].p[cascade]); + glm_mat4_inv_fast(lightview, lv[i].c[cascade]); + } } else if(l->type == k3_SPOT || l->type == k3_HALF_OMNI) { - glm_perspective(l->spot.angle, 1, 0.1, l->radius, projs[s]); + lv[i].projs = 1; + lv[i].cams = 1; + + glm_perspective(l->spot.angle, 1, 0.1, l->radius, lv[i].p[0]); mat4 view; glm_look_anyup(l->spot.position, l->spot.direction, view); - glm_mat4_inv_fast(view, cams[s]); - - s++; + glm_mat4_inv_fast(view, lv[i].c[0]); } else if(l->type == k3_OMNI) { static const vec3 dirs[] = {{0, 0, -1}, {0, 0, 1}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}}; static const vec3 ups[] = {{0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}}; + lv[i].projs = 1; + lv[i].cams = 6; + + mat4 proj; + glm_perspective(glm_rad(120), 1, 0.1, l->radius, proj); + + glm_mat4_copy(proj, lv[i].p[0]); + for(int d = 0; d < 6; d++) { - glm_perspective(glm_rad(120), 1, 0.1, l->radius, projs[s]); mat4 view; glm_look(l->omni.position, dirs[d], ups[d], view); - glm_mat4_inv_fast(view, cams[s]); + glm_mat4_inv_fast(view, lv[i].c[d]); - isCube[s] = d != 0; - - s++; } } + + totalTilesUsed += lv[i].cams; } - *_projs = projs; - *_cams = cams; - *_isCube = isCube; - - return s; + return totalTilesUsed; } #ifdef k3_IRREGULAR_SHADOWS @@ -1892,7 +1949,7 @@ void k3PassIrregular(struct k3Offscreen *mainview, mat4 mainproj, mat4 maincam) mat4 *cams; char *isCube; - size_t count = compute_light_views(&projs, &cams, &isCube); + count = compute_light_views(&projs, &cams, &isCube); LightShadowsCount = count; if(LightShadowsCount > LightShadowsCapacity) { @@ -1995,11 +2052,8 @@ void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr) { glm_mat4_copy(projection, ProjMat); glm_mat4_copy(cam, CamMat); - mat4 *projs; - mat4 *cams; - char *isCube; - - size_t count = compute_light_views(&projs, &cams, &isCube); + struct LightView *views = alloca(sizeof(*views) * LightCount); + size_t totalTilesUsed = compute_light_views(views); ShadowAtlas = offscr; @@ -2014,7 +2068,7 @@ void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr) { LightShadowsCapacity = LightShadowsCount; } - if(count == 0) { + if(totalTilesUsed == 0) { return; } @@ -2025,45 +2079,56 @@ void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr) { int cellsPerDimension = 0; - if(count == 1) { + if(totalTilesUsed == 1) { cellsPerDimension = 1; } else { - int cellsTotalLog = 1; - while((1 << cellsTotalLog) < count) { - cellsTotalLog++; + int cellsTotalSqrt = 1; + while((cellsTotalSqrt * cellsTotalSqrt) < totalTilesUsed) { + cellsTotalSqrt++; } - cellsPerDimension = 1 << ((cellsTotalLog + 1) / 2); + cellsPerDimension = cellsTotalSqrt; } uint16_t sz = k3TexSzX(offscr->depth); - uint16_t cellSz = sz / cellsPerDimension; - - size_t s = 0; + float cellSz = (float) sz / cellsPerDimension; k3BeginOffscreen(offscr); - for(size_t i = 0; i < count; i++) { - int cellX = i % cellsPerDimension; - int cellY = i / cellsPerDimension; + size_t i = 0; + for(size_t li = 0; li < LightCount; li++) { + struct LightView *lv = &views[li]; - int vp[] = {cellX * cellSz, cellY * cellSz, cellSz, cellSz}; - - mat4 view = GLM_MAT4_IDENTITY_INIT; - glm_mat4_inv(cams[i], view); - - if(!isCube[i]) { - glm_mat4_mul(projs[i], view, LightShadows[s].vp); - glm_vec4_copy((vec4) {i, (float) cellSz / sz, 0, 0}, LightShadows[s].atlasSegment); - s++; + for(int camIdx = 0; camIdx < lv->cams; camIdx++) { + int cellX = i % cellsPerDimension; + int cellY = i / cellsPerDimension; + + int vp[] = { + (int) roundf(cellX * cellSz), + (int) roundf(cellY * cellSz), + (int) cellSz, + (int) cellSz + }; + + mat4 view = GLM_MAT4_IDENTITY_INIT; + glm_mat4_inv(lv->c[camIdx], view); + + mat4 proj = GLM_MAT4_IDENTITY_INIT; + glm_mat4_copy(lv->projs == 1 ? lv->p[0] : lv->p[camIdx], proj); + + LightShadows[li].vpCount = lv->projs; + if(lv->projs > 1 || camIdx == 0) { + glm_mat4_mul(proj, view, LightShadows[li].vp[camIdx]); + } + if(camIdx == 0) { + glm_vec4_copy((vec4) {i, (float) cellSz / sz, 0, 0}, LightShadows[li].atlasSegment); + } + + glViewport(vp[0], vp[1], vp[2], vp[3]); + k3PassDepthOnly(proj, lv->c[camIdx], i == 0, false); + + i++; } - - glViewport(vp[0], vp[1], vp[2], vp[3]); - k3PassDepthOnly(projs[i], cams[i], i == 0, false); } k3EndOffscreen(offscr); - - _mm_free(projs); - _mm_free(cams); - _mm_free(isCube); } void k3BatchClear() { diff --git a/src/k3.h b/src/k3.h index d448948..ba452f3 100644 --- a/src/k3.h +++ b/src/k3.h @@ -136,6 +136,7 @@ struct k3Light { _Alignas(16) union { struct { vec4 direction; + uint8_t cascadeCount; } dir; struct { vec4 position;