re-introduce vis culling, mainly for Q2/surface lit maps

-novisapprox gone; replaced with -visapprox auto|none|vis|rays
This commit is contained in:
Jonathan 2022-06-18 23:56:45 -04:00
parent b54e72a184
commit e366898796
13 changed files with 372 additions and 20 deletions

View File

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

View File

@ -39,6 +39,8 @@ struct bouncelight_t
qvec3f surfnormal;
float area;
const mleaf_t *leaf;
/* estimated visible AABB culling */
aabb3d bounds;
};

View File

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

View File

@ -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<uint8_t> 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_t> 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<surfflags_t> 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);

View File

@ -32,6 +32,7 @@ struct surfacelight_t
*/
bool omnidirectional;
std::vector<qvec3f> points;
std::vector<const mleaf_t *> leaves;
// Surface light settings...
float intensity; // Surface light strength for each point

View File

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

View File

@ -144,7 +144,9 @@ static void AddBounceLight(const T &pos, const std::map<int, qvec3f> &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);
}

View File

@ -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_t> &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());

View File

@ -646,6 +646,59 @@ static void ExportObj(const fs::path &filename, const mbsp_t *bsp)
}
// obj
static vector<vector<const mleaf_t *>> faceleafs;
static vector<bool> 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);

View File

@ -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<uint8_t> &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; j<pvssize; j++) {
lightsurf->pvs[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<int, qvec3f> GetDirectLighting(
return result;
}
static bool VisCullEntity(const mbsp_t *bsp, const std::vector<uint8_t> &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);

View File

@ -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<mutex> lck{surfacelights_lock};

View File

@ -26,6 +26,35 @@
#endif
#include <cassert>
/*
==============
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

View File

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