light/qbsp: add _surflight_rescale key

This commit is contained in:
Eric Wasylishen 2022-11-12 17:07:02 -07:00
parent a5125583fa
commit d7acd046c3
10 changed files with 193 additions and 5 deletions

View File

@ -1693,14 +1693,14 @@ const bspversion_t bspver_qbism{Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II
bool surfflags_t::needs_write() const
{
return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || phong_angle ||
return no_dirt || no_shadow || no_bounce || no_minlight || no_expand || light_ignore || !surflight_rescale || phong_angle ||
phong_angle_concave || minlight || !qv::emptyExact(minlight_color) || light_alpha || maxlight || lightcolorscale != 1.0;
}
static auto as_tuple(const surfflags_t &flags)
{
return std::tie(flags.native, flags.is_nodraw, flags.is_hintskip, flags.is_hint, flags.no_dirt, flags.no_shadow, flags.no_bounce, flags.no_minlight, flags.no_expand,
flags.light_ignore, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale);
flags.light_ignore, flags.surflight_rescale, flags.phong_angle, flags.phong_angle_concave, flags.minlight, flags.minlight_color, flags.light_alpha, flags.maxlight, flags.lightcolorscale);
}
bool surfflags_t::operator<(const surfflags_t &other) const

View File

@ -622,6 +622,20 @@ void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed)
} while (out - decompressed < row);
}
bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num)
{
auto &lump_bytes = entries.at("DECOUPLED_LM");
auto stream = imemstream(lump_bytes.data(), lump_bytes.size());
stream.seekg(face_num * sizeof(bspx_decoupled_lm_perface));
stream >> endianness<std::endian::little>;
bspx_decoupled_lm_perface result;
stream >= result;
return result;
}
qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex)
{
/*
@ -901,3 +915,27 @@ qvec3f faceextents_t::LMCoordToWorld(qvec2f lm) const
const qvec4f res = lmToWorldMatrix * lmPadded;
return res;
}
/**
* Samples the lightmap at an integer coordinate
*/
qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord)
{
int pixel = coord[0] + (coord[1] * faceextents.width());
const uint8_t* data = bsp->dlightdata.data();
if (bsp->loadversion->game->has_rgb_lightmap) {
return qvec3f{
data[byte_offset_of_face + (pixel * 3) + 0],
data[byte_offset_of_face + (pixel * 3) + 1],
data[byte_offset_of_face + (pixel * 3) + 2]
};
} else {
return qvec3f{
data[byte_offset_of_face + pixel],
data[byte_offset_of_face + pixel],
data[byte_offset_of_face + pixel]
};
}
}

View File

@ -185,6 +185,10 @@ struct surfflags_t
// this face doesn't receive light
bool light_ignore;
// if true, rescales any surface light emitted by these brushes to emit 50% light at 90 degrees from the surface normal
// if false, use a more natural angle falloff of 0% at 90 degrees
bool surflight_rescale = true;
// if non zero, enables phong shading and gives the angle threshold to use
vec_t phong_angle;

View File

@ -20,6 +20,7 @@
#pragma once
#include <common/bspfile.hh>
#include <common/bspxfile.hh>
#include <common/mathlib.hh>
#include <common/qvec.hh>
#include <common/aabb.hh>
@ -107,6 +108,8 @@ void Face_DebugPrint(const mbsp_t *bsp, const mface_t *face);
void CompressRow(const uint8_t *vis, const size_t numbytes, std::back_insert_iterator<std::vector<uint8_t>> it);
void DecompressRow(const uint8_t *in, const int numbytes, uint8_t *decompressed);
bspx_decoupled_lm_perface BSPX_DecoupledLM(const bspxentries_t &entries, int face_num);
/* ======================================================================== */
qvec2d WorldToTexCoord(const qvec3d &world, const mtexinfo_t *tex);
@ -150,3 +153,5 @@ public:
qvec2f worldToLMCoord(qvec3f world) const;
qvec3f LMCoordToWorld(qvec2f lm) const;
};
qvec3b LM_Sample(const mbsp_t *bsp, const faceextents_t &faceextents, int byte_offset_of_face, qvec2i coord);

View File

@ -1072,6 +1072,9 @@ static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_
if (val.contains("light_ignore")) {
flags.light_ignore = val.at("light_ignore").get<bool>();
}
if (val.contains("surflight_rescale")) {
flags.surflight_rescale = val.at("surflight_rescale").get<bool>();
}
if (val.contains("phong_angle")) {
flags.phong_angle = val.at("phong_angle").get<vec_t>();
}

View File

@ -61,6 +61,8 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
auto poly = GLM_FacePoints(bsp, face);
const float facearea = qv::PolyArea(poly.begin(), poly.end());
const surfflags_t &extended_flags = extended_texinfo_flags[face->texinfo];
// Avoid small, or zero-area faces
if (facearea < 1)
return;
@ -115,7 +117,7 @@ static void MakeSurfaceLight(const mbsp_t *bsp, const settings::worldspawn_keys
l.omnidirectional = !is_directional;
l.points = std::move(points);
l.style = style;
l.rescale = true;
l.rescale = extended_flags.surflight_rescale;
// Init bbox...
if (light_options.visapprox.value() == visapprox_t::RAYS) {

View File

@ -576,6 +576,8 @@ static surfflags_t SurfFlagsForEntity(const maptexinfo_t &texinfo, const mapenti
flags.no_minlight = true;
if (entity.epairs.get_int("_lightignore") == 1)
flags.light_ignore = true;
if (entity.epairs.has("_surflight_rescale") && entity.epairs.get_int("_surflight_rescale") == 0)
flags.surflight_rescale = false;
// "_minlight_exclude", "_minlight_exclude2", "_minlight_exclude3"...
for (int i = 0; i <= 9; i++) {

View File

@ -368,6 +368,9 @@ static void WriteExtendedTexinfoFlags(void)
if (tx.flags.light_ignore) {
t["light_ignore"] = tx.flags.light_ignore;
}
if (tx.flags.surflight_rescale == false) {
t["surflight_rescale"] = tx.flags.surflight_rescale;
}
if (tx.flags.phong_angle) {
t["phong_angle"] = tx.flags.phong_angle;
}

View File

@ -0,0 +1,82 @@
// Game: Quake 2
// Format: Quake2 (Valve)
// entity 0
{
"classname" "worldspawn"
"_tb_textures" "textures/e1u1"
"_bounce" "0"
// brush 0
{
( 928 -1072 880 ) ( 928 -1504 880 ) ( 928 -1504 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 880 ) ( 1120 -1504 880 ) ( 1120 -1504 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 864 ) ( 1120 -1072 864 ) ( 928 -1072 864 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 880 ) ( 1120 -1072 880 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 864 ) ( 1120 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 880 ) ( 1120 -1072 880 ) ( 1120 -1072 864 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 1
{
( 928 -1072 1040 ) ( 928 -1504 1040 ) ( 928 -1504 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 1040 ) ( 1120 -1504 1040 ) ( 1120 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1024 ) ( 1120 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 1040 ) ( 1120 -1072 1040 ) ( 1120 -1504 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 1024 ) ( 1120 -1072 1040 ) ( 928 -1072 1040 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1040 ) ( 1120 -1072 1040 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 2
{
( 1120 -1072 1024 ) ( 1120 -1504 1024 ) ( 1120 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1504 1024 ) ( 1136 -1504 1024 ) ( 1136 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1136 -1504 880 ) ( 1136 -1072 880 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 1024 ) ( 1136 -1072 1024 ) ( 1136 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1136 -1072 880 ) ( 1136 -1072 1024 ) ( 1120 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1136 -1504 1024 ) ( 1136 -1072 1024 ) ( 1136 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 3
{
( 928 -1072 1024 ) ( 928 -1504 1024 ) ( 928 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 928 -1504 1024 ) ( 944 -1504 1024 ) ( 944 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1504 880 ) ( 944 -1072 880 ) ( 928 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 928 -1072 1024 ) ( 944 -1072 1024 ) ( 944 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 944 -1072 880 ) ( 944 -1072 1024 ) ( 928 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1504 1024 ) ( 944 -1072 1024 ) ( 944 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 4
{
( 944 -1072 1024 ) ( 944 -1088 1024 ) ( 944 -1088 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 944 -1088 1024 ) ( 1120 -1088 1024 ) ( 1120 -1088 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1088 880 ) ( 1120 -1072 880 ) ( 944 -1072 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 944 -1072 1024 ) ( 1120 -1072 1024 ) ( 1120 -1088 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1120 -1072 880 ) ( 1120 -1072 1024 ) ( 944 -1072 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1120 -1088 1024 ) ( 1120 -1072 1024 ) ( 1120 -1072 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
// brush 5
{
( 936 -1504 1024 ) ( 936 -1520 1024 ) ( 936 -1520 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 936 -1520 1024 ) ( 1112 -1520 1024 ) ( 1112 -1520 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1112 -1520 880 ) ( 1112 -1504 880 ) ( 936 -1504 880 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 936 -1504 1024 ) ( 1112 -1504 1024 ) ( 1112 -1520 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 0 0
( 1112 -1504 880 ) ( 1112 -1504 1024 ) ( 936 -1504 1024 ) e1u1/box3_7 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
( 1112 -1520 1024 ) ( 1112 -1504 1024 ) ( 1112 -1504 880 ) e1u1/box3_7 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 0 0
}
}
// entity 1
{
"classname" "info_player_start"
"origin" "1040 -1184 904"
"angle" "270"
}
// entity 2
{
"classname" "func_group"
"_surflight_rescale" "0"
// brush 0
{
( 968 -1264 992 ) ( 968 -1464 992 ) ( 968 -1464 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 968 -1464 992 ) ( 1104 -1464 992 ) ( 1104 -1464 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 1104 -1464 904 ) ( 1104 -1264 904 ) ( 968 -1264 904 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100
( 968 -1264 992 ) ( 1104 -1264 992 ) ( 1104 -1464 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 0 1 100
( 1104 -1264 904 ) ( 1104 -1264 992 ) ( 968 -1264 992 ) e1u1/box3_6 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
( 1104 -1464 992 ) ( 1104 -1264 992 ) ( 1104 -1264 904 ) e1u1/box3_6 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1 0 1 100
}
}

View File

@ -5,7 +5,12 @@
#include <qbsp/qbsp.hh>
#include <testmaps.hh>
static void LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args)
struct testresults_t {
mbsp_t bsp;
bspxentries_t bspx;
};
static testresults_t LoadTestmap(const std::filesystem::path &name, std::vector<std::string> extra_args)
{
auto map_path = std::filesystem::path(testmaps_dir) / name;
@ -52,9 +57,53 @@ static void LoadTestmap(const std::filesystem::path &name, std::vector<std::stri
// write to .json for inspection
serialize_bsp(bspdata, std::get<mbsp_t>(bspdata.bsp),
fs::path(qbsp_options.bsp_path).replace_extension(".bsp.json"));
return {std::move(std::get<mbsp_t>(bspdata.bsp)),
std::move(bspdata.bspx.entries)};
}
}
TEST_CASE("TestLight") {
LoadTestmap("q2_lightmap_custom_scale.map", {"-threads", "1", "-extra", "-world_units_per_luxel", "8"});
LoadTestmap("q2_lightmap_custom_scale.map", {"-threads", "1", "-world_units_per_luxel", "8"});
}
TEST_CASE("emissive cube artifacts") {
// A cube with surface flags "light", value "100", placed in a hallway.
//
// Generates harsh lines on the walls/ceiling due to a hack in `light` allowing
// surface lights to emit 50% at 90 degrees off their surface normal (when physically it should be 0%).
//
// It's wanted in some cases (base1.map sewer lights flush with the wall, desired for them to
// emit some lights on to their adjacent wall faces.)
//
// To disable the behaviour in this case with the cube lighting a hallway we have a entity key:
//
// "_surflight_rescale" "0"
//
auto [bsp, bspx] = LoadTestmap("light_q2_emissive_cube.map", {"-threads", "1", "-world_units_per_luxel", "4"});
const auto start = qvec3d{1044, -1244, 880};
const auto end = qvec3d{1044, -1272, 880};
auto *floor = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], start, {0, 0, 1});
auto lm_info = BSPX_DecoupledLM(bspx, Face_GetNum(&bsp, floor));
const faceextents_t extents(*floor, bsp, lm_info.lmwidth, lm_info.lmheight, lm_info.world_to_lm_space);
// sample the lightmap along the floor, approaching the glowing cube
// should get brighter
qvec3b previous_sample{};
for (int y = start[1]; y >= end[1]; y -= 4) {
qvec3d pos = start;
pos[1] = y;
auto lm_coord = extents.worldToLMCoord(pos);
auto sample = LM_Sample(&bsp, extents, lm_info.offset, lm_coord);
CHECK(sample[0] >= previous_sample[0]);
//logging::print("world: {} lm_coord: {} sample: {} lm size: {}x{}\n", pos, lm_coord, sample, lm_info.lmwidth, lm_info.lmheight);
previous_sample = sample;
}
}