mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-01-31 13:10:34 +00:00
[vulkan] Implement CSM rendering
This covers only the rendering of the shadow maps (actual use still needs to be implemented). Working with orthographic projection matrices is surprisingly difficult, partly because creating one includes the translations needed to get the scene into the view (and depth range), which means care needs to be taken with the view (camera) matrix in order to avoid double-translating depending on just how the orthographic matrix is set up (if it's set up to focus on the origin, then the camera matrix will need translation, otherwise the camera matrix needs to avoid translation).
This commit is contained in:
parent
e00a871824
commit
618740663b
4 changed files with 147 additions and 20 deletions
|
@ -44,10 +44,14 @@ typedef struct qfv_lightmatset_s DARRAY_TYPE (mat4f_t) qfv_lightmatset_t;
|
|||
|
||||
#define MaxLights 768
|
||||
|
||||
#define ST_NONE 0 // no shadows
|
||||
#define ST_PLANE 1 // single plane shadow map (small spotlight)
|
||||
#define ST_CASCADE 2 // cascaded shadow maps
|
||||
#define ST_CUBE 3 // cubemap (omni, large spotlight)
|
||||
enum {
|
||||
ST_NONE, // no shadows
|
||||
ST_PLANE, // single plane shadow map (small spotlight)
|
||||
ST_CASCADE, // cascaded shadow maps
|
||||
ST_CUBE, // cubemap (omni, large spotlight)
|
||||
|
||||
ST_COUNT
|
||||
};
|
||||
|
||||
enum {
|
||||
lighting_main,
|
||||
|
@ -136,6 +140,9 @@ typedef struct lightingctx_s {
|
|||
VkBuffer splat_verts;
|
||||
VkBuffer splat_inds;
|
||||
|
||||
vec4f_t world_mins;
|
||||
vec4f_t world_maxs;
|
||||
|
||||
uint32_t dynamic_base;
|
||||
uint32_t dynamic_matrix_base;
|
||||
uint32_t dynamic_count;
|
||||
|
|
|
@ -61,6 +61,8 @@ typedef struct matrixctx_s {
|
|||
double sky_time;
|
||||
int dirty;
|
||||
|
||||
float fov_x, fov_y;
|
||||
|
||||
matrixframeset_t frames;
|
||||
|
||||
struct qfv_resource_s *resource;
|
||||
|
|
|
@ -359,6 +359,8 @@ vulkan_set_fov (float x, float y)
|
|||
__auto_type mctx = vulkan_ctx->matrix_context;
|
||||
__auto_type mat = &mctx->matrices;
|
||||
|
||||
mctx->fov_x = x;
|
||||
mctx->fov_y = y;
|
||||
QFV_PerspectiveTan (mat->Projection3d, x, y, r_nearclip);
|
||||
|
||||
mctx->dirty = mctx->frames.size;
|
||||
|
|
|
@ -80,6 +80,10 @@
|
|||
#include "vkparse.h"
|
||||
|
||||
#define lnearclip 4
|
||||
#define num_cascade 4
|
||||
|
||||
static vec4f_t ref_direction = { 1, 0, 0, 0 };
|
||||
|
||||
#define ico_verts 12
|
||||
#define cone_verts 7
|
||||
static int ico_inds[] = {
|
||||
|
@ -108,13 +112,13 @@ static cvar_t dynlight_size_cvar = {
|
|||
.flags = CVAR_NONE,
|
||||
.value = { .type = &cexpr_int, .value = &dynlight_size },
|
||||
};
|
||||
#if 0
|
||||
|
||||
static const light_t *
|
||||
get_light (entity_t ent)
|
||||
get_light (uint32_t ent, ecs_registry_t *reg)
|
||||
{
|
||||
return Ent_GetComponent (ent.id, scene_light, ent.reg);
|
||||
return Ent_GetComponent (ent, scene_light, reg);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const dlight_t *
|
||||
get_dynlight (entity_t ent)
|
||||
{
|
||||
|
@ -164,6 +168,9 @@ lighting_setup_shadow (const exprval_t **params, exprval_t *result,
|
|||
}
|
||||
auto pass = Vulkan_Bsp_GetPass (ctx, QFV_bspShadow);
|
||||
auto brush = pass->brush;
|
||||
|
||||
lctx->world_mins = loadvec3f (brush->submodels[0].mins);
|
||||
lctx->world_maxs = loadvec3f (brush->submodels[0].maxs);
|
||||
set_t leafs = SET_STATIC_INIT (brush->modleafs, alloca);
|
||||
set_empty (&leafs);
|
||||
|
||||
|
@ -329,7 +336,7 @@ make_id (uint32_t matrix_index, uint32_t map_index, uint32_t layer,
|
|||
}
|
||||
|
||||
static void
|
||||
cube_mat (mat4f_t *mat, vec4f_t position)
|
||||
cube_mats (mat4f_t *mat, vec4f_t position)
|
||||
{
|
||||
mat4f_t view;
|
||||
mat4fidentity (view);
|
||||
|
@ -348,6 +355,97 @@ cube_mat (mat4f_t *mat, vec4f_t position)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
frustum_corners (vec4f_t corners[8], float minz, float maxz,
|
||||
const mat4f_t invproj)
|
||||
{
|
||||
for (int i = 0; i < 8; i++) {
|
||||
vec4f_t p = {
|
||||
(i & 1) ? 1 : -1,
|
||||
(i & 2) ? 1 : -1,
|
||||
(i & 4) ? maxz : minz,
|
||||
1
|
||||
};
|
||||
p = mvmulf (invproj, p);
|
||||
corners[i] = p / p[3];
|
||||
}
|
||||
}
|
||||
|
||||
static vec4f_t
|
||||
vec_select (vec4i_t c, vec4f_t a, vec4f_t b)
|
||||
{
|
||||
return (vec4f_t) ((c & (vec4i_t) a) | (~c & (vec4i_t) b));
|
||||
}
|
||||
|
||||
static void
|
||||
transform_corners (vec4f_t *mins, vec4f_t *maxs, const vec4f_t corners[8],
|
||||
const mat4f_t lightview)
|
||||
{
|
||||
*mins = (vec4f_t) { INFINITY, INFINITY, INFINITY, INFINITY };
|
||||
*maxs = (vec4f_t) { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
|
||||
for (int i = 0; i < 8; i++) {
|
||||
vec4f_t p = mvmulf (lightview, corners[i]);
|
||||
vec4i_t tmin = p <= *mins;
|
||||
vec4i_t tmax = p >= *maxs;
|
||||
*mins = vec_select (tmin, p, *mins);
|
||||
*maxs = vec_select (tmax, p, *maxs);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cascade_mats (mat4f_t *mat, vec4f_t position, vulkan_ctx_t *ctx)
|
||||
{
|
||||
auto mctx = ctx->matrix_context;
|
||||
auto lctx = ctx->lighting_context;
|
||||
mat4f_t invproj;
|
||||
QFV_InversePerspectiveTanFar (invproj, mctx->fov_x, mctx->fov_y,
|
||||
r_nearclip, r_farclip);
|
||||
mat4f_t inv_z_up;
|
||||
mat4ftranspose (inv_z_up, qfv_z_up);
|
||||
mmulf (invproj, inv_z_up, invproj);
|
||||
mmulf (invproj, r_refdef.camera, invproj);
|
||||
|
||||
mat4f_t lightview;
|
||||
// position points towards the light, but the view needs to be from the
|
||||
// light.
|
||||
mat4fquat (lightview, qrotf (-position, ref_direction));
|
||||
|
||||
// Find the world corner closest to the light in order to ensure the
|
||||
// entire world between the focal point and the light is included in the
|
||||
// depth range
|
||||
vec4i_t d = position >= (vec4f_t) {0, 0, 0, 0};
|
||||
vec4f_t lightcorner = vec_select (d, lctx->world_maxs, lctx->world_mins);
|
||||
// assumes position is a unit vector (which it will be for directional
|
||||
// lights loaded from quake maps)
|
||||
float corner_dist = dotf (lightcorner-r_refdef.camera[3], position)[0];
|
||||
|
||||
// Pre-swizzle the light view so it's done only once (and so the
|
||||
// frustum bounds get swizzled correctly for creating the orthographic
|
||||
// projection matrix).
|
||||
// Also, no need (actually, undesirable) to include any translation in
|
||||
// the light view matrix as the orthographic matrix setup includes
|
||||
// translation based on the bounds.
|
||||
mmulf (lightview, qfv_z_up, lightview);
|
||||
|
||||
vec2f_t z_range[] = {
|
||||
{ r_nearclip / 32, 1 },
|
||||
{ r_nearclip / 256, r_nearclip / 32 },
|
||||
{ r_nearclip / 1024, r_nearclip / 256 },
|
||||
{ 0, r_nearclip / 1024 },
|
||||
};
|
||||
for (int i = 0; i < num_cascade; i++) {
|
||||
vec4f_t corners[8];
|
||||
vec4f_t fmin, fmax;
|
||||
frustum_corners (corners, z_range[i][0], z_range[i][1], invproj);
|
||||
transform_corners (&fmin, &fmax, corners, lightview);
|
||||
// ensure evertything between the light and the far frustum corner
|
||||
// is in the depth range
|
||||
fmin[2] = min(fmin[2], -corner_dist);
|
||||
QFV_OrthographicV (mat[i], fmin, fmax);
|
||||
mmulf (mat[i], mat[i], lightview);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
lighting_update_lights (const exprval_t **params, exprval_t *result,
|
||||
exprctx_t *ectx)
|
||||
|
@ -373,8 +471,8 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
|
|||
QFV_PacketCopyBuffer (packet, lframe->style_buffer, 0, bb);
|
||||
QFV_PacketSubmit (packet);
|
||||
|
||||
uint32_t light_ids[4][MaxLights];
|
||||
uint32_t entids[4][MaxLights];
|
||||
uint32_t light_ids[ST_COUNT][MaxLights];
|
||||
uint32_t entids[ST_COUNT][MaxLights];
|
||||
|
||||
uint32_t light_count = 0;
|
||||
auto queue = lframe->light_queue;
|
||||
|
@ -405,6 +503,26 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
|
|||
queue[mode].count++;
|
||||
}
|
||||
|
||||
if (queue[ST_CASCADE].count) {
|
||||
packet = QFV_PacketAcquire (ctx->staging);
|
||||
uint32_t mat_count = queue[ST_CASCADE].count * num_cascade;
|
||||
mat4f_t *mats = QFV_PacketExtend (packet, sizeof (mat4f_t[mat_count]));
|
||||
qfv_scatter_t scatter[queue[ST_CASCADE].count];
|
||||
for (uint32_t i = 0; i < queue[ST_CASCADE].count; i++) {
|
||||
auto r = &lctx->light_control.a[light_ids[ST_CASCADE][i]];
|
||||
auto light = get_light (entids[ST_CASCADE][i], lctx->scene->reg);
|
||||
cascade_mats (&mats[i * num_cascade], light->position, ctx);
|
||||
scatter[i] = (qfv_scatter_t) {
|
||||
.srcOffset = sizeof (mat4f_t[i * num_cascade]),
|
||||
.dstOffset = sizeof (mat4f_t[r->matrix_id]),
|
||||
.length = sizeof (mat4f_t[num_cascade]),
|
||||
};
|
||||
}
|
||||
QFV_PacketScatterBuffer (packet, lframe->shadowmat_buffer,
|
||||
queue[ST_CASCADE].count, scatter, bb);
|
||||
QFV_PacketSubmit (packet);
|
||||
}
|
||||
|
||||
if (ndlight) {
|
||||
light_count += ndlight;
|
||||
packet = QFV_PacketAcquire (ctx->staging);
|
||||
|
@ -450,7 +568,7 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
|
|||
uint32_t msize = sizeof (mat4f_t[ndlight * 6]);
|
||||
mat4f_t *mats = QFV_PacketExtend (packet, msize);
|
||||
for (int i = 0; i < ndlight; i++) {
|
||||
cube_mat (&mats[i * 6], dynamic_lights[i]->origin);
|
||||
cube_mats (&mats[i * 6], dynamic_lights[i]->origin);
|
||||
}
|
||||
VkDeviceSize mat_offset = sizeof (mat4f_t[lctx->dynamic_matrix_base]);
|
||||
QFV_PacketCopyBuffer (packet, lframe->shadowmat_buffer, mat_offset, bb);
|
||||
|
@ -463,13 +581,13 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
|
|||
}
|
||||
|
||||
if (light_count) {
|
||||
for (int i = 1; i < 4; i++) {
|
||||
for (int i = 1; i < ST_COUNT; i++) {
|
||||
queue[i].start = queue[i - 1].start + queue[i - 1].count;
|
||||
}
|
||||
packet = QFV_PacketAcquire (ctx->staging);
|
||||
uint32_t *lids = QFV_PacketExtend (packet,
|
||||
sizeof (uint32_t[light_count]));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0; i < ST_COUNT; i++) {
|
||||
memcpy (lids + queue[i].start, light_ids[i],
|
||||
sizeof (uint32_t[queue[i].count]));
|
||||
}
|
||||
|
@ -480,7 +598,7 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
|
|||
packet = QFV_PacketAcquire (ctx->staging);
|
||||
uint32_t *eids = QFV_PacketExtend (packet,
|
||||
sizeof (uint32_t[light_count]));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0; i < ST_COUNT; i++) {
|
||||
memcpy (eids + queue[i].start, entids[i],
|
||||
sizeof (uint32_t[queue[i].count]));
|
||||
}
|
||||
|
@ -1221,8 +1339,6 @@ Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx)
|
|||
free (lctx);
|
||||
}
|
||||
|
||||
static vec4f_t ref_direction = { 1, 0, 0, 0 };
|
||||
|
||||
static void
|
||||
create_light_matrices (lightingctx_t *lctx)
|
||||
{
|
||||
|
@ -1290,7 +1406,7 @@ create_light_matrices (lightingctx_t *lctx)
|
|||
// dependent on view fustrum and cascade level
|
||||
mat4fidentity (proj);
|
||||
mmulf (view, qfv_z_up, view);
|
||||
for (int j = 0; j < 4; j++) {
|
||||
for (int j = 0; j < num_cascade; j++) {
|
||||
mmulf (lm[j], proj, view);
|
||||
}
|
||||
break;
|
||||
|
@ -1418,7 +1534,7 @@ allocate_map (mapctx_t *mctx, int type, int (*getsize) (const light_t *light))
|
|||
int size = -1;
|
||||
int numLayers = 0;
|
||||
int totalLayers = 0;
|
||||
int layers = ((int[4]) { 0, 1, 4, 6 })[type];
|
||||
int layers = ((int[ST_COUNT]) { 0, 1, num_cascade, 6 })[type];
|
||||
int cube = type == ST_CUBE;
|
||||
|
||||
for (int i = 0; i < mctx->numLights; i++) {
|
||||
|
@ -1647,7 +1763,7 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
|
|||
case 6:
|
||||
lr->renderpass_index = 2;
|
||||
break;
|
||||
case 4:
|
||||
case num_cascade:
|
||||
lr->renderpass_index = 1;
|
||||
break;
|
||||
case 1:
|
||||
|
|
Loading…
Reference in a new issue