From ae231319eae5c7c511b9d55426700f2537799806 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Sun, 25 Apr 2021 10:01:57 +0900 Subject: [PATCH] [vulkan] Implement all the extended light models Standard quake has just linear, but the modding community added inverse, inverse-square (raw and offset (1/(r^2+1)), infinite (sun), and ambient (minlight). Other than the lack of shadows, marcher now looks really good. --- include/QF/Vulkan/qf_lighting.h | 7 +- .../renderer/vulkan/shader/lighting.frag | 177 +++++++++++++----- libs/video/renderer/vulkan/vulkan_lighting.c | 161 ++++++++++++++-- 3 files changed, 281 insertions(+), 64 deletions(-) diff --git a/include/QF/Vulkan/qf_lighting.h b/include/QF/Vulkan/qf_lighting.h index 346f2bf5f..516d6acb7 100644 --- a/include/QF/Vulkan/qf_lighting.h +++ b/include/QF/Vulkan/qf_lighting.h @@ -41,7 +41,7 @@ typedef struct qfv_light_s { vec3_t color; int data; vec3_t position; - float radius; + float light; vec3_t direction; float cone; } qfv_light_t; @@ -55,9 +55,8 @@ typedef struct qfv_lightvisset_s DARRAY_TYPE (byte) qfv_lightvisset_t; typedef struct qfv_light_buffer_s { float intensity[NUM_STYLES + 4]; - int cascadeCount; - int planeCount; - int cubeCount; + float distFactor1; + float distFactor2; int lightCount; qfv_light_t lights[NUM_LIGHTS] __attribute__((aligned(16))); mat4f_t shadowMat[NUM_LIGHTS]; diff --git a/libs/video/renderer/vulkan/shader/lighting.frag b/libs/video/renderer/vulkan/shader/lighting.frag index 5f5524cc8..a6db899c8 100644 --- a/libs/video/renderer/vulkan/shader/lighting.frag +++ b/libs/video/renderer/vulkan/shader/lighting.frag @@ -10,12 +10,28 @@ struct LightData { vec3 color; int data;// bits 0-6: intensity key (however, values 0-66) vec3 position; - float radius; + float light; // doubles as radius for linear vec3 direction; float cone; }; -#define MaxLights 256 +#define MaxLights 256 + +#define StyleMask 0x07f +#define ModelMask 0x380 +#define ShadowMask 0xc00 + +#define LM_LINEAR (0 << 7) // light - dist (or radius + dist if -ve) +#define LM_INVERSE (1 << 7) // distFactor1 * light / dist +#define LM_INVERSE2 (2 << 7) // distFactor2 * light / (dist * dist) +#define LM_INFINITE (3 << 7) // light +#define LM_AMBIENT (4 << 7) // light +#define LM_INVERSE3 (5 << 7) // distFactor2 * light / (dist + distFactor2)**2 + +#define ST_NONE (0 << 10) // no shadows +#define ST_CASCADE (1 << 10) // cascaded shadow maps +#define ST_PLANE (2 << 10) // single plane shadow map (small spotlight) +#define ST_CUBE (3 << 10) // cubemap (omni, large spotlight) layout (set = 2, binding = 0) uniform sampler2DArrayShadow shadowCascade[MaxLights]; layout (set = 2, binding = 0) uniform sampler2DShadow shadowPlane[MaxLights]; @@ -23,7 +39,9 @@ layout (set = 2, binding = 0) uniform samplerCubeShadow shadowCube[MaxLights]; layout (set = 1, binding = 0) uniform Lights { vec4 intensity[17]; // 68 floats - ivec4 lightCounts; + float distFactor1; // for inverse + float distFactor2; // for inverse2 and inverse2 + int lightCount; LightData lights[MaxLights]; mat4 shadowMat[MaxLights]; vec4 shadowCascale[MaxLights]; @@ -31,45 +49,80 @@ layout (set = 1, binding = 0) uniform Lights { layout (location = 0) out vec4 frag_color; -vec3 -calc_light (LightData light, vec3 position, vec3 normal) +float +spot_cone (LightData light, vec3 incoming) { - float r = light.radius; - vec3 dist = light.position - position; - float d = sqrt (dot (dist, dist)); - if (d > r) { - // the light is too far away - return vec3 (0); - } - - vec3 incoming = dist / d; float spotdot = dot (incoming, light.direction); - float lightdot = dot (incoming, normal); - - int style = light.data & 0x7f; - // deliberate array index error: access intensity2 as well - float i = intensity[style / 4][style % 4]; - i *= step (d, r) * clamp (lightdot, 0, 1); - i *= smoothstep (spotdot, 1 - (1 - spotdot) * 0.995, light.cone); - return light.color * i * (r - d); + return smoothstep (spotdot, 1 - (1 - spotdot) * 0.995, light.cone); } -vec3 +float +diffuse (vec3 incoming, vec3 normal) +{ + float lightdot = dot (incoming, normal); + return clamp (lightdot, 0, 1); +} + +float +light_linear (LightData light, float d) +{ + float l = light.light; + if (l < 0) { + return min (l + d, 0); + } else { + return max (l - d, 0); + } +} + +float +light_inverse (LightData light, float d) +{ + float l = light.light; + return l / (distFactor1 * d); +} + +float +light_inverse2 (LightData light, float d) +{ + float l = light.light; + return l / (distFactor2 * d); +} + +float +light_infinite (LightData light) +{ + return light.light; +} + +float +light_ambient (LightData light) +{ + return light.light; +} + +float +light_inverse3 (LightData light, float d) +{ + float l = light.light; + return l / (distFactor2 * d + 1); +} + +float shadow_cascade (sampler2DArrayShadow map) { - return vec3(1); + return 1; } -vec3 +float shadow_plane (sampler2DShadow map) { - return vec3(1); + return 1; } -vec3 +float shadow_cube (samplerCubeShadow map) { - return vec3(1); + return 1; } void @@ -83,23 +136,59 @@ main (void) vec3 light = vec3 (0); if (MaxLights > 0) { - int i = 0; - while (i < lightCounts.x) { - shadow_cascade (shadowCascade[i]); - i++; - } - while (i < lightCounts.y) { - shadow_plane (shadowPlane[i]); - i++; - } - while (i < lightCounts.z) { - shadow_cube (shadowCube[i]); - i++; - } - while (i < lightCounts.w) { - light += calc_light (lights[i], p, n); - i++; + vec3 minLight = vec3 (0); + for (int i = 0; i < lightCount; i++) { + vec3 dist = lights[i].position - p; + float d = dot (dist, dist); + int model = lights[i].data & ModelMask; + + if (model != LM_INFINITE + && d > lights[i].light * lights[i].light) { + continue; + } + + float l = 0; + if (model == LM_LINEAR) { + d = sqrt (d); + l = light_linear (lights[i], d); + } else if (model == LM_INVERSE) { + d = sqrt (d); + l = light_inverse (lights[i], d); + } else if (model == LM_INVERSE2) { + l = light_inverse2 (lights[i], d); + d = sqrt (d); + } else if (model == LM_INFINITE) { + l = light_infinite (lights[i]); + dist = lights[i].direction; + d = -1; + } else if (model == LM_AMBIENT) { + l = light_ambient (lights[i]); + } else if (model == LM_INVERSE3) { + l = light_inverse3 (lights[i], d); + d = sqrt (d); + } + + int style = lights[i].data & StyleMask; + l *= intensity[style / 4][style % 4]; + + int shadow = lights[i].data & ShadowMask; + if (shadow == ST_CASCADE) { + l *= shadow_cascade (shadowCascade[i]); + } else if (shadow == ST_PLANE) { + l *= shadow_plane (shadowPlane[i]); + } else if (shadow == ST_CUBE) { + l *= shadow_cube (shadowCube[i]); + } + + if (model == LM_AMBIENT) { + minLight = max (l * lights[i].color, minLight); + } else { + vec3 incoming = dist / d; + l *= spot_cone (lights[i], incoming) * diffuse (incoming, n); + light += l * lights[i].color; + } } + light = max (light, minLight); } frag_color = vec4 (c * light + e, 1); } diff --git a/libs/video/renderer/vulkan/vulkan_lighting.c b/libs/video/renderer/vulkan/vulkan_lighting.c index fc0f59f51..8fd57078e 100644 --- a/libs/video/renderer/vulkan/vulkan_lighting.c +++ b/libs/video/renderer/vulkan/vulkan_lighting.c @@ -98,7 +98,8 @@ find_visible_lights (vulkan_ctx_t *ctx) memset (lframe->lightvis.a, 0, lframe->lightvis.size * sizeof (byte)); for (size_t i = 0; i < lctx->lightleafs.size; i++) { int l = lctx->lightleafs.a[i]; - if (lframe->pvs[l / 8] & (1 << (l % 8))) { + //FIXME -1 needs check for sky + if (l == -1 || lframe->pvs[l / 8] & (1 << (l % 8))) { lframe->lightvis.a[i] = 1; visible++; } @@ -132,9 +133,9 @@ update_lights (vulkan_ctx_t *ctx) light_data->intensity[66] = 1 / 16.0; light_data->intensity[67] = 1 / 16.0; - light_data->cascadeCount = 0; - light_data->planeCount = 0; - light_data->cubeCount = 0; + light_data->distFactor1 = 1 / 128.0; + light_data->distFactor2 = 1 / 16384.0; + light_data->lightCount = 0; R_FindNearLights (r_origin, NUM_LIGHTS - 1, lights); for (int i = 0; i < NUM_LIGHTS - 1; i++) { @@ -144,7 +145,7 @@ update_lights (vulkan_ctx_t *ctx) light_data->lightCount++; VectorCopy (lights[i]->color, light_data->lights[i].color); VectorCopy (lights[i]->origin, light_data->lights[i].position); - light_data->lights[i].radius = lights[i]->radius; + light_data->lights[i].light = lights[i]->radius; light_data->lights[i].data = 64; // default dynamic light VectorZero (light_data->lights[i].direction); light_data->lights[i].cone = 1; @@ -406,11 +407,121 @@ Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx) free (lctx); } +static void +dump_light (qfv_light_t *light, int leaf) +{ + Sys_MaskPrintf (SYS_vulkan, + "[%g, %g, %g] %d %d %d, " + "[%g %g %g] %g, [%g %g %g] %g, %d\n", + VectorExpand (light->color), + (light->data & 0x07f), + (light->data & 0x380) >> 7, + (light->data & 0xc00) >> 10, + VectorExpand (light->position), light->light, + VectorExpand (light->direction), light->cone, + leaf); +} + +static float +parse_float (const char *str, float defval) +{ + float val = defval; + if (str) { + char *end; + val = strtof (str, &end); + if (end == str) { + val = defval; + } + } + return val; +} + +static void +parse_vector (const char *str, vec_t *val) +{ + if (str) { + int num = sscanf (str, "%f %f %f", VectorExpandAddr (val)); + while (num < 3) { + val[num++] = 0; + } + } +} + +static float +ecos (float ang) +{ + if (ang == 90 || ang == -90) { + return 0; + } + if (ang == 180 || ang == -180) { + return -1; + } + if (ang == 0 || ang == 360) { + return 1; + } + return cos (ang * M_PI / 180); +} + +static float +esin (float ang) +{ + if (ang == 90) { + return 1; + } + if (ang == -90) { + return -1; + } + if (ang == 180 || ang == -180) { + return 0; + } + if (ang == 0 || ang == 360) { + return 0; + } + return sin (ang * M_PI / 180); +} + +static void +sun_vector (const vec_t *ang, vec_t *vec) +{ + // ang is yaw, pitch (maybe roll, but ignored + vec[0] = ecos (ang[1]) * ecos (ang[0]); + vec[1] = ecos (ang[1]) * esin (ang[0]); + vec[2] = esin (ang[1]); +} + +static void +parse_sun (lightingctx_t *lctx, plitem_t *entity) +{ + qfv_light_t light = {}; + float sunlight; + //float sunlight2; + vec3_t sunangle = { 0, -90, 0 }; + + sunlight = parse_float (PL_String (PL_ObjectForKey (entity, + "_sunlight")), 0); + //sunlight2 = parse_float (PL_String (PL_ObjectForKey (entity, + // "_sunlight2")), 0); + parse_vector (PL_String (PL_ObjectForKey (entity, "_sun_mangle")), + sunangle); + if (sunlight <= 0) { + return; + } + VectorSet (1, 1, 1, light.color); + light.data = 3 << 7; //FIXME magic number (LM_INFINITE) + light.light = sunlight; + sun_vector (sunangle, light.direction); + light.cone = 1; + DARRAY_APPEND (&lctx->lights, light); + DARRAY_APPEND (&lctx->lightleafs, -1); + dump_light (&light, -1); +} + static void parse_light (qfv_light_t *light, const plitem_t *entity, const plitem_t *targets) { const char *str; + int model = 0; /*Sys_Printf ("{\n"); for (int i = PL_D_NumKeys (entity); i-- > 0; ) { @@ -422,7 +533,7 @@ parse_light (qfv_light_t *light, const plitem_t *entity, light->cone = 1; light->data = 0; - light->radius = 300; + light->light = 300; VectorSet (1, 1, 1, light->color); if ((str = PL_String (PL_ObjectForKey (entity, "origin")))) { @@ -449,16 +560,34 @@ parse_light (qfv_light_t *light, const plitem_t *entity, if ((str = PL_String (PL_ObjectForKey (entity, "light_lev"))) || (str = PL_String (PL_ObjectForKey (entity, "_light")))) { - light->radius = atof (str); + light->light = atof (str); } if ((str = PL_String (PL_ObjectForKey (entity, "style")))) { light->data = atoi (str) & 0x3f; } + if ((str = PL_String (PL_ObjectForKey (entity, "delay")))) { + model = atoi (str) & 0x7; + if (model == 2) model = 5; //FIXME for marcher (need a map) + light->data |= model << 7; + } + if ((str = PL_String (PL_ObjectForKey (entity, "color"))) || (str = PL_String (PL_ObjectForKey (entity, "_color")))) { sscanf (str, "%f %f %f", VectorExpandAddr (light->color)); + VectorScale (light->color, 1/255.0, light->color); + } + + //FIXME magic numbers + if (model == 3) { // infinite + light->data |= 1 << 10; // cascade + } else if (model != 4) {// ambient + if (light->cone > -0.5) { + light->data |= 3 << 10; // cube + } else { + light->data |= 2 << 10; // plane + } } } @@ -500,11 +629,17 @@ Vulkan_LoadLights (model_t *model, const char *entity_data, vulkan_ctx_t *ctx) } } - for (int i = 1; i < PL_A_NumObjects (entities); i++) { + for (int i = 0; i < PL_A_NumObjects (entities); i++) { plitem_t *entity = PL_ObjectAtIndex (entities, i); const char *classname = PL_String (PL_ObjectForKey (entity, "classname")); - if (classname && strnequal (classname, "light", 5)) { + if (!classname) { + continue; + } + if (strequal (classname, "worldspawn")) { + // parse_sun can add many lights + parse_sun (lctx, entity); + } else if (strnequal (classname, "light", 5)) { qfv_light_t light = {}; parse_light (&light, entity, targets); @@ -512,13 +647,7 @@ Vulkan_LoadLights (model_t *model, const char *entity_data, vulkan_ctx_t *ctx) mleaf_t *leaf = Mod_PointInLeaf (&light.position[0], model); DARRAY_APPEND (&lctx->lightleafs, leaf - model->brush.leafs); - Sys_MaskPrintf (SYS_vulkan, - "[%g, %g, %g] %d, " - "[%g %g %g] %g, [%g %g %g] %g, %zd\n", - VectorExpand (light.color), light.data, - VectorExpand (light.position), light.radius, - VectorExpand (light.direction), light.cone, - leaf - model->brush.leafs); + dump_light (&light, leaf - model->brush.leafs); } } for (size_t i = 0; i < ctx->frames.size; i++) {