mirror of
https://github.com/w23/xash3d-fwgs
synced 2025-01-18 23:00:01 +01:00
663306f4e1
known issues: - visible cluster boundaries which affect sampling outcomes (essentially clusters act like very coarse shadows, and that's visible) moving brush models are not supported yet, affects perf measurements
1306 lines
39 KiB
C
1306 lines
39 KiB
C
#include "vk_light.h"
|
|
#include "vk_mapents.h"
|
|
#include "vk_textures.h"
|
|
#include "vk_brush.h"
|
|
#include "vk_lightmap.h"
|
|
#include "vk_cvar.h"
|
|
#include "vk_common.h"
|
|
#include "profiler.h"
|
|
|
|
#include "mod_local.h"
|
|
#include "xash3d_mathlib.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h> // isalnum...
|
|
|
|
#include "camera.h"
|
|
#include "pm_defs.h"
|
|
#include "pmtrace.h"
|
|
|
|
#define PROFILER_SCOPES(X) \
|
|
X(finalize , "VK_LightsFrameFinalize"); \
|
|
X(emissive_surface, "VK_LightsAddEmissiveSurface"); \
|
|
X(static_lights, "add static lights"); \
|
|
X(dlights, "add dlights"); \
|
|
//X(canSurfaceLightAffectAABB, "canSurfaceLightAffectAABB"); \
|
|
|
|
#define SCOPE_DECLARE(scope, name) APROF_SCOPE_DECLARE(scope)
|
|
PROFILER_SCOPES(SCOPE_DECLARE)
|
|
#undef SCOPE_DECLARE
|
|
|
|
static struct {
|
|
qboolean enabled;
|
|
char name_filter[256];
|
|
} debug_dump_lights;
|
|
|
|
static void debugDumpLights( void ) {
|
|
debug_dump_lights.enabled = true;
|
|
if (gEngine.Cmd_Argc() > 1) {
|
|
Q_strncpy(debug_dump_lights.name_filter, gEngine.Cmd_Argv(1), sizeof(debug_dump_lights.name_filter));
|
|
} else {
|
|
debug_dump_lights.name_filter[0] = '\0';
|
|
}
|
|
}
|
|
|
|
vk_lights_t g_lights = {0};
|
|
|
|
void VK_LightsInit( void ) {
|
|
PROFILER_SCOPES(APROF_SCOPE_INIT);
|
|
|
|
gEngine.Cmd_AddCommand("vk_lights_dump", debugDumpLights, "Dump all light sources for next frame");
|
|
}
|
|
|
|
static void clusterBitMapShutdown( void );
|
|
|
|
void VK_LightsShutdown( void ) {
|
|
gEngine.Cmd_RemoveCommand("vk_lights_dump");
|
|
clusterBitMapShutdown();
|
|
}
|
|
|
|
typedef struct {
|
|
int num;
|
|
int leafs[];
|
|
} vk_light_leaf_set_t;
|
|
|
|
typedef struct {
|
|
vk_light_leaf_set_t *potentially_visible_leafs;
|
|
} vk_surface_metadata_t;
|
|
|
|
static struct {
|
|
// Worldmodel surfaces
|
|
int num_surfaces;
|
|
vk_surface_metadata_t *surfaces;
|
|
|
|
// Used for accumulating potentially visible leafs
|
|
struct {
|
|
int count;
|
|
|
|
// This buffer space is used for two things:
|
|
// As a growing array of u16 leaf indexes (low 16 bits)
|
|
// As a bit field for marking added leafs (highest {31st} bit)
|
|
uint32_t leafs[MAX_MAP_LEAFS];
|
|
|
|
byte visbytes[(MAX_MAP_LEAFS+7)/8];
|
|
} accum;
|
|
|
|
} g_lights_bsp = {0};
|
|
|
|
static void loadRadData( const model_t *map, const char *fmt, ... ) {
|
|
fs_offset_t size;
|
|
char *data;
|
|
byte *buffer;
|
|
char filename[1024];
|
|
|
|
va_list argptr;
|
|
va_start( argptr, fmt );
|
|
vsnprintf( filename, sizeof filename, fmt, argptr );
|
|
va_end( argptr );
|
|
|
|
buffer = gEngine.COM_LoadFile( filename, &size, false);
|
|
|
|
if (!buffer) {
|
|
gEngine.Con_Printf(S_ERROR "Couldn't load RAD data from file %s, the map will be completely black\n", filename);
|
|
return;
|
|
}
|
|
|
|
gEngine.Con_Reportf("Loading RAD data from file %s\n", filename);
|
|
|
|
data = (char*)buffer;
|
|
for (;;) {
|
|
string name;
|
|
float r=0, g=0, b=0, scale=0;
|
|
int num;
|
|
char* line_end;
|
|
|
|
while (*data != '\0' && isspace(*data)) ++data;
|
|
if (*data == '\0')
|
|
break;
|
|
|
|
line_end = Q_strchr(data, '\n');
|
|
if (line_end) *line_end = '\0';
|
|
|
|
name[0] = '\0';
|
|
num = sscanf(data, "%s %f %f %f %f", name, &r, &g, &b, &scale);
|
|
gEngine.Con_Printf("raw rad entry (%d): %s %f %f %f %f\n", num, name, r, g, b, scale);
|
|
if (Q_strstr(name, "//") != NULL) {
|
|
num = 0;
|
|
}
|
|
|
|
if (num == 2) {
|
|
r = g = b;
|
|
} else if (num == 5) {
|
|
scale /= 255.f;
|
|
r *= scale;
|
|
g *= scale;
|
|
b *= scale;
|
|
} else if (num == 4) {
|
|
// Ok, rgb only, no scaling
|
|
} else {
|
|
gEngine.Con_Printf( "skipping rad entry %s\n", name[0] ? name : "(empty)" );
|
|
num = 0;
|
|
}
|
|
|
|
if (num != 0) {
|
|
gEngine.Con_Printf("rad entry (%d): %s %f %f %f (%f)\n", num, name, r, g, b, scale);
|
|
|
|
{
|
|
const char *wad_name = NULL;
|
|
char *texture_name = Q_strchr(name, '/');
|
|
string texname;
|
|
int tex_id;
|
|
const qboolean enabled = (r != 0 || g != 0 || b != 0);
|
|
|
|
if (!texture_name) {
|
|
texture_name = name;
|
|
} else {
|
|
// name is now just a wad name
|
|
texture_name[0] = '\0';
|
|
wad_name = name;
|
|
|
|
texture_name += 1;
|
|
}
|
|
|
|
// FIXME replace this with findTexturesNamedLike from vk_materials.c
|
|
|
|
// Try bsp texture first
|
|
tex_id = XVK_TextureLookupF("#%s:%s.mip", map->name, texture_name);
|
|
|
|
// Try wad texture if bsp is not there
|
|
if (!tex_id && wad_name) {
|
|
tex_id = XVK_TextureLookupF("%s.wad/%s.mip", wad_name, texture_name);
|
|
}
|
|
|
|
if (!tex_id) {
|
|
const char *wad = g_map_entities.wadlist;
|
|
for (; *wad;) {
|
|
const char *const wad_end = Q_strchr(wad, ';');
|
|
tex_id = XVK_TextureLookupF("%.*s/%s.mip", wad_end - wad, wad, texture_name);
|
|
if (tex_id)
|
|
break;
|
|
wad = wad_end + 1;
|
|
}
|
|
}
|
|
|
|
if (tex_id) {
|
|
vk_emissive_texture_t *const etex = g_lights.map.emissive_textures + tex_id;
|
|
ASSERT(tex_id < MAX_TEXTURES);
|
|
|
|
etex->emissive[0] = r;
|
|
etex->emissive[1] = g;
|
|
etex->emissive[2] = b;
|
|
etex->set = enabled;
|
|
|
|
// See DIRECT_SCALE in qrad/lightmap.c
|
|
VectorScale(etex->emissive, 0.1f, etex->emissive);
|
|
|
|
if (!enabled)
|
|
gEngine.Con_Reportf("rad entry %s disabled due to zero intensity\n", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!line_end)
|
|
break;
|
|
|
|
data = line_end + 1;
|
|
}
|
|
|
|
Mem_Free(buffer);
|
|
}
|
|
|
|
static void leafAccumPrepare( void ) {
|
|
memset(&g_lights_bsp.accum, 0, sizeof(g_lights_bsp.accum));
|
|
}
|
|
|
|
#define LEAF_ADDED_BIT 0x8000000ul
|
|
|
|
static qboolean leafAccumAdd( uint16_t leaf_index ) {
|
|
// Check whether this leaf was already added
|
|
if (g_lights_bsp.accum.leafs[leaf_index] & LEAF_ADDED_BIT)
|
|
return false;
|
|
|
|
g_lights_bsp.accum.leafs[leaf_index] |= LEAF_ADDED_BIT;
|
|
|
|
g_lights_bsp.accum.leafs[g_lights_bsp.accum.count++] |= leaf_index;
|
|
return true;
|
|
}
|
|
|
|
static void leafAccumFinalize( void ) {
|
|
for (int i = 0; i < g_lights_bsp.accum.count; ++i)
|
|
g_lights_bsp.accum.leafs[i] &= 0xffffu;
|
|
}
|
|
|
|
static int leafAccumAddPotentiallyVisibleFromLeaf(const model_t *const map, const mleaf_t *leaf, qboolean print_debug) {
|
|
int pvs_leaf_index = 0;
|
|
int leafs_added = 0;
|
|
const byte *pvs = leaf->compressed_vis;
|
|
for (;pvs_leaf_index < map->numleafs; ++pvs) {
|
|
uint8_t bits = pvs[0];
|
|
|
|
// PVS is RLE encoded
|
|
if (bits == 0) {
|
|
const int skip = pvs[1];
|
|
pvs_leaf_index += skip * 8;
|
|
++pvs;
|
|
continue;
|
|
}
|
|
|
|
for (int k = 0; k < 8; ++k, ++pvs_leaf_index, bits >>= 1) {
|
|
if ((bits&1) == 0)
|
|
continue;
|
|
|
|
if (leafAccumAdd( pvs_leaf_index + 1 )) {
|
|
leafs_added++;
|
|
if (print_debug)
|
|
gEngine.Con_Reportf(" .%d", pvs_leaf_index + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return leafs_added;
|
|
}
|
|
|
|
vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) {
|
|
const model_t *const map = gEngine.pfnGetModelByIndex( 1 );
|
|
const int surf_index = surf - map->surfaces;
|
|
vk_surface_metadata_t * const smeta = g_lights_bsp.surfaces + surf_index;
|
|
const qboolean verbose_debug = false;
|
|
|
|
if (surf_index < 0 || surf_index >= g_lights_bsp.num_surfaces) {
|
|
gEngine.Con_Printf(S_ERROR "FIXME not implemented: attempting to add non-static polygon light\n");
|
|
return NULL;
|
|
}
|
|
|
|
ASSERT(surf_index >= 0);
|
|
ASSERT(surf_index < g_lights_bsp.num_surfaces);
|
|
|
|
// Check if PVL hasn't been collected yet
|
|
if (!smeta->potentially_visible_leafs) {
|
|
int leafs_direct = 0, leafs_pvs = 0;
|
|
leafAccumPrepare();
|
|
|
|
// Enumerate all the map leafs and pick ones that have this surface referenced
|
|
if (verbose_debug)
|
|
gEngine.Con_Reportf("Collecting visible leafs for surface %d:", surf_index);
|
|
for (int i = 1; i <= map->numleafs; ++i) {
|
|
const mleaf_t *leaf = map->leafs + i;
|
|
//if (verbose_debug) gEngine.Con_Reportf(" leaf %d(c%d)/%d:", i, leaf->cluster, map->numleafs);
|
|
for (int j = 0; j < leaf->nummarksurfaces; ++j) {
|
|
const msurface_t *leaf_surf = leaf->firstmarksurface[j];
|
|
if (leaf_surf != surf) {
|
|
/* if (verbose_debug) { */
|
|
/* const int leaf_surf_index = leaf_surf - map->surfaces; */
|
|
/* gEngine.Con_Reportf(" !%d", leaf_surf_index); */
|
|
/* } */
|
|
continue;
|
|
}
|
|
|
|
// FIXME split direct leafs marking from pvs propagation
|
|
leafs_direct++;
|
|
if (leafAccumAdd( i )) {
|
|
if (verbose_debug) gEngine.Con_Reportf(" %d", i);
|
|
} else {
|
|
// This leaf was already added earlier by PVS
|
|
// but it really should be counted as direct
|
|
--leafs_pvs;
|
|
}
|
|
|
|
// Get all PVS leafs
|
|
leafs_pvs += leafAccumAddPotentiallyVisibleFromLeaf(map, leaf, verbose_debug);
|
|
}
|
|
|
|
//if (verbose_debug) gEngine.Con_Reportf("\n");
|
|
}
|
|
if (verbose_debug)
|
|
gEngine.Con_Reportf(" (sum=%d, direct=%d, pvs=%d)\n", g_lights_bsp.accum.count, leafs_direct, leafs_pvs);
|
|
|
|
leafAccumFinalize();
|
|
|
|
smeta->potentially_visible_leafs = (vk_light_leaf_set_t*)Mem_Malloc(vk_core.pool, sizeof(smeta->potentially_visible_leafs[0]) + sizeof(int) * g_lights_bsp.accum.count);
|
|
smeta->potentially_visible_leafs->num = g_lights_bsp.accum.count;
|
|
|
|
for (int i = 0; i < g_lights_bsp.accum.count; ++i) {
|
|
smeta->potentially_visible_leafs->leafs[i] = g_lights_bsp.accum.leafs[i];
|
|
}
|
|
}
|
|
|
|
return smeta->potentially_visible_leafs;
|
|
}
|
|
|
|
|
|
static struct {
|
|
#define CLUSTERS_BIT_MAP_SIZE_UINT ((g_lights.map.grid_cells + 31) / 32)
|
|
uint32_t *clusters_bit_map;
|
|
} g_lights_tmp;
|
|
|
|
static void clusterBitMapClear( void ) {
|
|
memset(g_lights_tmp.clusters_bit_map, 0, CLUSTERS_BIT_MAP_SIZE_UINT * sizeof(uint32_t));
|
|
}
|
|
|
|
// Returns true if wasn't set
|
|
static qboolean clusterBitMapCheckOrSet( int cell_index ) {
|
|
uint32_t *const bits = g_lights_tmp.clusters_bit_map + (cell_index / 32);
|
|
const uint32_t bit = 1u << (cell_index % 32);
|
|
|
|
if ((*bits) & bit)
|
|
return false;
|
|
|
|
(*bits) |= bit;
|
|
return true;
|
|
}
|
|
|
|
static void clusterBitMapInit( void ) {
|
|
ASSERT(!g_lights_tmp.clusters_bit_map);
|
|
|
|
g_lights_tmp.clusters_bit_map = Mem_Malloc(vk_core.pool, CLUSTERS_BIT_MAP_SIZE_UINT * sizeof(uint32_t));
|
|
clusterBitMapClear();
|
|
}
|
|
|
|
static void clusterBitMapShutdown( void ) {
|
|
if (g_lights_tmp.clusters_bit_map)
|
|
Mem_Free(g_lights_tmp.clusters_bit_map);
|
|
g_lights_tmp.clusters_bit_map = NULL;
|
|
}
|
|
|
|
int R_LightCellIndex( const int light_cell[3] ) {
|
|
if (light_cell[0] < 0 || light_cell[1] < 0 || light_cell[2] < 0
|
|
|| (light_cell[0] >= g_lights.map.grid_size[0])
|
|
|| (light_cell[1] >= g_lights.map.grid_size[1])
|
|
|| (light_cell[2] >= g_lights.map.grid_size[2]))
|
|
return -1;
|
|
|
|
return light_cell[0] + light_cell[1] * g_lights.map.grid_size[0] + light_cell[2] * g_lights.map.grid_size[0] * g_lights.map.grid_size[1];
|
|
}
|
|
|
|
vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, const matrix3x4 *transform_row ) {
|
|
const model_t *const map = gEngine.pfnGetModelByIndex( 1 );
|
|
const mextrasurf_t *const extra = surf->info;
|
|
|
|
// This is a very conservative way to construct a bounding sphere. It's not great.
|
|
const vec3_t bbox_center = {
|
|
(extra->mins[0] + extra->maxs[0]) / 2.f,
|
|
(extra->mins[1] + extra->maxs[1]) / 2.f,
|
|
(extra->mins[2] + extra->maxs[2]) / 2.f,
|
|
};
|
|
|
|
const vec3_t bbox_size = {
|
|
extra->maxs[0] - extra->mins[0],
|
|
extra->maxs[1] - extra->mins[1],
|
|
extra->maxs[2] - extra->mins[2],
|
|
};
|
|
|
|
int leafs_direct = 0, leafs_pvs = 0;
|
|
|
|
const float radius = .5f * VectorLength(bbox_size);
|
|
vec3_t origin;
|
|
|
|
Matrix3x4_VectorTransform(*transform_row, bbox_center, origin);
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf("\torigin = %f, %f, %f, R = %f\n",
|
|
origin[0], origin[1], origin[2], radius
|
|
);
|
|
}
|
|
|
|
leafAccumPrepare();
|
|
|
|
// TODO it's possible to somehow more efficiently traverse the bsp and collect only the affected leafs
|
|
// (origin + radius will accidentally touch leafs that are really should not be affected)
|
|
gEngine.R_FatPVS(origin, radius, g_lights_bsp.accum.visbytes, /*merge*/ false, /*fullvis*/ false);
|
|
if (debug_dump_lights.enabled)
|
|
gEngine.Con_Reportf("Collecting visible leafs for moving surface %p: %f,%f,%f %f: ", surf,
|
|
origin[0], origin[1], origin[2], radius);
|
|
|
|
for (int i = 0; i <= map->numleafs; ++i) {
|
|
const mleaf_t *leaf = map->leafs + i;
|
|
if( !CHECKVISBIT( g_lights_bsp.accum.visbytes, i ))
|
|
continue;
|
|
|
|
leafs_direct++;
|
|
|
|
if (leafAccumAdd( i + 1 )) {
|
|
if (debug_dump_lights.enabled)
|
|
gEngine.Con_Reportf(" %d", i + 1);
|
|
} else {
|
|
// This leaf was already added earlier by PVS
|
|
// but it really should be counted as direct
|
|
leafs_pvs--;
|
|
}
|
|
}
|
|
|
|
if (debug_dump_lights.enabled)
|
|
gEngine.Con_Reportf(" (sum=%d, direct=%d, pvs=%d)\n", g_lights_bsp.accum.count, leafs_direct, leafs_pvs);
|
|
|
|
leafAccumFinalize();
|
|
|
|
// ...... oh no
|
|
return (vk_light_leaf_set_t*)&g_lights_bsp.accum.count;
|
|
}
|
|
|
|
static void prepareSurfacesLeafVisibilityCache( const struct model_s *map ) {
|
|
if (g_lights_bsp.surfaces != NULL) {
|
|
for (int i = 0; i < g_lights_bsp.num_surfaces; ++i) {
|
|
vk_surface_metadata_t *smeta = g_lights_bsp.surfaces + i;
|
|
if (smeta->potentially_visible_leafs)
|
|
Mem_Free(smeta->potentially_visible_leafs);
|
|
}
|
|
Mem_Free(g_lights_bsp.surfaces);
|
|
}
|
|
|
|
g_lights_bsp.num_surfaces = map->numsurfaces;
|
|
g_lights_bsp.surfaces = Mem_Malloc(vk_core.pool, g_lights_bsp.num_surfaces * sizeof(vk_surface_metadata_t));
|
|
for (int i = 0; i < g_lights_bsp.num_surfaces; ++i)
|
|
g_lights_bsp.surfaces[i].potentially_visible_leafs = NULL;
|
|
}
|
|
|
|
void RT_LightsNewMapBegin( const struct model_s *map ) {
|
|
// 1. Determine map bounding box (and optimal grid size?)
|
|
// map->mins, maxs
|
|
vec3_t map_size, min_cell, max_cell;
|
|
VectorSubtract(map->maxs, map->mins, map_size);
|
|
|
|
VectorDivide(map->mins, LIGHT_GRID_CELL_SIZE, min_cell);
|
|
min_cell[0] = floorf(min_cell[0]);
|
|
min_cell[1] = floorf(min_cell[1]);
|
|
min_cell[2] = floorf(min_cell[2]);
|
|
VectorCopy(min_cell, g_lights.map.grid_min_cell);
|
|
|
|
VectorDivide(map->maxs, LIGHT_GRID_CELL_SIZE, max_cell);
|
|
max_cell[0] = ceilf(max_cell[0]);
|
|
max_cell[1] = ceilf(max_cell[1]);
|
|
max_cell[2] = ceilf(max_cell[2]);
|
|
|
|
VectorSubtract(max_cell, min_cell, g_lights.map.grid_size);
|
|
g_lights.map.grid_cells = g_lights.map.grid_size[0] * g_lights.map.grid_size[1] * g_lights.map.grid_size[2];
|
|
|
|
ASSERT(g_lights.map.grid_cells < MAX_LIGHT_CLUSTERS);
|
|
|
|
gEngine.Con_Reportf("Map mins:(%f, %f, %f), maxs:(%f, %f, %f), size:(%f, %f, %f), min_cell:(%f, %f, %f) cells:(%d, %d, %d); total: %d\n",
|
|
map->mins[0], map->mins[1], map->mins[2],
|
|
map->maxs[0], map->maxs[1], map->maxs[2],
|
|
map_size[0], map_size[1], map_size[2],
|
|
min_cell[0], min_cell[1], min_cell[2],
|
|
g_lights.map.grid_size[0],
|
|
g_lights.map.grid_size[1],
|
|
g_lights.map.grid_size[2],
|
|
g_lights.map.grid_cells
|
|
);
|
|
|
|
clusterBitMapShutdown();
|
|
clusterBitMapInit();
|
|
|
|
prepareSurfacesLeafVisibilityCache( map );
|
|
|
|
// Load RAD data based on map name
|
|
memset(g_lights.map.emissive_textures, 0, sizeof(g_lights.map.emissive_textures));
|
|
loadRadData( map, "maps/lights.rad" );
|
|
|
|
{
|
|
int name_len = Q_strlen(map->name);
|
|
|
|
// Strip ".bsp" suffix
|
|
if (name_len > 4 && 0 == Q_stricmp(map->name + name_len - 4, ".bsp"))
|
|
name_len -= 4;
|
|
|
|
loadRadData( map, "%.*s.rad", name_len, map->name );
|
|
}
|
|
|
|
// Clear static lights counts
|
|
{
|
|
g_lights.num_polygons = g_lights.num_static.polygons = 0;
|
|
g_lights.num_point_lights = g_lights.num_static.point_lights = 0;
|
|
g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices = 0;
|
|
|
|
for (int i = 0; i < g_lights.map.grid_cells; ++i) {
|
|
vk_lights_cell_t *const cell = g_lights.cells + i;
|
|
cell->num_point_lights = cell->num_static.point_lights = 0;
|
|
cell->num_polygons = cell->num_static.polygons = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RT_LightsFrameInit( void ) {
|
|
g_lights.num_polygons = g_lights.num_static.polygons;
|
|
g_lights.num_point_lights = g_lights.num_static.point_lights;
|
|
g_lights.num_polygon_vertices = g_lights.num_static.polygon_vertices;
|
|
|
|
for (int i = 0; i < g_lights.map.grid_cells; ++i) {
|
|
vk_lights_cell_t *const cell = g_lights.cells + i;
|
|
cell->num_polygons = cell->num_static.polygons;
|
|
cell->num_point_lights = cell->num_static.point_lights;
|
|
}
|
|
}
|
|
|
|
static qboolean addSurfaceLightToCell( int cell_index, int polygon_light_index ) {
|
|
vk_lights_cell_t *const cluster = g_lights.cells + cell_index;
|
|
|
|
if (cluster->num_polygons == MAX_VISIBLE_SURFACE_LIGHTS) {
|
|
return false;
|
|
}
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf(" adding polygon light %d to cell %d (count=%d)\n", polygon_light_index, cell_index, cluster->num_polygons+1);
|
|
}
|
|
|
|
cluster->polygons[cluster->num_polygons++] = polygon_light_index;
|
|
return true;
|
|
}
|
|
|
|
static qboolean addLightToCell( int cell_index, int light_index ) {
|
|
vk_lights_cell_t *const cluster = g_lights.cells + cell_index;
|
|
|
|
if (cluster->num_point_lights == MAX_VISIBLE_POINT_LIGHTS)
|
|
return false;
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf(" adding point light %d to cell %d (count=%d)\n", light_index, cell_index, cluster->num_point_lights+1);
|
|
}
|
|
|
|
cluster->point_lights[cluster->num_point_lights++] = light_index;
|
|
return true;
|
|
}
|
|
|
|
static qboolean canSurfaceLightAffectAABB(const model_t *mod, const msurface_t *surf, const vec3_t emissive, const float minmax[6]) {
|
|
//APROF_SCOPE_BEGIN_EARLY(canSurfaceLightAffectAABB); // DO NOT DO THIS. We have like 600k of these calls per frame :feelsbadman:
|
|
qboolean retval = true;
|
|
// FIXME transform surface
|
|
// this here only works for static map model
|
|
|
|
// Use bbox center for normal culling estimation
|
|
const vec3_t bbox_center = {
|
|
(minmax[0] + minmax[3]) / 2.f,
|
|
(minmax[1] + minmax[4]) / 2.f,
|
|
(minmax[2] + minmax[5]) / 2.f,
|
|
};
|
|
|
|
float bbox_plane_dist = PlaneDiff(bbox_center, surf->plane);
|
|
if( FBitSet( surf->flags, SURF_PLANEBACK ))
|
|
bbox_plane_dist = -bbox_plane_dist;
|
|
|
|
if (bbox_plane_dist < 0.f) {
|
|
// Fast conservative estimate by max distance from bbox center
|
|
// TODO is enumerating all points or finding a closest one is better/faster?
|
|
const float size_x = minmax[0] - minmax[3];
|
|
const float size_y = minmax[1] - minmax[4];
|
|
const float size_z = minmax[2] - minmax[5];
|
|
const float plane_dist_guard_sqr = (size_x * size_x + size_y * size_y + size_z * size_z) * .25f;
|
|
|
|
// Check whether this bbox is completely behind the surface
|
|
if (bbox_plane_dist*bbox_plane_dist > plane_dist_guard_sqr)
|
|
retval = false;
|
|
}
|
|
|
|
//APROF_SCOPE_END(canSurfaceLightAffectAABB);
|
|
|
|
return retval;
|
|
}
|
|
|
|
#if 0
|
|
void VK_LightsAddEmissiveSurface( const struct vk_render_geometry_s *geom, const matrix3x4 *transform_row, qboolean static_map ) {
|
|
APROF_SCOPE_BEGIN_EARLY(emissive_surface);
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
const int texture_num = geom->texture; // Animated texture
|
|
vk_emissive_surface_t *retval = NULL;
|
|
vec3_t emissive_color = {0};
|
|
|
|
ASSERT(texture_num >= 0);
|
|
ASSERT(texture_num < MAX_TEXTURES);
|
|
|
|
// Only brush model surfaces are supported to be emissive. This is not _strictly_ necessary, but is a bit simpler.
|
|
if (!geom->surf)
|
|
goto fin; // TODO break? no surface means that model is not brush
|
|
|
|
// Find out whether this surface is emissive
|
|
{
|
|
const int surface_index = geom->surf - world->surfaces;
|
|
const xvk_patch_surface_t *psurf = g_map_entities.patch.surfaces ? g_map_entities.patch.surfaces + surface_index : NULL;
|
|
|
|
ASSERT(surface_index >= 0);
|
|
ASSERT(surface_index < world->numsurfaces);
|
|
|
|
if (psurf && psurf->flags & Patch_Surface_Emissive) {
|
|
VectorCopy(psurf->emissive, emissive_color);
|
|
} else if (geom->material == kXVkMaterialEmissive) {
|
|
VectorCopy(geom->emissive, emissive_color);
|
|
} else if (g_lights.map.emissive_textures[texture_num].set) {
|
|
VectorCopy(g_lights.map.emissive_textures[texture_num].emissive, emissive_color);
|
|
} else {
|
|
goto fin;
|
|
}
|
|
|
|
if (emissive_color[0] == 0 && emissive_color[1] == 0 && emissive_color[2] == 0) {
|
|
if (static_map) {
|
|
gEngine.Con_Reportf("Surface %d got zero emissive color, not adding as a light source\n", surface_index);
|
|
}
|
|
goto fin;
|
|
}
|
|
}
|
|
|
|
if (g_lights.num_polygons >= 256)
|
|
goto fin;
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
const vk_texture_t *tex = findTexture(texture_num);
|
|
ASSERT(tex);
|
|
gEngine.Con_Reportf("surface light %d: %s (%f %f %f)\n", g_lights.num_polygons, tex->name,
|
|
emissive_color[0],
|
|
emissive_color[1],
|
|
emissive_color[2]);
|
|
}
|
|
|
|
{
|
|
const vk_light_leaf_set_t *const leafs = static_map
|
|
? getMapLeafsAffectedByMapSurface( geom->surf )
|
|
: getMapLeafsAffectedByMovingSurface( geom->surf, transform_row );
|
|
vk_emissive_surface_t *esurf = g_lights.polygons + g_lights.num_polygons;
|
|
|
|
// Insert into emissive surfaces
|
|
esurf->kusok_index = geom->kusok_index;
|
|
VectorCopy(emissive_color, esurf->emissive);
|
|
Matrix3x4_Copy(esurf->transform, *transform_row);
|
|
|
|
clusterBitMapClear();
|
|
|
|
// Iterate through each visible/potentially affected leaf to get a range of grid cells
|
|
for (int i = 0; i < leafs->num; ++i) {
|
|
const mleaf_t *const leaf = world->leafs + leafs->leafs[i];
|
|
|
|
const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE);
|
|
|
|
const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
|
|
const qboolean not_visible = static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs);
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf(" adding leaf %d (%d of %d) min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n",
|
|
leaf->cluster, i, leafs->num,
|
|
min_x, min_y, min_z,
|
|
max_x, max_y, max_z,
|
|
(max_x - min_x) * (max_y - min_y) * (max_z - min_z)
|
|
);
|
|
}
|
|
|
|
if (not_visible)
|
|
continue;
|
|
|
|
for (int x = min_x; x < max_x; ++x)
|
|
for (int y = min_y; y < max_y; ++y)
|
|
for (int z = min_z; z < max_z; ++z) {
|
|
const int cell[3] = {
|
|
x - g_lights.map.grid_min_cell[0],
|
|
y - g_lights.map.grid_min_cell[1],
|
|
z - g_lights.map.grid_min_cell[2]
|
|
};
|
|
|
|
const int cell_index = R_LightCellIndex( cell );
|
|
if (cell_index < 0)
|
|
continue;
|
|
|
|
if (clusterBitMapCheckOrSet( cell_index )) {
|
|
const float minmaxs[6] = {
|
|
x * LIGHT_GRID_CELL_SIZE,
|
|
y * LIGHT_GRID_CELL_SIZE,
|
|
z * LIGHT_GRID_CELL_SIZE,
|
|
(x+1) * LIGHT_GRID_CELL_SIZE,
|
|
(y+1) * LIGHT_GRID_CELL_SIZE,
|
|
(z+1) * LIGHT_GRID_CELL_SIZE,
|
|
};
|
|
|
|
if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs))
|
|
continue;
|
|
|
|
if (!addSurfaceLightToCell(cell_index, g_lights.num_polygons)) {
|
|
ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of emissive surfaces slots",
|
|
cell[0], cell[1], cell[2], cell_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
++g_lights.num_polygons;
|
|
retval = esurf;
|
|
}
|
|
|
|
fin:
|
|
APROF_SCOPE_END(emissive_surface);
|
|
}
|
|
#endif
|
|
|
|
static void addLightIndexToleaf( const mleaf_t *leaf, int index ) {
|
|
const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE);
|
|
|
|
const int max_x = ceilf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE);
|
|
const int max_y = ceilf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE);
|
|
const int max_z = ceilf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE);
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf(" adding leaf %d min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n",
|
|
leaf->cluster,
|
|
min_x, min_y, min_z,
|
|
max_x, max_y, max_z,
|
|
(max_x - min_x) * (max_y - min_y) * (max_z - min_z)
|
|
);
|
|
}
|
|
|
|
for (int x = min_x; x < max_x; ++x)
|
|
for (int y = min_y; y < max_y; ++y)
|
|
for (int z = min_z; z < max_z; ++z) {
|
|
const int cell[3] = {
|
|
x - g_lights.map.grid_min_cell[0],
|
|
y - g_lights.map.grid_min_cell[1],
|
|
z - g_lights.map.grid_min_cell[2]
|
|
};
|
|
|
|
const int cell_index = R_LightCellIndex( cell );
|
|
if (cell_index < 0)
|
|
continue;
|
|
|
|
if (clusterBitMapCheckOrSet( cell_index )) {
|
|
if (!addLightToCell(cell_index, index)) {
|
|
ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of light slots",
|
|
cell[0], cell[1], cell[2], cell_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void addPointLightToClusters( int index ) {
|
|
vk_point_light_t *const light = g_lights.point_lights + index;
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
const mleaf_t* leaf = gEngine.Mod_PointInLeaf(light->origin, world->nodes);
|
|
const vk_light_leaf_set_t *const leafs = (vk_light_leaf_set_t*)&g_lights_bsp.accum.count;
|
|
|
|
leafAccumPrepare();
|
|
leafAccumAddPotentiallyVisibleFromLeaf( world, leaf, false);
|
|
leafAccumFinalize();
|
|
|
|
clusterBitMapClear();
|
|
for (int i = 0; i < leafs->num; ++i) {
|
|
const mleaf_t *const leaf = world->leafs + leafs->leafs[i];
|
|
addLightIndexToleaf( leaf, index );
|
|
}
|
|
}
|
|
|
|
static void addPointLightToAllClusters( int index ) {
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
|
|
clusterBitMapClear();
|
|
for (int i = 1; i <= world->numleafs; ++i) {
|
|
const mleaf_t *const leaf = world->leafs + i;
|
|
addLightIndexToleaf( leaf, index );
|
|
}
|
|
}
|
|
|
|
static int addPointLight( const vec3_t origin, const vec3_t color, float radius, int lightstyle, float hack_attenuation ) {
|
|
const int index = g_lights.num_point_lights;
|
|
vk_point_light_t *const plight = g_lights.point_lights + index;
|
|
|
|
if (g_lights.num_point_lights >= MAX_POINT_LIGHTS) {
|
|
ERROR_THROTTLED(10, "Too many lights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS);
|
|
return -1;
|
|
}
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Printf("point light %d: origin=(%f %f %f) R=%f color=(%f %f %f)\n", index,
|
|
origin[0], origin[1], origin[2], radius,
|
|
color[0], color[1], color[2]);
|
|
}
|
|
|
|
*plight = (vk_point_light_t){0};
|
|
VectorCopy(origin, plight->origin);
|
|
plight->radius = radius;
|
|
|
|
VectorScale(color, hack_attenuation, plight->base_color);
|
|
VectorCopy(plight->base_color, plight->color);
|
|
plight->lightstyle = lightstyle;
|
|
|
|
// Omnidirectional light
|
|
plight->stopdot = plight->stopdot2 = -1.f;
|
|
VectorSet(plight->dir, 0, 0, 0);
|
|
|
|
addPointLightToClusters( index );
|
|
g_lights.num_point_lights++;
|
|
return index;
|
|
}
|
|
|
|
static int addSpotLight( const vk_light_entity_t *le, float radius, int lightstyle, float hack_attenuation, qboolean all_clusters ) {
|
|
const int index = g_lights.num_point_lights;
|
|
vk_point_light_t *const plight = g_lights.point_lights + index;
|
|
|
|
if (g_lights.num_point_lights >= MAX_POINT_LIGHTS) {
|
|
ERROR_THROTTLED(10, "Too many lights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS);
|
|
return -1;
|
|
}
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Printf("%s light %d: origin=(%f %f %f) color=(%f %f %f) dir=(%f %f %f)\n",
|
|
le->type == LightTypeEnvironment ? "environment" : "spot",
|
|
index,
|
|
le->origin[0], le->origin[1], le->origin[2],
|
|
le->color[0], le->color[1], le->color[2],
|
|
le->dir[0], le->dir[1], le->dir[2]);
|
|
}
|
|
|
|
*plight = (vk_point_light_t){0};
|
|
VectorCopy(le->origin, plight->origin);
|
|
plight->radius = radius;
|
|
|
|
VectorScale(le->color, hack_attenuation, plight->base_color);
|
|
VectorCopy(plight->base_color, plight->color);
|
|
plight->lightstyle = lightstyle;
|
|
|
|
VectorCopy(le->dir, plight->dir);
|
|
plight->stopdot = le->stopdot;
|
|
plight->stopdot2 = le->stopdot2;
|
|
|
|
if (le->type == LightTypeEnvironment)
|
|
plight->flags = LightFlag_Environment;
|
|
|
|
if (all_clusters)
|
|
addPointLightToAllClusters( index );
|
|
else
|
|
addPointLightToClusters( index );
|
|
|
|
g_lights.num_point_lights++;
|
|
return index;
|
|
}
|
|
|
|
void R_LightAddFlashlight(const struct cl_entity_s *ent, qboolean local_player ) {
|
|
// parameters
|
|
const float hack_attenuation = 0.1;
|
|
float radius = 1.0;
|
|
// TODO: better tune it
|
|
const float _cone = 10.0;
|
|
const float _cone2 = 30.0;
|
|
const vec3_t light_color = {255, 255, 192};
|
|
float light_intensity = 300;
|
|
|
|
vec3_t color;
|
|
vec3_t origin;
|
|
vec3_t angles;
|
|
vk_light_entity_t le;
|
|
|
|
float thirdperson_offset = 25;
|
|
vec3_t forward, view_ofs;
|
|
vec3_t vecSrc, vecEnd;
|
|
pmtrace_t *trace;
|
|
if( local_player )
|
|
{
|
|
// local player case
|
|
// position
|
|
if (gEngine.EngineGetParm(PARM_THIRDPERSON, 0)) { // thirdperson
|
|
AngleVectors( g_camera.viewangles, forward, NULL, NULL );
|
|
view_ofs[0] = view_ofs[1] = 0.0f;
|
|
if( ent->curstate.usehull == 1 ) {
|
|
view_ofs[2] = 12.0f; // VEC_DUCK_VIEW;
|
|
} else {
|
|
view_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT
|
|
}
|
|
VectorAdd( ent->origin, view_ofs, vecSrc );
|
|
VectorMA( vecSrc, thirdperson_offset, forward, vecEnd );
|
|
trace = gEngine.EV_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );
|
|
VectorCopy( trace->endpos, origin );
|
|
VectorCopy( forward, le.dir);
|
|
} else { // firstperson
|
|
// based on https://github.com/SNMetamorph/PrimeXT/blob/0869b1abbddd13c1229769d8cd71941610be0bf3/client/flashlight.cpp#L35
|
|
origin[0] = g_camera.vieworg[0] + (g_camera.vright[0] * (-4.0f)) + (g_camera.vforward[0] * 14.0); // forward-back
|
|
origin[1] = g_camera.vieworg[1] + (g_camera.vright[1] * (-4.0f)) + (g_camera.vforward[1] * 14.0); // left-right
|
|
origin[2] = g_camera.vieworg[2] + (g_camera.vright[2] * (-4.0f)) + (g_camera.vforward[2] * 14.0); // up-down
|
|
origin[2] += 2.0f;
|
|
VectorCopy(g_camera.vforward, le.dir);
|
|
}
|
|
}
|
|
else // non-local player case
|
|
{
|
|
thirdperson_offset = 10;
|
|
radius = 10;
|
|
light_intensity = 60;
|
|
|
|
VectorCopy( ent->angles, angles );
|
|
// NOTE: pitch divided by 3.0 twice. So we need apply 3^2 = 9
|
|
angles[PITCH] = ent->curstate.angles[PITCH] * 9.0f;
|
|
angles[YAW] = ent->angles[YAW];
|
|
angles[ROLL] = 0.0f; // roll not used
|
|
|
|
AngleVectors( angles, angles, NULL, NULL );
|
|
view_ofs[0] = view_ofs[1] = 0.0f;
|
|
if( ent->curstate.usehull == 1 ) {
|
|
view_ofs[2] = 12.0f; // VEC_DUCK_VIEW;
|
|
} else {
|
|
view_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT
|
|
}
|
|
VectorAdd( ent->origin, view_ofs, vecSrc );
|
|
VectorMA( vecSrc, thirdperson_offset, angles, vecEnd );
|
|
trace = gEngine.EV_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX );
|
|
VectorCopy( trace->endpos, origin );
|
|
VectorCopy( angles, le.dir );
|
|
}
|
|
|
|
VectorCopy(origin, le.origin);
|
|
|
|
// prepare colors by parseEntPropRgbav
|
|
VectorScale(light_color, light_intensity / 255.0f, color);
|
|
|
|
// convert colors by weirdGoldsrcLightScaling
|
|
float l1 = Q_max(color[0], Q_max(color[1], color[2]));
|
|
l1 = l1 * l1 / 10;
|
|
VectorScale(color, l1, le.color);
|
|
|
|
// convert stopdots by parseStopDot
|
|
le.stopdot = cosf(_cone * M_PI / 180.f);
|
|
le.stopdot2 = cosf(_cone2 * M_PI / 180.f);
|
|
|
|
/*
|
|
gEngine.Con_Printf("flashlight: origin=(%f %f %f) color=(%f %f %f) dir=(%f %f %f)\n",
|
|
le.origin[0], le.origin[1], le.origin[2],
|
|
le.color[0], le.color[1], le.color[2],
|
|
le.dir[0], le.dir[1], le.dir[2]);
|
|
*/
|
|
|
|
addSpotLight(&le, radius, 0, hack_attenuation, false);
|
|
}
|
|
|
|
static float sphereSolidAngleFromDistDiv2Pi(float r, float d) {
|
|
return 1. - sqrt(d*d - r*r)/d;
|
|
}
|
|
|
|
static void addDlight( const dlight_t *dlight ) {
|
|
const float k_light_radius = 2.f;
|
|
const float k_threshold = 2.f;
|
|
|
|
float max_comp;
|
|
vec3_t color;
|
|
int index;
|
|
float scaler;
|
|
|
|
max_comp = Q_max(dlight->color.r, Q_max(dlight->color.g, dlight->color.b));
|
|
if (max_comp < k_threshold || dlight->radius <= k_light_radius)
|
|
return;
|
|
|
|
scaler = k_threshold / (max_comp * sphereSolidAngleFromDistDiv2Pi(k_light_radius, dlight->radius));
|
|
|
|
VectorSet(
|
|
color,
|
|
dlight->color.r * scaler,
|
|
dlight->color.g * scaler,
|
|
dlight->color.b * scaler);
|
|
|
|
index = addPointLight(dlight->origin, color, k_light_radius, -1, 1.f);
|
|
if (index < 0)
|
|
return;
|
|
}
|
|
|
|
static void processStaticPointLights( void ) {
|
|
APROF_SCOPE_BEGIN_EARLY(static_lights);
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
ASSERT(world);
|
|
|
|
g_lights.num_point_lights = 0;
|
|
for (int i = 0; i < g_map_entities.num_lights; ++i) {
|
|
const vk_light_entity_t *le = g_map_entities.lights + i;
|
|
const float default_radius = 2.f; // FIXME tune
|
|
const float hack_attenuation = .1f; // FIXME tune
|
|
const float hack_attenuation_spot = .1f; // FIXME tune
|
|
const float radius = le->radius > 0.f ? le->radius : default_radius;
|
|
int index;
|
|
|
|
switch (le->type) {
|
|
case LightTypePoint:
|
|
index = addPointLight(le->origin, le->color, radius, le->style, hack_attenuation);
|
|
break;
|
|
|
|
case LightTypeSpot:
|
|
case LightTypeEnvironment:
|
|
index = addSpotLight(le, radius, le->style, hack_attenuation_spot, i == g_map_entities.single_environment_index);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(!"Unexpected light type");
|
|
continue;
|
|
}
|
|
|
|
if (index < 0)
|
|
break;
|
|
}
|
|
APROF_SCOPE_END(static_lights);
|
|
}
|
|
|
|
void RT_LightsNewMapEnd( const struct model_s *map ) {
|
|
//debug_dump_lights.enabled = true;
|
|
|
|
processStaticPointLights();
|
|
|
|
#if 0
|
|
// Load static map model
|
|
{
|
|
matrix3x4 xform;
|
|
const vk_brush_model_t *const bmodel = map->cache.data;
|
|
ASSERT(bmodel);
|
|
Matrix3x4_LoadIdentity(xform);
|
|
|
|
for (int i = 0; i < bmodel->render_model.num_geometries; ++i) {
|
|
const vk_render_geometry_t *geom = bmodel->render_model.geometries + i;
|
|
VK_LightsAddEmissiveSurface( geom, &xform, true );
|
|
// TODO how to differentiate between this and non-emissive gEngine.Con_Printf(S_ERROR "Ran out of surface light slots, geom %d of %d\n", i, bmodel->render_model.num_geometries);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Fix static counts
|
|
{
|
|
g_lights.num_static.polygons = g_lights.num_polygons;
|
|
g_lights.num_static.point_lights = g_lights.num_point_lights;
|
|
g_lights.num_static.polygon_vertices = g_lights.num_polygon_vertices;
|
|
|
|
for (int i = 0; i < g_lights.map.grid_cells; ++i) {
|
|
vk_lights_cell_t *const cell = g_lights.cells + i;
|
|
cell->num_static.point_lights = cell->num_point_lights;
|
|
cell->num_static.polygons = cell->num_polygons;
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean RT_GetEmissiveForTexture( vec3_t out, int texture_id ) {
|
|
ASSERT(texture_id >= 0);
|
|
ASSERT(texture_id < MAX_TEXTURES);
|
|
|
|
{
|
|
vk_emissive_texture_t *const etex = g_lights.map.emissive_textures + texture_id;
|
|
if (etex->set) {
|
|
VectorCopy(etex->emissive, out);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VK_LightsFrameFinalize( void ) {
|
|
APROF_SCOPE_BEGIN_EARLY(finalize);
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
|
|
if (g_lights.num_polygons > UINT8_MAX) {
|
|
ERROR_THROTTLED(10, "Too many emissive surfaces found: %d; some areas will be dark", g_lights.num_polygons);
|
|
g_lights.num_polygons = UINT8_MAX;
|
|
}
|
|
|
|
/* for (int i = 0; i < MAX_ELIGHTS; ++i) { */
|
|
/* const dlight_t *dlight = gEngine.GetEntityLight(i); */
|
|
/* if (!addDlight(dlight)) { */
|
|
/* ERROR_THROTTLED(10,"Too many elights, MAX_POINT_LIGHTS=%d", MAX_POINT_LIGHTS); */
|
|
/* break; */
|
|
/* } */
|
|
/* } */
|
|
|
|
for (int i = 0; i < g_lights.num_point_lights; ++i) {
|
|
vk_point_light_t *const light = g_lights.point_lights + i;
|
|
if (light->lightstyle < 0 || light->lightstyle >= MAX_LIGHTSTYLES)
|
|
continue;
|
|
|
|
{
|
|
const float scale = g_lightmap.lightstylevalue[light->lightstyle] / 255.f;
|
|
VectorScale(light->base_color, scale, light->color);
|
|
}
|
|
}
|
|
|
|
APROF_SCOPE_BEGIN(dlights);
|
|
for (int i = 0; i < MAX_DLIGHTS; ++i) {
|
|
const dlight_t *dlight = gEngine.GetDynamicLight(i);
|
|
if( !dlight || dlight->die < gpGlobals->time || !dlight->radius )
|
|
continue;
|
|
addDlight(dlight);
|
|
}
|
|
APROF_SCOPE_END(dlights);
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
#if 0
|
|
// Print light grid stats
|
|
gEngine.Con_Reportf("Emissive surfaces found: %d\n", g_lights.num_polygons);
|
|
|
|
{
|
|
#define GROUPSIZE 4
|
|
int histogram[1 + (MAX_VISIBLE_SURFACE_LIGHTS + GROUPSIZE - 1) / GROUPSIZE] = {0};
|
|
for (int i = 0; i < g_lights.map.grid_cells; ++i) {
|
|
const vk_lights_cell_t *cluster = g_lights.cells + i;
|
|
const int hist_index = cluster->num_polygons ? 1 + cluster->num_polygons / GROUPSIZE : 0;
|
|
histogram[hist_index]++;
|
|
}
|
|
|
|
gEngine.Con_Reportf("Built %d light clusters. Stats:\n", g_lights.map.grid_cells);
|
|
gEngine.Con_Reportf(" 0: %d\n", histogram[0]);
|
|
for (int i = 1; i < ARRAYSIZE(histogram); ++i)
|
|
gEngine.Con_Reportf(" %d-%d: %d\n",
|
|
(i - 1) * GROUPSIZE,
|
|
i * GROUPSIZE - 1,
|
|
histogram[i]);
|
|
}
|
|
|
|
{
|
|
int num_clusters_with_lights_in_range = 0;
|
|
for (int i = 0; i < g_lights.map.grid_cells; ++i) {
|
|
const vk_lights_cell_t *cluster = g_lights.cells + i;
|
|
if (cluster->num_polygons > 0) {
|
|
gEngine.Con_Reportf(" cluster %d: polygons=%d\n", i, cluster->num_polygons);
|
|
}
|
|
|
|
for (int j = 0; j < cluster->num_polygons; ++j) {
|
|
const int index = cluster->polygons[j];
|
|
if (index >= vk_rtx_light_begin->value && index < vk_rtx_light_end->value) {
|
|
++num_clusters_with_lights_in_range;
|
|
}
|
|
}
|
|
}
|
|
|
|
gEngine.Con_Reportf("Clusters with filtered lights: %d\n", num_clusters_with_lights_in_range);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
debug_dump_lights.enabled = false;
|
|
APROF_SCOPE_END(finalize);
|
|
}
|
|
|
|
static void addPolygonLeafSetToClusters(const vk_light_leaf_set_t *leafs, int poly_index) {
|
|
const model_t* const world = gEngine.pfnGetModelByIndex( 1 );
|
|
|
|
// FIXME this shouldn't happen in prod
|
|
if (!leafs)
|
|
return;
|
|
|
|
clusterBitMapClear();
|
|
|
|
// Iterate through each visible/potentially affected leaf to get a range of grid cells
|
|
for (int i = 0; i < leafs->num; ++i) {
|
|
const mleaf_t *const leaf = world->leafs + leafs->leafs[i];
|
|
|
|
const int min_x = floorf(leaf->minmaxs[0] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_y = floorf(leaf->minmaxs[1] / LIGHT_GRID_CELL_SIZE);
|
|
const int min_z = floorf(leaf->minmaxs[2] / LIGHT_GRID_CELL_SIZE);
|
|
|
|
const int max_x = floorf(leaf->minmaxs[3] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
const int max_y = floorf(leaf->minmaxs[4] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
const int max_z = floorf(leaf->minmaxs[5] / LIGHT_GRID_CELL_SIZE) + 1;
|
|
|
|
const qboolean not_visible = false; //TODO static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, leaf->minmaxs);
|
|
|
|
if (debug_dump_lights.enabled) {
|
|
gEngine.Con_Reportf(" adding leaf %d (%d of %d) min=(%d, %d, %d), max=(%d, %d, %d) total=%d\n",
|
|
leaf->cluster, i, leafs->num,
|
|
min_x, min_y, min_z,
|
|
max_x, max_y, max_z,
|
|
(max_x - min_x) * (max_y - min_y) * (max_z - min_z)
|
|
);
|
|
}
|
|
|
|
if (not_visible)
|
|
continue;
|
|
|
|
for (int x = min_x; x < max_x; ++x)
|
|
for (int y = min_y; y < max_y; ++y)
|
|
for (int z = min_z; z < max_z; ++z) {
|
|
const int cell[3] = {
|
|
x - g_lights.map.grid_min_cell[0],
|
|
y - g_lights.map.grid_min_cell[1],
|
|
z - g_lights.map.grid_min_cell[2]
|
|
};
|
|
|
|
const int cell_index = R_LightCellIndex( cell );
|
|
if (cell_index < 0)
|
|
continue;
|
|
|
|
if (clusterBitMapCheckOrSet( cell_index )) {
|
|
const float minmaxs[6] = {
|
|
x * LIGHT_GRID_CELL_SIZE,
|
|
y * LIGHT_GRID_CELL_SIZE,
|
|
z * LIGHT_GRID_CELL_SIZE,
|
|
(x+1) * LIGHT_GRID_CELL_SIZE,
|
|
(y+1) * LIGHT_GRID_CELL_SIZE,
|
|
(z+1) * LIGHT_GRID_CELL_SIZE,
|
|
};
|
|
|
|
/* TODO if (static_map && !canSurfaceLightAffectAABB(world, geom->surf, esurf->emissive, minmaxs)) */
|
|
/* continue; */
|
|
|
|
if (!addSurfaceLightToCell(cell_index, poly_index)) {
|
|
ERROR_THROTTLED(10, "Cluster %d,%d,%d(%d) ran out of polygon light slots",
|
|
cell[0], cell[1], cell[2], cell_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int RT_LightAddPolygon(const rt_light_add_polygon_t *addpoly) {
|
|
if (g_lights.num_polygons == MAX_SURFACE_LIGHTS) {
|
|
gEngine.Con_Printf(S_ERROR "Max number of polygon lights %d reached\n", MAX_SURFACE_LIGHTS);
|
|
return -1;
|
|
}
|
|
|
|
ASSERT(addpoly->num_vertices > 2);
|
|
ASSERT(addpoly->num_vertices < 8);
|
|
ASSERT(g_lights.num_polygon_vertices + addpoly->num_vertices <= COUNTOF(g_lights.polygon_vertices));
|
|
|
|
{
|
|
rt_light_polygon_t *const poly = g_lights.polygons + g_lights.num_polygons;
|
|
vec3_t normal;
|
|
|
|
poly->vertices.offset = g_lights.num_polygon_vertices;
|
|
poly->vertices.count = addpoly->num_vertices;
|
|
|
|
VectorCopy(addpoly->emissive, poly->emissive);
|
|
VectorSet(poly->center, 0, 0, 0);
|
|
VectorSet(normal, 0, 0, 0);
|
|
|
|
for (int i = 0; i < addpoly->num_vertices; ++i) {
|
|
VectorCopy(addpoly->vertices[i], g_lights.polygon_vertices[poly->vertices.offset + i]);
|
|
VectorAdd(addpoly->vertices[i], poly->center, poly->center);
|
|
|
|
if (i > 1) {
|
|
vec3_t e[2], lnormal;
|
|
VectorSubtract(addpoly->vertices[i-0], addpoly->vertices[0], e[0]);
|
|
VectorSubtract(addpoly->vertices[i-1], addpoly->vertices[0], e[1]);
|
|
CrossProduct(e[0], e[1], lnormal);
|
|
VectorAdd(lnormal, normal, normal);
|
|
}
|
|
}
|
|
|
|
poly->area = VectorLength(normal);
|
|
VectorM(1.f / poly->area, normal, poly->plane);
|
|
poly->plane[3] = -DotProduct(addpoly->vertices[0], poly->plane);
|
|
|
|
VectorM(1.f / poly->vertices.count, poly->center, poly->center);
|
|
|
|
gEngine.Con_Reportf("added polygon light index=%d color=(%f, %f, %f) center=(%f, %f, %f) plane=(%f, %f, %f, %f) area=%f num_vertices=%d\n",
|
|
g_lights.num_polygons,
|
|
poly->emissive[0],
|
|
poly->emissive[1],
|
|
poly->emissive[2],
|
|
poly->center[0],
|
|
poly->center[1],
|
|
poly->center[2],
|
|
poly->plane[0],
|
|
poly->plane[1],
|
|
poly->plane[2],
|
|
poly->plane[3],
|
|
poly->area,
|
|
poly->vertices.count
|
|
);
|
|
|
|
{
|
|
const vk_light_leaf_set_t *const leafs = getMapLeafsAffectedByMapSurface(addpoly->surface);
|
|
addPolygonLeafSetToClusters(leafs, g_lights.num_polygons);
|
|
}
|
|
|
|
g_lights.num_polygon_vertices += addpoly->num_vertices;
|
|
return g_lights.num_polygons++;
|
|
}
|
|
}
|