[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.
This commit is contained in:
Bill Currie 2021-04-25 10:01:57 +09:00
parent 52168da93e
commit ae231319ea
3 changed files with 281 additions and 64 deletions

View file

@ -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];

View file

@ -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);
}

View file

@ -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++) {