diff --git a/ref_vk/shaders/ray.rgen b/ref_vk/shaders/ray.rgen index 23631cde..19155fee 100644 --- a/ref_vk/shaders/ray.rgen +++ b/ref_vk/shaders/ray.rgen @@ -34,7 +34,7 @@ layout(set = 0, binding = 2) uniform UBO { layout (set = 0, binding = 7/*, align=4*/) uniform UBOLights { Lights lights; }; struct LightCluster { - uint8_t num_dlights; + uint8_t num_point_lights; uint8_t num_emissive_surfaces; uint8_t point_lights[MAX_VISIBLE_POINT_LIGHTS]; uint8_t emissive_surfaces[MAX_VISIBLE_SURFACE_LIGHTS]; @@ -147,123 +147,133 @@ vec3 sampleSurfaceTriangle(vec3 color, vec3 view_dir, MaterialProperties materia return color; } -vec3 computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material) { +vec3 computePointLights(uint cluster_index, vec3 throughput, vec3 view_dir, MaterialProperties material) { vec3 C = vec3(0.); - const ivec3 light_cell = ivec3(floor(payload.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; - const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); - if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) { - C = vec3(1., 0., 0.); - } else { - // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; - // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); - // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); - // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; - //C = vec3(float(num_emissive_surfaces)); - //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); - //C += .3 * fract(vec3(light_cell) / 4.); + const uint num_point_lights = uint(light_grid.clusters[cluster_index].num_point_lights); + for (uint j = 0; j < num_point_lights; ++j) { + const uint i = uint(light_grid.clusters[cluster_index].point_lights[j]); - const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); - float sampling_light_scale = 1.; -#if 0 - const uint max_lights_per_frame = 4; - uint begin_i = 0, end_i = num_emissive_kusochki; - if (end_i > max_lights_per_frame) { - begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame); - end_i = begin_i + max_lights_per_frame; - sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame); - } - for (uint i = begin_i; i < end_i; ++i) { -#else + vec3 color = lights.point_lights[i].color_stopdot.rgb; + color *= throughput * payload.base_color; + if (dot(color,color) < color_culling_threshold) + continue; - for (uint i = 0; i < num_emissive_kusochki; ++i) { -#endif - const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); + const vec4 origin_r = lights.point_lights[i].origin_r; + const float stopdot = lights.point_lights[i].color_stopdot.a; + const vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; + const float stopdot2 = lights.point_lights[i].dir_stopdot2.a; - if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { - if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) - continue; - } + const vec3 light_dir = origin_r.xyz - payload.hit_pos_t.xyz; + const vec3 light_dir_norm = normalize(light_dir); + const float light_dot = dot(light_dir_norm, payload.normal); + if (light_dot < 1e-5) + continue; - const EmissiveKusok ek = lights.kusochki[index_into_emissive_kusochki]; - const uint emissive_kusok_index = lights.kusochki[index_into_emissive_kusochki].kusok_index; - const Kusok ekusok = kusochki[emissive_kusok_index]; - const vec3 emissive = ekusok.emissive; + const float spot_dot = -dot(light_dir_norm, dir); + if (spot_dot < stopdot2) + continue; - // TODO streamline matrices layouts - const mat4x3 emissive_transform = mat4x3( - vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), - vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), - vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), - vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) - ); - - const mat3 emissive_transform_normal = transpose(inverse(mat3(emissive_transform))); - - if (emissive_kusok_index == uint(payload.kusok_index)) - continue; - - const uint triangle_index = rand_range(ekusok.triangles); - C += sampling_light_scale * sampleSurfaceTriangle(throughput * payload.base_color * emissive, view_dir, material, emissive_transform, emissive_transform_normal, triangle_index, ekusok.index_offset, ekusok.vertex_offset); - } // for all emissive kusochki - - - for (uint i = 0; i < lights.num_point_lights; ++i) { - const vec4 origin_r = lights.point_lights[i].origin_r; - vec3 color = lights.point_lights[i].color_stopdot.rgb; - float stopdot = lights.point_lights[i].color_stopdot.a; - vec3 dir = lights.point_lights[i].dir_stopdot2.xyz; - float stopdot2 = lights.point_lights[i].dir_stopdot2.a; - - color *= throughput * payload.base_color; - // if (dot(color,color) < color_culling_threshold) - // continue; - - const vec3 light_dir = origin_r.xyz - payload.hit_pos_t.xyz; - const vec3 light_dir_norm = normalize(light_dir); - const float light_dot = dot(light_dir_norm, payload.normal); - if (light_dot < 1e-5) - continue; - - const float spot_dot = -dot(light_dir_norm, dir); - if (spot_dot < stopdot2) - continue; - - float spot_attenuation = 1.f; - if (spot_dot < stopdot) - spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2); + float spot_attenuation = 1.f; + if (spot_dot < stopdot) + spot_attenuation = (spot_dot - stopdot2) / (stopdot - stopdot2); #if 1 - const float d2 = dot(light_dir, light_dir); - const float r2 = origin_r.w * origin_r.w; - const float light_dist = sqrt(d2); - const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); - const float pdf = 1.f / (fdist * light_dot * spot_attenuation); + const float d2 = dot(light_dir, light_dir); + const float r2 = origin_r.w * origin_r.w; + const float light_dist = sqrt(d2); + const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); + const float pdf = 1.f / (fdist * light_dot * spot_attenuation); #else - const float d2 = dot(light_dir, light_dir); - //const float r2 = origin_r.w * origin_r.w; - const float light_dist = sqrt(d2); - //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); - //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); - const float fdist = (light_dist > 1.) ? 1.f / d2 : 1.f; // qrad workaround - const float pdf = 1.f / (fdist * light_dot * spot_attenuation); + const float d2 = dot(light_dir, light_dir); + //const float r2 = origin_r.w * origin_r.w; + const float light_dist = sqrt(d2); + //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); + //const float fdist = 2.f / (r2 + d2 + light_dist * sqrt(d2 + r2)); + const float fdist = (light_dist > 1.) ? 1.f / d2 : 1.f; // qrad workaround + const float pdf = 1.f / (fdist * light_dot * spot_attenuation); #endif - color /= pdf; - // if (dot(color,color) < color_culling_threshold) - // continue; + color /= pdf; + // if (dot(color,color) < color_culling_threshold) + // continue; - color *= evalCombinedBRDF(payload.normal, light_dir_norm, view_dir, material); - if (dot(color,color) < color_culling_threshold) - continue; + color *= evalCombinedBRDF(payload.normal, light_dir_norm, view_dir, material); + if (dot(color,color) < color_culling_threshold) + continue; - if (shadowed(payload.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge)) - continue; + if (shadowed(payload.hit_pos_t.xyz, light_dir_norm, light_dist + shadow_offset_fudge)) + continue; - C += color; - } // for all lights + C += color; + } // for all lights + + return C; +} + +vec3 computeLighting(vec3 throughput, vec3 view_dir, MaterialProperties material) { + const ivec3 light_cell = ivec3(floor(payload.hit_pos_t.xyz / LIGHT_GRID_CELL_SIZE)) - light_grid.grid_min; + const uint cluster_index = uint(dot(light_cell, ivec3(1, light_grid.grid_size.x, light_grid.grid_size.x * light_grid.grid_size.y))); + + if (any(greaterThanEqual(light_cell, light_grid.grid_size)) || cluster_index >= MAX_LIGHT_CLUSTERS) + return throughput * vec3(1., 0., 0.); + + vec3 C = vec3(0.); + + // const uint cluster_offset = cluster_index * LIGHT_CLUSTER_SIZE + HACK_OFFSET; + // const int num_dlights = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_DLIGHTS_OFFSET]); + // const int num_emissive_surfaces = int(light_grid.clusters_data[cluster_offset + LIGHT_CLUSTER_NUM_EMISSIVE_SURFACES_OFFSET]); + // const uint emissive_surfaces_offset = cluster_offset + LIGHT_CLUSTER_EMISSIVE_SURFACES_DATA_OFFSET; + //C = vec3(float(num_emissive_surfaces)); + + //C = vec3(float(int(light_grid.clusters[cluster_index].num_emissive_surfaces))); + //C += .3 * fract(vec3(light_cell) / 4.); + + const uint num_emissive_kusochki = uint(light_grid.clusters[cluster_index].num_emissive_surfaces); + float sampling_light_scale = 1.; +#if 0 + const uint max_lights_per_frame = 4; + uint begin_i = 0, end_i = num_emissive_kusochki; + if (end_i > max_lights_per_frame) { + begin_i = rand() % (num_emissive_kusochki - max_lights_per_frame); + end_i = begin_i + max_lights_per_frame; + sampling_light_scale = float(num_emissive_kusochki) / float(max_lights_per_frame); } + for (uint i = begin_i; i < end_i; ++i) { +#else + for (uint i = 0; i < num_emissive_kusochki; ++i) { +#endif + const uint index_into_emissive_kusochki = uint(light_grid.clusters[cluster_index].emissive_surfaces[i]); + + if (push_constants.debug_light_index_begin < push_constants.debug_light_index_end) { + if (index_into_emissive_kusochki < push_constants.debug_light_index_begin || index_into_emissive_kusochki >= push_constants.debug_light_index_end) + continue; + } + + const EmissiveKusok ek = lights.kusochki[index_into_emissive_kusochki]; + const uint emissive_kusok_index = lights.kusochki[index_into_emissive_kusochki].kusok_index; + const Kusok ekusok = kusochki[emissive_kusok_index]; + const vec3 emissive = ekusok.emissive; + + // TODO streamline matrices layouts + const mat4x3 emissive_transform = mat4x3( + vec3(ek.tx_row_x.x, ek.tx_row_y.x, ek.tx_row_z.x), + vec3(ek.tx_row_x.y, ek.tx_row_y.y, ek.tx_row_z.y), + vec3(ek.tx_row_x.z, ek.tx_row_y.z, ek.tx_row_z.z), + vec3(ek.tx_row_x.w, ek.tx_row_y.w, ek.tx_row_z.w) + ); + + const mat3 emissive_transform_normal = transpose(inverse(mat3(emissive_transform))); + + if (emissive_kusok_index == uint(payload.kusok_index)) + continue; + + const uint triangle_index = rand_range(ekusok.triangles); + C += sampling_light_scale * sampleSurfaceTriangle(throughput * payload.base_color * emissive, view_dir, material, emissive_transform, emissive_transform_normal, triangle_index, ekusok.index_offset, ekusok.vertex_offset); + } // for all emissive kusochki + + C += computePointLights(cluster_index, throughput, view_dir, material); return C; } diff --git a/ref_vk/vk_const.h b/ref_vk/vk_const.h index 319f1e68..114cb417 100644 --- a/ref_vk/vk_const.h +++ b/ref_vk/vk_const.h @@ -14,8 +14,10 @@ // indexed by uint8_t #define MAX_POINT_LIGHTS 255 +// indexed by uint8_t #define MAX_VISIBLE_POINT_LIGHTS 31 // indexed by uint8_t #define MAX_VISIBLE_SURFACE_LIGHTS 255 + #define MAX_LIGHT_CLUSTERS 262144 //131072 //32768 #define LIGHT_GRID_CELL_SIZE 128 diff --git a/ref_vk/vk_light.c b/ref_vk/vk_light.c index b623ba98..802cfd1f 100644 --- a/ref_vk/vk_light.c +++ b/ref_vk/vk_light.c @@ -479,13 +479,13 @@ static struct { } g_lights_bsp = {0}; -static void prepareLeafAccum( void ) { +static void leafAccumPrepare( void ) { memset(&g_lights_bsp.accum, 0, sizeof(g_lights_bsp.accum)); } #define LEAF_ADDED_BIT 0x8000000ul -static qboolean addLeafToAccum( uint16_t leaf_index ) { +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; @@ -496,9 +496,15 @@ static qboolean addLeafToAccum( uint16_t leaf_index ) { return true; } -static int addCompressedPVSLeafsToAccum(const model_t *const map, const byte *pvs, qboolean print_debug) { +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]; @@ -514,7 +520,7 @@ static int addCompressedPVSLeafsToAccum(const model_t *const map, const byte *pv if ((bits&1) == 0) continue; - if (addLeafToAccum( pvs_leaf_index + 1 )) { + if (leafAccumAdd( pvs_leaf_index + 1 )) { leafs_added++; if (print_debug) gEngine.Con_Reportf(" .%d", pvs_leaf_index + 1); @@ -536,7 +542,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { // Check if PVL hasn't been collected yet if (!smeta->potentially_visible_leafs) { int leafs_direct = 0, leafs_pvs = 0; - prepareLeafAccum(); + leafAccumPrepare(); // Enumerate all the map leafs and pick ones that have this surface referenced if (verbose_debug) @@ -550,7 +556,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { // FIXME split direct leafs marking from pvs propagation leafs_direct++; - if (addLeafToAccum( i )) { + if (leafAccumAdd( i )) { if (verbose_debug) gEngine.Con_Reportf(" %d", i); } else { @@ -560,17 +566,19 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMapSurface( const msurface_t *surf ) { } // Get all PVS leafs - leafs_pvs += addCompressedPVSLeafsToAccum(map, leaf->compressed_vis, verbose_debug); + leafs_pvs += leafAccumAddPotentiallyVisibleFromLeaf(map, leaf, verbose_debug); } } 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) + 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] & 0xffffu; + smeta->potentially_visible_leafs->leafs[i] = g_lights_bsp.accum.leafs[i]; } } @@ -622,7 +630,6 @@ static int computeCellIndex( const int light_cell[3] ) { 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]; } -//static qboolean dump_fatpvs = true; static qboolean have_surf = false; vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, const matrix3x4 *transform_row ) { @@ -655,7 +662,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, ); } - prepareLeafAccum(); + 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) @@ -671,7 +678,7 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, leafs_direct++; - if (addLeafToAccum( i + 1 )) { + if (leafAccumAdd( i + 1 )) { if (!have_surf || debug_dump_lights.enabled) gEngine.Con_Reportf(" %d", i + 1); } else { @@ -679,17 +686,12 @@ vk_light_leaf_set_t *getMapLeafsAffectedByMovingSurface( const msurface_t *surf, // but it really should be counted as direct leafs_pvs--; } - - // it seems that FatPVS already implements PVS walk leafs_pvs += addCompressedPVSLeafsToAccum(map, leaf->compressed_vis, debug_dump_lights.enabled); } if (!have_surf || debug_dump_lights.enabled) gEngine.Con_Reportf(" (sum=%d, direct=%d, pvs=%d)\n", g_lights_bsp.accum.count, leafs_direct, leafs_pvs); - //dump_fatpvs = false; - - for (int i = 0; i < g_lights_bsp.accum.count; ++i) - g_lights_bsp.accum.leafs[i] &= 0xffffu; + leafAccumFinalize(); // ...... oh no return (vk_light_leaf_set_t*)&g_lights_bsp.accum.count; @@ -745,10 +747,7 @@ static void prepareSurfacesLeafVisibilityCache( void ) { g_lights_bsp.surfaces[i].potentially_visible_leafs = NULL; } -extern void traverseBSP( void ); - -void VK_LightsNewMap( void ) -{ +void VK_LightsNewMap( void ) { const model_t *map = gEngine.pfnGetModelByIndex( 1 ); // 1. Determine map bounding box (and optimal grid size?) @@ -786,14 +785,12 @@ void VK_LightsNewMap( void ) clusterBitMapShutdown(); clusterBitMapInit(); - //traverseBSP(); prepareSurfacesLeafVisibilityCache(); VK_LightsLoadMapStaticLights(); } -void VK_LightsLoadMapStaticLights( void ) -{ +void VK_LightsLoadMapStaticLights( void ) { const model_t *map = gEngine.pfnGetModelByIndex( 1 ); parseStaticLightEntities(); @@ -832,6 +829,16 @@ static qboolean addSurfaceLightToCell( int cell_index, int emissive_surface_inde 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; + + 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; @@ -975,12 +982,57 @@ fin: return retval; } +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]; + + 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); + + 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 = computeCellIndex( cell ); + if (cell_index < 0) + continue; + + if (clusterBitMapCheckOrSet( cell_index )) { + if (!addLightToCell(cell_index, index)) { + // gEngine.Con_Printf(S_ERROR "Cluster %d,%d,%d(%d) ran out of light slots\n", + // cell[0], cell[1], cell[2], cell_index); + } + } + } + } +} + static int addPointLight( const vec3_t origin, const vec3_t color, float radius, 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) { - gEngine.Con_Printf(S_ERROR "Too many dlights, MAX_POINT_LIGHTS=%d\n", MAX_POINT_LIGHTS); + gEngine.Con_Printf(S_ERROR "Too many lights, MAX_POINT_LIGHTS=%d\n", MAX_POINT_LIGHTS); return -1; } @@ -999,6 +1051,7 @@ static int addPointLight( const vec3_t origin, const vec3_t color, float radius, plight->stopdot = plight->stopdot2 = -1.f; VectorSet(plight->dir, 0, 0, 0); + addPointLightToClusters( index ); g_lights.num_point_lights++; return index; } @@ -1008,7 +1061,7 @@ static int addSpotLight( const vk_light_entity_t *le, float radius, float hack_a vk_point_light_t *const plight = g_lights.point_lights + index; if (g_lights.num_point_lights >= MAX_POINT_LIGHTS) { - gEngine.Con_Printf(S_ERROR "Too many dlights, MAX_POINT_LIGHTS=%d\n", MAX_POINT_LIGHTS); + gEngine.Con_Printf(S_ERROR "Too many lights, MAX_POINT_LIGHTS=%d\n", MAX_POINT_LIGHTS); return -1; } @@ -1028,6 +1081,7 @@ static int addSpotLight( const vk_light_entity_t *le, float radius, float hack_a plight->stopdot = le->stopdot; plight->stopdot2 = le->stopdot2; + addPointLightToClusters( index ); g_lights.num_point_lights++; return index; }