From e3668987963407b7314f83b2765f581e213fa31f Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 18 Jun 2022 23:56:45 -0400 Subject: [PATCH] re-introduce vis culling, mainly for Q2/surface lit maps -novisapprox gone; replaced with -visapprox auto|none|vis|rays --- include/common/bspfile_generic.hh | 4 +- include/light/bounce.hh | 2 + include/light/entities.hh | 2 + include/light/light.hh | 21 ++- include/light/surflight.hh | 1 + include/light/trace.hh | 3 + light/bounce.cc | 4 +- light/entities.cc | 13 +- light/light.cc | 65 ++++++++- light/ltface.cc | 230 +++++++++++++++++++++++++++++- light/surflight.cc | 10 +- light/trace.cc | 29 ++++ vis/vis.cc | 8 +- 13 files changed, 372 insertions(+), 20 deletions(-) diff --git a/include/common/bspfile_generic.hh b/include/common/bspfile_generic.hh index ff5a9a46..e00f952e 100644 --- a/include/common/bspfile_generic.hh +++ b/include/common/bspfile_generic.hh @@ -69,7 +69,7 @@ struct mvis_t // fetch the bit offset of the specified cluster/vistype // relative to the start of the bits array - inline int32_t get_bit_offset(vistype_t type, size_t cluster) + inline int32_t get_bit_offset(vistype_t type, size_t cluster) const { return bit_offsets[cluster][type] - header_offset(); } @@ -451,7 +451,7 @@ struct mleaf_t { // bsp2_dleaf_t int32_t contents; - int32_t visofs; /* -1 = no visibility info */ + int32_t visofs; /* -1 = no visibility info; Q1 only! */ qvec3f mins; /* for frustum culling */ qvec3f maxs; uint32_t firstmarksurface; diff --git a/include/light/bounce.hh b/include/light/bounce.hh index 8540e252..a4573f3a 100644 --- a/include/light/bounce.hh +++ b/include/light/bounce.hh @@ -39,6 +39,8 @@ struct bouncelight_t qvec3f surfnormal; float area; + const mleaf_t *leaf; + /* estimated visible AABB culling */ aabb3d bounds; }; diff --git a/include/light/entities.hh b/include/light/entities.hh index 96279c0b..72c07630 100644 --- a/include/light/entities.hh +++ b/include/light/entities.hh @@ -66,6 +66,8 @@ public: bool generated = false; // if true, don't write to the bsp + const mleaf_t *leaf; + aabb3d bounds; settings::setting_scalar light{this, "light", DEFAULTLIGHTLEVEL}; diff --git a/include/light/light.hh b/include/light/light.hh index 34ea56ee..a8dbf210 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -141,6 +141,13 @@ struct lightsurf_t fully occluded. dirtgain/dirtscale are not applied yet */ float *occlusion; // new'ed array of numpoints + + /* + pvs for the entire light surface. generated by ORing together + the pvs at each of the sample points + */ + std::vector pvs; + bool skyvisible; /* for sphere culling */ qvec3d origin; @@ -249,6 +256,14 @@ public: modelinfo_t(const mbsp_t *b, const dmodelh2_t *m, float lmscale) : bsp{b}, model{m}, lightmapscale{lmscale} { } }; +enum class visapprox_t +{ + NONE, + AUTO, + VIS, + RAYS +}; + // // worldspawn keys / command-line settings // @@ -409,8 +424,8 @@ public: this, "lmscale", 0, &experimental_group, "change lightmap scale, vanilla engines only allow 16"}; setting_extra extra{ this, {"extra", "extra4"}, 1, &performance_group, "supersampling; 2x2 (extra) or 4x4 (extra4) respectively"}; - setting_bool novisapprox{ - this, "novisapprox", false, &debug_group, "disable approximate visibility culling of lights"}; + setting_enum visapprox{ + this, "visapprox", visapprox_t::AUTO, { { "auto", visapprox_t::AUTO }, { "none", visapprox_t::NONE }, { "vis", visapprox_t::VIS }, { "rays", visapprox_t::RAYS } }, &debug_group, "change approximate visibility algorithm. auto = choose default based on format. vis = use BSP vis data. rays = use sphere culling with fired rays"}; setting_func lit{this, "lit", [&]() { write_litfile |= lightfile::external; }, &output_group, "write .lit file"}; setting_func lit2{ this, "lit2", [&]() { write_litfile = lightfile::lit2; }, &experimental_group, "write .lit2 file"}; @@ -518,6 +533,8 @@ extern uint8_t *lux_filebase; extern std::vector extended_texinfo_flags; +bool Leaf_HasSky(const mbsp_t *bsp, const mleaf_t *leaf); + // public functions void SetGlobalSetting(std::string name, std::string value, bool cmdline); diff --git a/include/light/surflight.hh b/include/light/surflight.hh index db7b5edf..16fff491 100644 --- a/include/light/surflight.hh +++ b/include/light/surflight.hh @@ -32,6 +32,7 @@ struct surfacelight_t */ bool omnidirectional; std::vector points; + std::vector leaves; // Surface light settings... float intensity; // Surface light strength for each point diff --git a/include/light/trace.hh b/include/light/trace.hh index b445b037..b1bf6840 100644 --- a/include/light/trace.hh +++ b/include/light/trace.hh @@ -107,3 +107,6 @@ raystream_intersection_t *MakeIntersectionRayStream(int maxrays); raystream_occlusion_t *MakeOcclusionRayStream(int maxrays); void MakeTnodes(const mbsp_t *bsp); + +const mleaf_t *Light_PointInLeaf( const mbsp_t *bsp, const qvec3d &point ); +int Light_PointContents( const mbsp_t *bsp, const qvec3d &point ); diff --git a/light/bounce.cc b/light/bounce.cc index dd14e893..226c8eaf 100644 --- a/light/bounce.cc +++ b/light/bounce.cc @@ -144,7 +144,9 @@ static void AddBounceLight(const T &pos, const std::map &colorBySty l.area = area; l.bounds = qvec3d(0); - if (!options.novisapprox.value()) { + if (options.visapprox.value() == visapprox_t::VIS) { + l.leaf = Light_PointInLeaf(bsp, pos); + } else if (options.visapprox.value() == visapprox_t::RAYS) { l.bounds = EstimateVisibleBoundsAtPoint(pos); } diff --git a/light/entities.cc b/light/entities.cc index ede8d868..de5df1b4 100644 --- a/light/entities.cc +++ b/light/entities.cc @@ -945,6 +945,13 @@ void FixLightsOnFaces(const mbsp_t *bsp) } } +static void SetupLightLeafnums(const mbsp_t *bsp) +{ + for (auto &entity : all_lights) { + entity->leaf = Light_PointInLeaf(bsp, entity->origin.value()); + } +} + // Maps uniform random variables U and V in [0, 1] to uniformly distributed points on a sphere // from http://mathworld.wolfram.com/SpherePointPicking.html @@ -1025,8 +1032,9 @@ inline void EstimateLightAABB(const std::unique_ptr &light) void EstimateLightVisibility(void) { - if (options.novisapprox.value()) + if (options.visapprox.value() != visapprox_t::RAYS) { return; + } logging::print("--- EstimateLightVisibility ---\n"); @@ -1054,6 +1062,9 @@ void SetupLights(const settings::worldspawn_keys &cfg, const mbsp_t *bsp) SetupSkyDomes(cfg); FixLightsOnFaces(bsp); EstimateLightVisibility(); + if (options.visapprox.value() == visapprox_t::VIS) { + SetupLightLeafnums(bsp); + } logging::print("Final count: {} lights, {} suns in use.\n", all_lights.size(), all_suns.size()); diff --git a/light/light.cc b/light/light.cc index 9c895a0b..7b1e2f06 100644 --- a/light/light.cc +++ b/light/light.cc @@ -646,6 +646,59 @@ static void ExportObj(const fs::path &filename, const mbsp_t *bsp) } // obj +static vector> faceleafs; +static vector leafhassky; + +// index some stuff from the bsp +static void BuildPvsIndex(const mbsp_t *bsp) +{ + // build leafsForFace + faceleafs.resize(bsp->dfaces.size()); + for (size_t i = 0; i < bsp->dleafs.size(); i++) { + const mleaf_t &leaf = bsp->dleafs[i]; + for (int k = 0; k < leaf.nummarksurfaces; k++) { + const int facenum = bsp->dleaffaces[leaf.firstmarksurface + k]; + faceleafs.at(facenum).push_back(&leaf); + } + } + + // build leafhassky + leafhassky.resize(bsp->dleafs.size(), false); + for (size_t i = 0; i < bsp->dleafs.size(); i++) { + const bsp2_dleaf_t &leaf = bsp->dleafs[i]; + + // check for sky, contents check + if (bsp->loadversion->game->contents_are_sky({ leaf.contents })) { + leafhassky.at(i) = true; + continue; + } + + // search for sky faces + for (size_t k = 0; k < leaf.nummarksurfaces; k++) { + const mface_t &surf = bsp->dfaces[bsp->dleaffaces[leaf.firstmarksurface + k]]; + const mtexinfo_t &texinfo = bsp->texinfo[surf.texinfo]; + + if (bsp->loadversion->game->id == GAME_QUAKE_II) { + if (texinfo.flags.native & Q2_SURF_SKY) { + leafhassky.at(i) = true; + } + break; + } + + const char *texname = Face_TextureName(bsp, &surf); + if (!strncmp("sky", texname, 3)) { + leafhassky.at(i) = true; + break; + } + } + } +} + +bool Leaf_HasSky(const mbsp_t *bsp, const mleaf_t *leaf) +{ + const int leafnum = leaf - bsp->dleafs.data(); + return leafhassky.at(leafnum); +} // returns the face with a centroid nearest the given point. static const mface_t *Face_NearestCentroid(const mbsp_t *bsp, const qvec3f &point) @@ -914,9 +967,19 @@ int light_main(int argc, const char **argv) } } + // check vis approx type + if (options.visapprox.value() == visapprox_t::AUTO) { + if (bspdata.loadversion->game->id == GAME_QUAKE_II) { + options.visapprox.setValue(visapprox_t::VIS); + } else { + options.visapprox.setValue(visapprox_t::RAYS); + } + } + img::init_palette(bspdata.loadversion->game); img::load_textures(&bsp); - + + BuildPvsIndex(&bsp); LoadExtendedTexinfoFlags(source, &bsp); LoadEntities(options, &bsp); diff --git a/light/ltface.cc b/light/ltface.cc index 1d1d2c7e..f3822858 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -687,6 +687,179 @@ static void CalcPoints( } } +static size_t DecompressedVisSize(const mbsp_t *bsp) +{ + if (bsp->loadversion->game->id == GAME_QUAKE_II) { + return (bsp->dvis.bit_offsets.size() + 7) / 8; + } + + return (bsp->dmodels[0].visleafs + 7) / 8; +} + +// from DarkPlaces +static void Mod_Q1BSP_DecompressVis(const uint8_t *in, const uint8_t *inend, uint8_t *out, uint8_t *outend) +{ + int c; + uint8_t *outstart = out; + while (out < outend) + { + if (in == inend) + { + logging::print("Mod_Q1BSP_DecompressVis: input underrun (decompressed {} of {} output bytes)\n", (out - outstart), (outend - outstart)); + return; + } + + c = *in++; + if (c) { + *out++ = c; + continue; + } + + if (in == inend) + { + logging::print("Mod_Q1BSP_DecompressVis: input underrun (during zero-run) (decompressed {} of {} output bytes)\n", (out - outstart), (outend - outstart)); + return; + } + + for (c = *in++;c > 0;c--) + { + if (out == outend) + { + logging::print("Mod_Q1BSP_DecompressVis: output overrun (decompressed {} of {} output bytes)\n", (out - outstart), (outend - outstart)); + return; + } + *out++ = 0; + } + } +} + +static bool Mod_LeafPvs(const mbsp_t *bsp, const mleaf_t *leaf, uint8_t *out) +{ + const size_t num_pvsclusterbytes = DecompressedVisSize(bsp); + + // init to all visible + memset(out, 0xFF, num_pvsclusterbytes); + + if (bsp->loadversion->game->id == GAME_QUAKE_II) { + if (leaf->cluster < 0) { + return false; + } + + if (leaf->cluster >= bsp->dvis.bit_offsets.size() || + bsp->dvis.get_bit_offset(VIS_PVS, leaf->cluster) >= bsp->dvis.bits.size()) { + logging::print("Mod_LeafPvs: invalid visofs for cluster {}\n", leaf->cluster); + return false; + } + + Mod_Q1BSP_DecompressVis(bsp->dvis.bits.data() + bsp->dvis.get_bit_offset(VIS_PVS, leaf->cluster), + bsp->dvis.bits.data() + bsp->dvis.bits.size(), + out, + out + num_pvsclusterbytes); + } else { + if (leaf->visofs < 0) { + return false; + } + + const ptrdiff_t leafnum = (leaf - bsp->dleafs.data()); + + // this is confusing.. "visleaf numbers" are the leaf number minus 1. + // they also don't go as high, bsp->dmodels[0].visleafs instead of bsp->numleafs + const int visleaf = leafnum - 1; + if (visleaf < 0 || visleaf >= bsp->dmodels[0].visleafs) { + return false; + } + + if (leaf->visofs >= bsp->dvis.bits.size()) { + logging::print("Mod_LeafPvs: invalid visofs for leaf {}\n", leafnum); + return false; + } + + Mod_Q1BSP_DecompressVis(bsp->dvis.bits.data() + leaf->visofs, + bsp->dvis.bits.data() + bsp->dvis.bits.size(), + out, + out + num_pvsclusterbytes); + } + + return true; +} + +// returns true if pvs can see leaf +static bool Pvs_LeafVisible(const mbsp_t *bsp, const std::vector &pvs, const mleaf_t *leaf) +{ + if (bsp->loadversion->game->id == GAME_QUAKE_II) { + if (leaf->cluster < 0) { + return false; + } + + if (leaf->cluster >= bsp->dvis.bit_offsets.size() || + bsp->dvis.get_bit_offset(VIS_PVS, leaf->cluster) >= bsp->dvis.bits.size()) { + logging::print("Pvs_LeafVisible: invalid visofs for cluster {}\n", leaf->cluster); + return false; + } + + return !!(pvs[leaf->cluster>>3] & (1<<(leaf->cluster&7))); + } else { + const int leafnum = (leaf - bsp->dleafs.data()); + const int visleaf = leafnum - 1; + + if (visleaf < 0 || visleaf >= bsp->dmodels[0].visleafs) { + logging::print("WARNING: bad/empty vis data on leaf?"); + return false; + } + + return !!(pvs[visleaf>>3] & (1<<(visleaf&7))); + } +} + +static void CalcPvs(const mbsp_t *bsp, lightsurf_t *lightsurf) +{ + const int pvssize = DecompressedVisSize(bsp); + const mleaf_t *lastleaf = nullptr; + + // set defaults + lightsurf->pvs.clear(); + lightsurf->skyvisible = true; + + if (!bsp->dvis.bits.size()) { + return; + } + + // set lightsurf->pvs + uint8_t *pointpvs = (uint8_t *) alloca(pvssize); + lightsurf->pvs.resize(pvssize); + + for (int i = 0; i < lightsurf->numpoints; i++) { + const mleaf_t *leaf = Light_PointInLeaf (bsp, lightsurf->points[i]); + + /* most/all of the surface points are probably in the same leaf */ + if (leaf == lastleaf) + continue; + + lastleaf = leaf; + + /* copy the pvs for this leaf into pointpvs */ + Mod_LeafPvs(bsp, leaf, pointpvs); + + /* merge the pvs for this sample point into lightsurf->pvs */ + for (int j=0; jpvs[j] |= pointpvs[j]; + } + } + + // set lightsurf->skyvisible + lightsurf->skyvisible = false; + for (int i = 0; i < bsp->dleafs.size(); i++) { + const mleaf_t *leaf = &bsp->dleafs[i]; + if (Pvs_LeafVisible(bsp, lightsurf->pvs, leaf)) { + // we can see this leaf, search for sky faces in it + if (Leaf_HasSky(bsp, leaf)) { + lightsurf->skyvisible = true; + break; + } + } + } +} + static bool Lightsurf_Init( const modelinfo_t *modelinfo, const mface_t *face, const mbsp_t *bsp, lightsurf_t *lightsurf, facesup_t *facesup) { @@ -794,6 +967,12 @@ static bool Lightsurf_Init( lightsurf->intersection_stream = MakeIntersectionRayStream(lightsurf->numpoints); lightsurf->occlusion_stream = MakeOcclusionRayStream(lightsurf->numpoints); + + /* Setup vis data */ + if (options.visapprox.value() == visapprox_t::VIS) { + CalcPvs(bsp, lightsurf); + } + return true; } @@ -1204,7 +1383,7 @@ inline bool CullLight(const light_t *entity, const lightsurf_t *lightsurf) { const settings::worldspawn_keys &cfg = *lightsurf->cfg; - if (!options.novisapprox.value() && entity->bounds.disjoint(lightsurf->bounds, 0.001)) { + if (options.visapprox.value() == visapprox_t::RAYS && entity->bounds.disjoint(lightsurf->bounds, 0.001)) { return true; } @@ -1356,6 +1535,27 @@ std::map GetDirectLighting( return result; } +static bool VisCullEntity(const mbsp_t *bsp, const std::vector &pvs, const mleaf_t *entleaf) +{ + if (options.visapprox.value() != visapprox_t::VIS) { + return false; + } + if (pvs.empty()) { + return false; + } + if (entleaf == nullptr) { + return false; + } + + if (bsp->loadversion->game->contents_are_solid({ entleaf->contents }) || + bsp->loadversion->game->contents_are_sky({ entleaf->contents })) { + return false; + } + + return !Pvs_LeafVisible(bsp, pvs, entleaf); +} + + /* * ================ * LightFace_Entity @@ -1368,6 +1568,11 @@ static void LightFace_Entity( const modelinfo_t *modelinfo = lightsurf->modelinfo; const qplane3d *plane = &lightsurf->plane; + /* vis cull */ + if (VisCullEntity(bsp, lightsurf->pvs, entity->leaf)) { + return; + } + const vec_t planedist = plane->distance_to(entity->origin.value()); /* don't bother with lights behind the surface. @@ -1473,6 +1678,11 @@ static void LightFace_Entity( */ static void LightFace_Sky(const sun_t *sun, const lightsurf_t *lightsurf, lightmapdict_t *lightmaps) { + /* If vis data says we can't see any sky faces, skip raytracing */ + if (!lightsurf->skyvisible) { + return; + } + const settings::worldspawn_keys &cfg = *lightsurf->cfg; const modelinfo_t *modelinfo = lightsurf->modelinfo; const qplane3d *plane = &lightsurf->plane; @@ -1861,8 +2071,9 @@ inline bool BounceLight_SphereCull(const mbsp_t *bsp, const bouncelight_t *vpl, { const settings::worldspawn_keys &cfg = *lightsurf->cfg; - if (!options.novisapprox.value() && vpl->bounds.disjoint(lightsurf->bounds, 0.001)) + if (options.visapprox.value() == visapprox_t::RAYS && vpl->bounds.disjoint(lightsurf->bounds, 0.001)) { return true; + } const qvec3f dir = qvec3f(lightsurf->origin) - vpl->pos; // vpl -> sample point const float dist = qv::length(dir) + lightsurf->radius; @@ -1876,8 +2087,9 @@ inline bool BounceLight_SphereCull(const mbsp_t *bsp, const bouncelight_t *vpl, static bool // mxd SurfaceLight_SphereCull(const surfacelight_t *vpl, const lightsurf_t *lightsurf) { - if (!options.novisapprox.value() && vpl->bounds.disjoint(lightsurf->bounds, 0.001)) + if (options.visapprox.value() == visapprox_t::RAYS && vpl->bounds.disjoint(lightsurf->bounds, 0.001)) { return true; + } const settings::worldspawn_keys &cfg = *lightsurf->cfg; const qvec3f dir = qvec3f(lightsurf->origin) - qvec3f(vpl->pos); // vpl -> sample point @@ -1930,6 +2142,10 @@ static void LightFace_Bounce( #if 1 for (const bouncelight_t &vpl : BounceLights()) { + if (VisCullEntity(bsp, lightsurf->pvs, vpl.leaf)) { + continue; + } + if (BounceLight_SphereCull(bsp, &vpl, lightsurf)) continue; @@ -2103,7 +2319,7 @@ static void LightFace_Bounce( } static void // mxd -LightFace_SurfaceLight(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps) +LightFace_SurfaceLight(const mbsp_t *bsp, const lightsurf_t *lightsurf, lightmapdict_t *lightmaps) { const settings::worldspawn_keys &cfg = *lightsurf->cfg; @@ -2114,6 +2330,10 @@ LightFace_SurfaceLight(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps) raystream_occlusion_t *rs = lightsurf->occlusion_stream; for (int c = 0; c < vpl.points.size(); c++) { + if (VisCullEntity(bsp, lightsurf->pvs, vpl.leaves[c])) { + continue; + } + rs->clearPushedRays(); for (int i = 0; i < lightsurf->numpoints; i++) { @@ -3235,7 +3455,7 @@ void LightFace(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, const setti LightFace_Sky(&sun, lightsurf, lightmaps); // mxd. Add surface lights... - LightFace_SurfaceLight(lightsurf, lightmaps); + LightFace_SurfaceLight(bsp, lightsurf, lightmaps); /* add indirect lighting */ LightFace_Bounce(bsp, face, lightsurf, lightmaps); diff --git a/light/surflight.cc b/light/surflight.cc index 127eb717..7a9dc824 100644 --- a/light/surflight.cc +++ b/light/surflight.cc @@ -118,6 +118,13 @@ static void MakeSurfaceLightsThread(const mbsp_t *bsp, const settings::worldspaw l.surfnormal = facenormal; l.omnidirectional = true;//(info->flags.native & Q2_SURF_SKY) ? true : false; l.points = points; + + if (options.visapprox.value() == visapprox_t::VIS) { + for (auto &pt : points) { + l.leaves.push_back(Light_PointInLeaf(bsp, pt + l.surfnormal)); + } + } + l.pos = facemidpoint; // Store surfacelight settings... @@ -128,8 +135,9 @@ static void MakeSurfaceLightsThread(const mbsp_t *bsp, const settings::worldspaw // Init bbox... l.bounds = qvec3d(0); - if (!options.novisapprox.value()) + if (options.visapprox.value() == visapprox_t::RAYS) { l.bounds = EstimateVisibleBoundsAtPoint(facemidpoint); + } // Store light... unique_lock lck{surfacelights_lock}; diff --git a/light/trace.cc b/light/trace.cc index 4b00dfde..6a12eb2f 100644 --- a/light/trace.cc +++ b/light/trace.cc @@ -26,6 +26,35 @@ #endif #include +/* +============== +Light_PointInLeaf + +from hmap2 +============== +*/ +const mleaf_t *Light_PointInLeaf( const mbsp_t *bsp, const qvec3d &point ) +{ + int num = 0; + + while( num >= 0 ) + num = bsp->dnodes[num].children[bsp->dplanes[bsp->dnodes[num].planenum].distance_to_fast(point) < 0]; + + return &bsp->dleafs[-1 - num]; +} + +/* +============== +Light_PointContents + +from hmap2 +============== +*/ +int Light_PointContents( const mbsp_t *bsp, const qvec3d &point ) +{ + return Light_PointInLeaf(bsp, point)->contents; +} + /* * ============================================================================ * FENCE TEXTURE TESTING diff --git a/vis/vis.cc b/vis/vis.cc index 6d141e6f..b109877e 100644 --- a/vis/vis.cc +++ b/vis/vis.cc @@ -486,13 +486,7 @@ static void ClusterFlow(int clusternum, leafbits_t &buffer, mbsp_t *bsp) bsp->dvis.set_bit_offset(VIS_PVS, clusternum, visofs); // Set pointers - if (bsp->loadversion->game->id == GAME_QUAKE_II) { - for (i = 1; i < bsp->dleafs.size(); i++) { - if (bsp->dleafs[i].cluster == clusternum) { - bsp->dleafs[i].visofs = visofs; - } - } - } else { + if (bsp->loadversion->game->id != GAME_QUAKE_II) { for (i = 0; i < portalleafs_real; i++) { if (bsp->dleafs[i + 1].cluster == clusternum) { bsp->dleafs[i + 1].visofs = visofs;