light: add octree experimental version of lightgrid lump
This commit is contained in:
parent
6aa705e5f6
commit
f399b95bb8
|
|
@ -240,6 +240,7 @@ enum class visapprox_t
|
||||||
enum class lightgrid_format_t {
|
enum class lightgrid_format_t {
|
||||||
UNIFORM,
|
UNIFORM,
|
||||||
CLUSTER,
|
CLUSTER,
|
||||||
|
OCTREE
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
||||||
281
light/light.cc
281
light/light.cc
|
|
@ -318,7 +318,7 @@ light_settings::light_settings()
|
||||||
lightgrid{this, "lightgrid", false, &experimental_group, "experimental LIGHTGRID bspx lump"},
|
lightgrid{this, "lightgrid", false, &experimental_group, "experimental LIGHTGRID bspx lump"},
|
||||||
lightgrid_dist{this, "lightgrid_dist", 32.f, 32.f, 32.f, &experimental_group, "distance between lightgrid sample points, in world units. controls lightgrid size."},
|
lightgrid_dist{this, "lightgrid_dist", 32.f, 32.f, 32.f, &experimental_group, "distance between lightgrid sample points, in world units. controls lightgrid size."},
|
||||||
lightgrid_format{this, "lightgrid_format", lightgrid_format_t::CLUSTER,
|
lightgrid_format{this, "lightgrid_format", lightgrid_format_t::CLUSTER,
|
||||||
{{"cluster", lightgrid_format_t::CLUSTER}, {"uniform", lightgrid_format_t::UNIFORM}}, &experimental_group, "lightgrid BSPX lump to use"},
|
{{"cluster", lightgrid_format_t::CLUSTER}, {"uniform", lightgrid_format_t::UNIFORM}, {"octree", lightgrid_format_t::OCTREE}}, &experimental_group, "lightgrid BSPX lump to use"},
|
||||||
|
|
||||||
dirtdebug{this, {"dirtdebug", "debugdirt"},
|
dirtdebug{this, {"dirtdebug", "debugdirt"},
|
||||||
[&](source) {
|
[&](source) {
|
||||||
|
|
@ -1099,6 +1099,10 @@ static void LightGrid(bspdata_t *bspdata)
|
||||||
occlusion[sample_index] = occluded;
|
occlusion[sample_index] = occluded;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto get_grid_index = [&](int x, int y, int z) -> int {
|
||||||
|
return (grid_size[0] * grid_size[1] * z) + (grid_size[0] * y) + x;
|
||||||
|
};
|
||||||
|
|
||||||
// the maximum used styles across the map.
|
// the maximum used styles across the map.
|
||||||
const uint8_t num_styles = [&](){
|
const uint8_t num_styles = [&](){
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
@ -1199,7 +1203,7 @@ static void LightGrid(bspdata_t *bspdata)
|
||||||
for (int z = cm[2]; z < (cm[2] + cs[2]); ++z) {
|
for (int z = cm[2]; z < (cm[2] + cs[2]); ++z) {
|
||||||
for (int y = cm[1]; y < (cm[1] + cs[1]); ++y) {
|
for (int y = cm[1]; y < (cm[1] + cs[1]); ++y) {
|
||||||
for (int x = cm[0]; x < (cm[0] + cs[0]); ++x) {
|
for (int x = cm[0]; x < (cm[0] + cs[0]); ++x) {
|
||||||
int sample_index = (grid_size[0] * grid_size[1] * z) + (grid_size[0] * y) + x;
|
int sample_index = get_grid_index(x, y, z);
|
||||||
|
|
||||||
if (occlusion[sample_index]) {
|
if (occlusion[sample_index]) {
|
||||||
str <= static_cast<uint8_t>(0xff);
|
str <= static_cast<uint8_t>(0xff);
|
||||||
|
|
@ -1222,6 +1226,279 @@ static void LightGrid(bspdata_t *bspdata)
|
||||||
|
|
||||||
bspdata->bspx.transfer("LIGHTGRID_PERCLUSTER", std::move(vec));
|
bspdata->bspx.transfer("LIGHTGRID_PERCLUSTER", std::move(vec));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// octree lump
|
||||||
|
if (light_options.lightgrid_format.value() == lightgrid_format_t::OCTREE)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* returns the octant index in [0..7]
|
||||||
|
*/
|
||||||
|
auto child_index = [](qvec3i division_point, qvec3i test_point) -> int {
|
||||||
|
int sign[3];
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
sign[i] = (test_point[i] >= division_point[i]);
|
||||||
|
|
||||||
|
return (4 * sign[0]) + (2 * sign[1]) + (sign[2]);
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_assert(child_index({1,1,1}, {2,2,2}) == 7);
|
||||||
|
Q_assert(child_index({1,1,1}, {1,1,0}) == 6);
|
||||||
|
Q_assert(child_index({1,1,1}, {1,0,1}) == 5);
|
||||||
|
Q_assert(child_index({1,1,1}, {1,0,0}) == 4);
|
||||||
|
Q_assert(child_index({1,1,1}, {0,1,1}) == 3);
|
||||||
|
Q_assert(child_index({1,1,1}, {0,1,0}) == 2);
|
||||||
|
Q_assert(child_index({1,1,1}, {0,0,1}) == 1);
|
||||||
|
Q_assert(child_index({1,1,1}, {0,0,0}) == 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns octant index `i`'s mins and size
|
||||||
|
*/
|
||||||
|
auto get_octant = [](int i, qvec3i mins, qvec3i size, qvec3i division_point) -> std::tuple<qvec3i, qvec3i> {
|
||||||
|
qvec3i child_mins;
|
||||||
|
qvec3i child_size;
|
||||||
|
for (int axis = 0; axis < 3; ++axis) {
|
||||||
|
int bit;
|
||||||
|
if (axis == 0) {
|
||||||
|
bit = 4;
|
||||||
|
} else if (axis == 1) {
|
||||||
|
bit = 2;
|
||||||
|
} else {
|
||||||
|
bit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i & bit) {
|
||||||
|
child_mins[axis] = division_point[axis];
|
||||||
|
child_size[axis] = mins[axis] + size[axis] - division_point[axis];
|
||||||
|
} else {
|
||||||
|
child_mins[axis] = mins[axis];
|
||||||
|
child_size[axis] = division_point[axis] - mins[axis];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {child_mins, child_size};
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_assert(get_octant(0, {0,0,0}, {2,2,2}, {1,1,1}) == (std::tuple<qvec3i, qvec3i>{{0,0,0}, {1,1,1}}));
|
||||||
|
Q_assert(get_octant(7, {0,0,0}, {2,2,2}, {1,1,1}) == (std::tuple<qvec3i, qvec3i>{{1,1,1}, {1,1,1}}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given a bounding box, selects the division point.
|
||||||
|
*/
|
||||||
|
auto get_division_point = [](qvec3i mins, qvec3i size) -> qvec3i {
|
||||||
|
return mins + (size / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto is_all_occluded = [&](qvec3i mins, qvec3i size) -> bool {
|
||||||
|
for (int z = mins[2]; z < (mins[2] + size[2]); ++z) {
|
||||||
|
for (int y = mins[1]; y < (mins[1] + size[1]); ++y) {
|
||||||
|
for (int x = mins[0]; x < (mins[0] + size[0]); ++x) {
|
||||||
|
int sample_index = get_grid_index(x, y, z);
|
||||||
|
if (!occlusion[sample_index]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int MAX_DEPTH = 5;
|
||||||
|
// if any axis is fewer than this many grid points, don't bother subdividing further, just create a leaf
|
||||||
|
constexpr int MIN_NODE_DIMENSION = 4;
|
||||||
|
|
||||||
|
// if set, it's an index in the leafs array
|
||||||
|
constexpr uint32_t FLAG_LEAF = 1 << 31;
|
||||||
|
constexpr uint32_t FLAG_OCCLUDED = 1 << 30;
|
||||||
|
constexpr uint32_t FLAGS = (FLAG_LEAF | FLAG_OCCLUDED);
|
||||||
|
// if neither flags are set, it's a node index
|
||||||
|
|
||||||
|
struct octree_node {
|
||||||
|
qvec3i division_point;
|
||||||
|
std::array<uint32_t, 8> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct octree_leaf {
|
||||||
|
qvec3i mins, size;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<octree_node> octree_nodes;
|
||||||
|
std::vector<octree_leaf> octree_leafs;
|
||||||
|
|
||||||
|
int occluded_cells = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - inserts either a node or leaf
|
||||||
|
* - returns one of:
|
||||||
|
* - FLAG_OCCLUDED if the entire bounds is occluded
|
||||||
|
* - (FLAG_LEAF | leaf_num) for a leaf - a literal chunk of grid samples
|
||||||
|
* - otherwise, it's a node index
|
||||||
|
*/
|
||||||
|
std::function<int(qvec3i, qvec3i, int depth)> build_octree;
|
||||||
|
build_octree = [&](qvec3i mins, qvec3i size, int depth) -> uint32_t {
|
||||||
|
assert(size[0] > 0);
|
||||||
|
assert(size[1] > 0);
|
||||||
|
assert(size[2] > 0);
|
||||||
|
|
||||||
|
// special case: fully occluded leaf, just represented as a flag bit
|
||||||
|
if (is_all_occluded(mins, size)) {
|
||||||
|
occluded_cells += size[0] * size[1] * size[2];
|
||||||
|
return FLAG_OCCLUDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decide whether we are creating a regular leaf or a node?
|
||||||
|
bool make_leaf = false;
|
||||||
|
if (size[0] < MIN_NODE_DIMENSION || size[1] < MIN_NODE_DIMENSION || size[2] < MIN_NODE_DIMENSION)
|
||||||
|
make_leaf = true;
|
||||||
|
if (depth == MAX_DEPTH)
|
||||||
|
make_leaf = true;
|
||||||
|
|
||||||
|
if (make_leaf) {
|
||||||
|
// make a leaf
|
||||||
|
const uint32_t leafnum = static_cast<uint32_t>(octree_leafs.size());
|
||||||
|
octree_leafs.push_back({
|
||||||
|
.mins = mins, .size = size
|
||||||
|
});
|
||||||
|
return FLAG_LEAF | leafnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a node
|
||||||
|
|
||||||
|
const qvec3i division_point = get_division_point(mins, size);
|
||||||
|
|
||||||
|
// create the 8 child nodes/leafs recursively, store the returned indices
|
||||||
|
std::array<uint32_t, 8> children;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
// figure out the mins/size of this child
|
||||||
|
auto [child_mins, child_size] = get_octant(i, mins, size, division_point);
|
||||||
|
children[i] = build_octree(child_mins, child_size, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the node
|
||||||
|
const uint32_t nodenum = static_cast<uint32_t>(octree_nodes.size());
|
||||||
|
octree_nodes.push_back({
|
||||||
|
.division_point = division_point,
|
||||||
|
.children = children
|
||||||
|
});
|
||||||
|
return nodenum;
|
||||||
|
};
|
||||||
|
|
||||||
|
// build the root node
|
||||||
|
const uint32_t root_node = build_octree(qvec3i{0,0,0}, grid_size, 0);
|
||||||
|
|
||||||
|
// stats
|
||||||
|
int stored_cells = 0;
|
||||||
|
for (auto &leaf : octree_leafs) {
|
||||||
|
stored_cells += leaf.size[0] * leaf.size[1] * leaf.size[2];
|
||||||
|
}
|
||||||
|
logging::print("octree stored {} grid nodes + {} occluded = {} total, full stored {} (octree is {} percent)\n",
|
||||||
|
stored_cells,
|
||||||
|
occluded_cells,
|
||||||
|
stored_cells + occluded_cells,
|
||||||
|
occlusion.size(),
|
||||||
|
100.0f * stored_cells / (float) occlusion.size());
|
||||||
|
|
||||||
|
logging::print("octree nodes size: {} bytes ({} * {})\n",
|
||||||
|
octree_nodes.size() * sizeof(octree_node),
|
||||||
|
octree_nodes.size(),
|
||||||
|
sizeof(octree_node));
|
||||||
|
|
||||||
|
logging::print("octree leafs {} overhead {} bytes\n",
|
||||||
|
octree_leafs.size(),
|
||||||
|
octree_leafs.size() * sizeof(octree_leaf));
|
||||||
|
|
||||||
|
// lookup function
|
||||||
|
std::function<std::tuple<lightgrid_samples_t, bool>(uint32_t, qvec3i)> octree_lookup_r;
|
||||||
|
octree_lookup_r = [&](uint32_t node_index, qvec3i test_point) -> std::tuple<lightgrid_samples_t, bool> {
|
||||||
|
if (node_index & FLAG_OCCLUDED) {
|
||||||
|
return {{}, true};
|
||||||
|
}
|
||||||
|
if (node_index & FLAG_LEAF) {
|
||||||
|
auto &leaf = octree_leafs.at(node_index & (~FLAG_LEAF));
|
||||||
|
// in actuality, we'd pull the data from a 3D grid stored in the leaf.
|
||||||
|
int i = get_grid_index(test_point[0], test_point[1], test_point[2]);
|
||||||
|
return {grid_result[i], occlusion[i]};
|
||||||
|
}
|
||||||
|
auto &node = octree_nodes[node_index];
|
||||||
|
int i = child_index(node.division_point, test_point); // [0..7]
|
||||||
|
return octree_lookup_r(node.children[i], test_point);
|
||||||
|
};
|
||||||
|
|
||||||
|
// self-check
|
||||||
|
for (int z = 0; z < grid_size[2]; ++z) {
|
||||||
|
for (int y = 0; y < grid_size[1]; ++y) {
|
||||||
|
for (int x = 0; x < grid_size[0]; ++x) {
|
||||||
|
auto [color, occluded] = octree_lookup_r(root_node, {x, y, z});
|
||||||
|
|
||||||
|
int sample_index = get_grid_index(x, y, z);
|
||||||
|
|
||||||
|
// compare against original data
|
||||||
|
if (occluded) {
|
||||||
|
Q_assert(occlusion[sample_index]);
|
||||||
|
} else {
|
||||||
|
Q_assert(!occlusion[sample_index]);
|
||||||
|
Q_assert(grid_result[sample_index] == color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the binary data
|
||||||
|
const qvec3f grid_dist = qvec3f{light_options.lightgrid_dist.value()};
|
||||||
|
|
||||||
|
std::ostringstream str(std::ios_base::out | std::ios_base::binary);
|
||||||
|
str << endianness<std::endian::little>;
|
||||||
|
str <= grid_dist;
|
||||||
|
str <= grid_size;
|
||||||
|
str <= grid_mins;
|
||||||
|
str <= num_styles;
|
||||||
|
|
||||||
|
str << static_cast<uint32_t>(root_node);
|
||||||
|
|
||||||
|
// the nodes (fixed-size)
|
||||||
|
str << static_cast<uint32_t>(octree_nodes.size());
|
||||||
|
for (const auto &node : octree_nodes) {
|
||||||
|
str << node.division_point;
|
||||||
|
for (const auto child : node.children) {
|
||||||
|
str << child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the leafs (each is variable sized)
|
||||||
|
str << static_cast<uint32_t>(octree_leafs.size());
|
||||||
|
for (const auto &leaf : octree_leafs) {
|
||||||
|
str <= leaf.mins;
|
||||||
|
str <= leaf.size;
|
||||||
|
|
||||||
|
// logging::print("cluster {} bounds grid mins {} grid size {}\n", cluster, cluster_min_grid_coord, cluster_grid_size);
|
||||||
|
|
||||||
|
auto &cm = leaf.mins;
|
||||||
|
auto &cs = leaf.size;
|
||||||
|
|
||||||
|
for (int z = cm[2]; z < (cm[2] + cs[2]); ++z) {
|
||||||
|
for (int y = cm[1]; y < (cm[1] + cs[1]); ++y) {
|
||||||
|
for (int x = cm[0]; x < (cm[0] + cs[0]); ++x) {
|
||||||
|
int sample_index = get_grid_index(x, y, z);
|
||||||
|
|
||||||
|
if (occlusion[sample_index]) {
|
||||||
|
str <= static_cast<uint8_t>(0xff);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lightgrid_samples_t &samples = grid_result[sample_index];
|
||||||
|
str <= static_cast<uint8_t>(samples.used_styles());
|
||||||
|
for (int i = 0; i < samples.used_styles(); ++i) {
|
||||||
|
str <= static_cast<uint8_t>(samples.samples_by_style[i].style);
|
||||||
|
str <= samples.samples_by_style[i].round_to_int();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vec = StringToVector(str.str());
|
||||||
|
logging::print(" {:8} bytes LIGHTGRID_OCTREE\n", vec.size());
|
||||||
|
|
||||||
|
bspdata->bspx.transfer("LIGHTGRID_OCTREE", std::move(vec));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_t *bsp)
|
static void LoadExtendedTexinfoFlags(const fs::path &sourcefilename, const mbsp_t *bsp)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue