diff --git a/include/light/light.hh b/include/light/light.hh index f3cb40fa..0bda894d 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -383,6 +383,7 @@ public: setting_int32 facestyles; setting_bool exportobj; setting_int32 lmshift; + setting_bool lightgrid; setting_func dirtdebug; setting_func bouncedebug; diff --git a/include/light/ltface.hh b/include/light/ltface.hh index 14a77130..63d3389b 100644 --- a/include/light/ltface.hh +++ b/include/light/ltface.hh @@ -57,4 +57,5 @@ void FinishLightmapSurface(const mbsp_t *bsp, lightsurf_t *lightsurf); void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, bspx_decoupled_lm_perface *facesup_decoupled, lightsurf_t *lightsurf, const faceextents_t &extents, const faceextents_t &output_extents); +qvec3d CalcLightgridAtPoint(const mbsp_t *bsp, const qvec3d &world_point); void ResetLtFace(); diff --git a/light/light.cc b/light/light.cc index 317f2861..1daf8dcb 100644 --- a/light/light.cc +++ b/light/light.cc @@ -35,6 +35,7 @@ #include #include #include +#include #if defined(HAVE_EMBREE) && defined(__SSE2__) #include @@ -314,6 +315,8 @@ light_settings::light_settings() exportobj{this, "exportobj", false, &output_group, "export an .OBJ for inspection"}, lmshift{this, "lmshift", 4, &output_group, "force a specified lmshift to be applied to the entire map; this is useful if you want to re-light a map with higher quality BSPX lighting without the sources. Will add the LMSHIFT lump to the BSP."}, + lightgrid{this, "lightgrid", false, &experimental_group, "experimental LIGHTGRID bspx lump"}, + dirtdebug{this, {"dirtdebug", "debugdirt"}, [&](source) { CheckNoDebugModeSet(); @@ -1042,6 +1045,51 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale) } } +static void LightGrid(bspdata_t *bspdata) +{ + if (!light_options.lightgrid.value()) + return; + + logging::funcheader(); + + auto &bsp = std::get(bspdata->bsp); + auto world_size = bsp.dmodels[0].maxs - bsp.dmodels[0].mins; + + constexpr int GRIDSIZE = 64; + + std::vector grid_result; + grid_result.resize(GRIDSIZE * GRIDSIZE * GRIDSIZE * 3); + + tbb::parallel_for(tbb::blocked_range3d(0, GRIDSIZE, 0, GRIDSIZE, 0, GRIDSIZE), + [&](const tbb::blocked_range3d& range) { + const auto &z_range = range.pages(); + const auto &y_range = range.rows(); + const auto &x_range = range.cols(); + + for (int z = z_range.begin(); z < z_range.end(); ++z) { + for (int y = y_range.begin(); y < y_range.end(); ++y) { + for (int x = x_range.begin(); x < x_range.end(); ++x) { + double max_grid_coord = GRIDSIZE - 1; + qvec3d world_point = bsp.dmodels[0].mins + (world_size * qvec3d{x / max_grid_coord, + y / max_grid_coord, + z / max_grid_coord}); + + qvec3d color = CalcLightgridAtPoint(&bsp, world_point); + + int sample_index = (GRIDSIZE * GRIDSIZE * z) + (GRIDSIZE * y) + x; + + grid_result[sample_index * 3] = clamp((int)color[0], 0, 255); + grid_result[sample_index * 3 + 1] = clamp((int)color[1], 0, 255); + grid_result[sample_index * 3 + 2] = clamp((int)color[2], 0, 255); + } + } + } + }); + + // non-final, experimental lump + bspdata->bspx.transfer("LIGHTGRID", std::move(grid_result)); +} + static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_t *bsp) { // always create the zero'ed array @@ -1676,6 +1724,8 @@ int light_main(int argc, const char **argv) LightWorld(&bspdata, light_options.lightmap_scale.isChanged()); + LightGrid(&bspdata); + // invalidate normals bspdata.bspx.entries.erase("FACENORMALS"); diff --git a/light/ltface.cc b/light/ltface.cc index e101bfeb..705b90bc 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -816,9 +816,15 @@ vec_t GetLightValue(const settings::worldspawn_keys &cfg, const light_t *entity, } static float GetLightValueWithAngle(const settings::worldspawn_keys &cfg, const light_t *entity, const qvec3d &surfnorm, - const qvec3d &surfpointToLightDir, float dist, bool twosided) + bool use_surfnorm, const qvec3d &surfpointToLightDir, float dist, bool twosided) { - vec_t angle = qv::dot(surfpointToLightDir, surfnorm); + vec_t angle; + if (use_surfnorm) { + angle = qv::dot(surfpointToLightDir, surfnorm); + } else { + angle = 1.0f; + } + if (entity->bleed.value() || twosided) { if (angle < 0) { angle = -angle; // ericw -- support "_bleed" option @@ -919,7 +925,7 @@ static bool LightFace_SampleMipTex( } static void GetLightContrib(const settings::worldspawn_keys &cfg, const light_t *entity, const qvec3d &surfnorm, - const qvec3d &surfpoint, bool twosided, qvec3f &color_out, qvec3d &surfpointToLightDir_out, + bool use_surfnorm, const qvec3d &surfpoint, bool twosided, qvec3f &color_out, qvec3d &surfpointToLightDir_out, qvec3d &normalmap_addition_out, float *dist_out) { float dist = GetDir(surfpoint, entity->origin.value(), surfpointToLightDir_out); @@ -929,7 +935,7 @@ static void GetLightContrib(const settings::worldspawn_keys &cfg, const light_t dist = 0.1f; surfpointToLightDir_out = {0, 0, 1}; } - const float add = GetLightValueWithAngle(cfg, entity, surfnorm, surfpointToLightDir_out, dist, twosided); + const float add = GetLightValueWithAngle(cfg, entity, surfnorm, use_surfnorm, surfpointToLightDir_out, dist, twosided); /* write out the final color */ if (entity->projectedmip) { @@ -1180,7 +1186,7 @@ static void LightFace_Entity( qvec3f color; qvec3d normalcontrib; - GetLightContrib(cfg, entity, surfnorm, surfpoint, lightsurf->twosided, color, surfpointToLightDir, + GetLightContrib(cfg, entity, surfnorm, true, surfpoint, lightsurf->twosided, color, surfpointToLightDir, normalcontrib, &surfpointToLightDist); const float occlusion = @@ -1243,6 +1249,45 @@ static void LightFace_Entity( } } +/** + * Calculates light at a given point from an entity + */ +static void LightPoint_Entity( + const mbsp_t *bsp, raystream_occlusion_t &rs, const light_t *entity, const qvec3d &surfpoint, qvec3d &result) +{ + if (entity->style.value() != 0) + return; // not doing style lightgrid for now + + rs.clearPushedRays(); + + qvec3d surfpointToLightDir; + float surfpointToLightDist; + qvec3f color; + qvec3d normalcontrib; + + GetLightContrib(light_options, entity, {0,0,0}, false, surfpoint, false, color, surfpointToLightDir, + normalcontrib, &surfpointToLightDist); + + /* Quick distance check first */ + if (fabs(LightSample_Brightness(color)) <= light_options.gate.value()) { + return; + } + + rs.pushRay(0, surfpoint, surfpointToLightDir, surfpointToLightDist, &color, &normalcontrib); + + rs.tracePushedRaysOcclusion(nullptr, CHANNEL_MASK_DEFAULT); + + // add result + const int N = rs.numPushedRays(); + for (int j = 0; j < N; j++) { + if (rs.getPushedRayOccluded(j)) { + continue; + } + + result += rs.getPushedRayColor(j); + } +} + /* * ============= * LightFace_Sky @@ -1356,6 +1401,49 @@ static void LightFace_Sky(const sun_t *sun, lightsurf_t *lightsurf, lightmapdict } } +static void LightPoint_Sky( + const mbsp_t *bsp, raystream_intersection_t &rs, const sun_t *sun, const qvec3d &surfpoint, qvec3d &result) +{ + if (sun->style != 0) + return; // not doing style lightgrid for now + + // FIXME: Normalized sun vector should be stored in the sun_t. Also clarify which way the vector points (towards or + // away..) + // FIXME: Much of this is copied/pasted from LightFace_Entity, should probably be merged + qvec3d incoming = qv::normalize(sun->sunvec); + + rs.clearPushedRays(); + + // only 1 ray + { + float value = sun->sunlight; + qvec3f color = sun->sunlight_color * (value / 255.0); + + /* Quick distance check first */ + if (fabs(LightSample_Brightness(color)) <= light_options.gate.value()) { + return; + } + + qvec3d normalcontrib{}; // unused + + rs.pushRay(0, surfpoint, incoming, MAX_SKY_DIST, &color, &normalcontrib); + } + + // We need to check if the first hit face is a sky face, so we need + // to test intersection (not occlusion) + rs.tracePushedRaysIntersection(nullptr, CHANNEL_MASK_DEFAULT); + + // add result + const int N = rs.numPushedRays(); + for (int j = 0; j < N; j++) { + if (rs.getPushedRayHitType(j) != hittype_t::SKY) { + continue; + } + + result += rs.getPushedRayColor(j); + } +} + // Mottle static int mod_round_to_neg_inf(int x, int y) { @@ -1660,14 +1748,14 @@ inline qvec3f SurfaceLight_ColorAtDist( // dir: vpl -> sample point direction // mxd. returns color in [0,255] inline qvec3f GetSurfaceLighting(const settings::worldspawn_keys &cfg, const surfacelight_t *vpl, const qvec3f &dir, - const float dist, const qvec3f &normal, const vec_t &standard_scale, const vec_t &sky_scale, const float &hotspot_clamp) + const float dist, const qvec3f &normal, bool use_normal, const vec_t &standard_scale, const vec_t &sky_scale, const float &hotspot_clamp) { qvec3f result; float dotProductFactor = 1.0f; float dp1 = qv::dot(vpl->surfnormal, dir); const qvec3f sp_vpl = dir * -1.0f; - float dp2 = qv::dot(sp_vpl, normal); + float dp2 = use_normal ? qv::dot(sp_vpl, normal) : 1.0f; if (!vpl->omnidirectional) { if (dp1 < -LIGHT_ANGLE_EPSILON) @@ -1756,7 +1844,7 @@ LightFace_SurfaceLight(const mbsp_t *bsp, lightsurf_t *lightsurf, lightmapdict_t else dir /= dist; - const qvec3f indirect = GetSurfaceLighting(cfg, &vpl, dir, dist, lightsurf_normal, standard_scale, sky_scale, hotspot_clamp); + const qvec3f indirect = GetSurfaceLighting(cfg, &vpl, dir, dist, lightsurf_normal, true, standard_scale, sky_scale, hotspot_clamp); if (!qv::gate(indirect, surflight_gate)) { // Each point contributes very little to the final result rs.pushRay(i, pos, dir, dist, &indirect); } @@ -1800,6 +1888,54 @@ LightFace_SurfaceLight(const mbsp_t *bsp, lightsurf_t *lightsurf, lightmapdict_t } } +static void // mxd +LightPoint_SurfaceLight(const mbsp_t *bsp, raystream_occlusion_t &rs, const std::vector &surface_lights, + const vec_t &standard_scale, const vec_t &sky_scale, const float &hotspot_clamp, const qvec3d &surfpoint, qvec3d &result) +{ + const settings::worldspawn_keys &cfg = light_options; + const float surflight_gate = 0.01f; + + for (const surfacelight_t &vpl : surface_lights) { + for (int c = 0; c < vpl.points.size(); c++) { + rs.clearPushedRays(); + + // 1 ray + { + qvec3f pos = vpl.points[c]; + qvec3f dir = surfpoint - pos; + float dist = qv::length(dir); + + if (dist == 0.0f) + dir = {0, 0, 1}; + else + dir /= dist; + + const qvec3f indirect = GetSurfaceLighting(cfg, &vpl, dir, dist, {0,0,0}, false, standard_scale, sky_scale, hotspot_clamp); + if (!qv::gate(indirect, surflight_gate)) { // Each point contributes very little to the final result + rs.pushRay(0, pos, dir, dist, &indirect); + } + } + + if (!rs.numPushedRays()) + continue; + + rs.tracePushedRaysOcclusion(nullptr, CHANNEL_MASK_DEFAULT); + + const int numrays = rs.numPushedRays(); + for (int j = 0; j < numrays; j++) { + if (rs.getPushedRayOccluded(j)) + continue; + + qvec3f indirect = rs.getPushedRayColor(j); + + Q_assert(!std::isnan(indirect[0])); + + result += indirect; + } + } + } +} + static void LightFace_OccludedDebug(lightsurf_t *lightsurf, lightmapdict_t *lightmaps) { Q_assert(light_options.debugmode == debugmodes::debugoccluded); @@ -3018,6 +3154,78 @@ void IndirectLightFace(const mbsp_t *bsp, lightsurf_t &lightsurf, const settings } } +// lightgrid + +qvec3d CalcLightgridAtPoint(const mbsp_t *bsp, const qvec3d &world_point) +{ + // TODO: use more than 1 ray for better performance + raystream_occlusion_t rs(1); + raystream_intersection_t rsi(1); + + auto &cfg = light_options; + + qvec3d result {0, 0, 0}; + + // from DirectLightFace + + /* + * The lighting procedure is: cast all positive lights, fix + * minlight levels, then cast all negative lights. Finally, we + * clamp any values that may have gone negative. + */ + + /* positive lights */ + for (const auto &entity : GetLights()) { + if (entity->getFormula() == LF_LOCALMIN) + continue; + if (entity->nostaticlight.value()) + continue; + if (entity->light.value() > 0) + LightPoint_Entity(bsp, rs, entity.get(), world_point, result); + } + + for (const sun_t &sun : GetSuns()) + if (sun.sunlight > 0) + LightPoint_Sky(bsp, rsi, &sun, world_point, result); + + // mxd. Add surface lights... + // FIXME: negative surface lights + LightPoint_SurfaceLight(bsp, rs, GetSurfaceLights(), cfg.surflightscale.value(), cfg.surflightskyscale.value(), 16.0f, world_point, result); + +#if 0 + // FIXME: port to lightgrid + float minlight = cfg.minlight.value(); + qvec3d minlight_color = cfg.minlight_color.value(); + + if (minlight) { + LightFace_Min(bsp, face, minlight_color, minlight, &lightsurf, lightmaps, 0); + } + + LightFace_LocalMin(bsp, face, &lightsurf, lightmaps); +#endif + + /* negative lights */ + for (const auto &entity : GetLights()) { + if (entity->getFormula() == LF_LOCALMIN) + continue; + if (entity->nostaticlight.value()) + continue; + if (entity->light.value() < 0) + LightPoint_Entity(bsp, rs, entity.get(), world_point, result); + } + for (const sun_t &sun : GetSuns()) + if (sun.sunlight < 0) + LightPoint_Sky(bsp, rsi, &sun, world_point, result); + + // from IndirectLightFace + + /* add bounce lighting */ + // note: scale here is just to keep it close-ish to the old code + LightPoint_SurfaceLight(bsp, rs, BounceLights(), cfg.bouncescale.value() * 0.5, cfg.bouncescale.value(), 128.0f, world_point, result); + + return result; +} + void ResetLtFace() { total_light_rays = 0;