Refactor: move part of light tree logic from #LightManager to #LightTree

This commit is contained in:
Weizhen Huang
2023-04-04 16:17:05 +02:00
parent e58a05ca68
commit 87cbdcbe7c
4 changed files with 148 additions and 128 deletions

View File

@@ -445,90 +445,23 @@ void LightManager::device_update_tree(Device *,
/* Update light tree. */
progress.set_status("Updating Lights", "Computing tree");
/* Add both lights and emissive triangles to this vector for light tree construction. */
vector<LightTreeEmitter> emitters;
emitters.reserve(kintegrator->num_distribution);
vector<LightTreeEmitter> distant_lights;
distant_lights.reserve(kintegrator->num_distant_lights);
vector<uint> object_lookup_offsets(scene->objects.size());
/* When we keep track of the light index, only contributing lights will be added to the device.
* Therefore, we want to keep track of the light's index on the device.
* However, we also need the light's index in the scene when we're constructing the tree. */
int device_light_index = 0;
int scene_light_index = 0;
foreach (Light *light, scene->lights) {
if (light->is_enabled) {
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
distant_lights.emplace_back(scene, ~device_light_index, scene_light_index);
}
else {
emitters.emplace_back(scene, ~device_light_index, scene_light_index);
}
device_light_index++;
}
scene_light_index++;
}
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
size_t total_triangles = 0;
int object_id = 0;
foreach (Object *object, scene->objects) {
if (progress.get_cancel())
return;
if (!object->usable_as_light()) {
object_id++;
continue;
}
object_lookup_offsets[object_id] = total_triangles;
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
emitters.emplace_back(scene, i, object_id);
}
}
total_triangles += mesh_num_triangles;
object_id++;
}
/* Append distant lights to the end of `emitters` */
std::move(distant_lights.begin(), distant_lights.end(), std::back_inserter(emitters));
/* Update integrator state. */
kintegrator->use_direct_light = !emitters.empty();
/* TODO: For now, we'll start with a smaller number of max lights in a node.
* More benchmarking is needed to determine what number works best. */
LightTree light_tree(emitters, kintegrator->num_distant_lights, 8);
LightTree light_tree(scene, dscene, progress, 8);
LightTreeNode *root = light_tree.build(scene, dscene);
/* We want to create separate arrays corresponding to triangles and lights,
* which will be used to index back into the light tree for PDF calculations. */
const size_t num_lights = kintegrator->num_lights;
uint *light_array = dscene->light_to_tree.alloc(num_lights);
uint *object_offsets = dscene->object_lookup_offset.alloc(object_lookup_offsets.size());
uint *triangle_array = dscene->triangle_to_tree.alloc(total_triangles);
for (int i = 0; i < object_lookup_offsets.size(); i++) {
object_offsets[i] = object_lookup_offsets[i];
}
uint *light_array = dscene->light_to_tree.alloc(kintegrator->num_lights);
uint *triangle_array = dscene->triangle_to_tree.alloc(light_tree.num_triangles);
/* First initialize the light tree's nodes. */
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(light_tree.size());
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(emitters.size());
const size_t num_emitters = light_tree.num_emitters();
KernelLightTreeNode *light_tree_nodes = dscene->light_tree_nodes.alloc(light_tree.num_nodes);
KernelLightTreeEmitter *light_tree_emitters = dscene->light_tree_emitters.alloc(num_emitters);
/* Update integrator state. */
kintegrator->use_direct_light = num_emitters > 0;
/* Copy the light tree nodes to an array in the device. */
/* The nodes are arranged in a depth-first order, meaning the left child of each inner node
@@ -543,8 +476,8 @@ void LightManager::device_update_tree(Device *,
int left_index_stack[32]; /* `sizeof(bit_trail) * 8 == 32`. */
LightTreeNode *right_node_stack[32];
int stack_id = 0;
const LightTreeNode *node = light_tree.get_root();
for (int node_index = 0; node_index < light_tree.size(); node_index++) {
const LightTreeNode *node = root;
for (int node_index = 0; node_index < light_tree.num_nodes; node_index++) {
light_tree_nodes[node_index].energy = node->measure.energy;
light_tree_nodes[node_index].bbox.min = node->measure.bbox.min;
@@ -563,7 +496,7 @@ void LightManager::device_update_tree(Device *,
for (int i = 0; i < node->num_emitters; i++) {
int emitter_index = i + node->first_emitter_index;
LightTreeEmitter &emitter = emitters[emitter_index];
const LightTreeEmitter &emitter = light_tree.get_emitter(emitter_index);
light_tree_emitters[emitter_index].energy = emitter.measure.energy;
light_tree_emitters[emitter_index].theta_o = emitter.measure.bcone.theta_o;
@@ -600,7 +533,7 @@ void LightManager::device_update_tree(Device *,
light_tree_emitters[emitter_index].prim_id = emitter.prim_id + mesh->prim_offset;
light_tree_emitters[emitter_index].mesh_light.shader_flag = shader_flag;
light_tree_emitters[emitter_index].emission_sampling = shader->emission_sampling;
triangle_array[emitter.prim_id + object_lookup_offsets[emitter.object_id]] =
triangle_array[emitter.prim_id + dscene->object_lookup_offset[emitter.object_id]] =
emitter_index;
}
else {
@@ -634,7 +567,6 @@ void LightManager::device_update_tree(Device *,
dscene->light_tree_nodes.copy_to_device();
dscene->light_tree_emitters.copy_to_device();
dscene->light_to_tree.copy_to_device();
dscene->object_lookup_offset.copy_to_device();
dscene->triangle_to_tree.copy_to_device();
}

View File

@@ -81,6 +81,7 @@ class Light : public Node {
bool has_contribution(Scene *scene);
friend class LightManager;
friend class LightTree;
};
class LightManager {

View File

@@ -5,6 +5,8 @@
#include "scene/mesh.h"
#include "scene/object.h"
#include "util/progress.h"
CCL_NAMESPACE_BEGIN
float OrientationBounds::calculate_measure() const
@@ -208,43 +210,114 @@ LightTreeEmitter::LightTreeEmitter(Scene *scene, int prim_id, int object_id)
}
}
LightTree::LightTree(vector<LightTreeEmitter> &emitters,
const int &num_distant_lights,
LightTree::LightTree(Scene *scene,
DeviceScene *dscene,
Progress &progress,
uint max_lights_in_leaf)
: progress_(progress), max_lights_in_leaf_(max_lights_in_leaf)
{
if (emitters.empty()) {
return;
KernelIntegrator *kintegrator = &dscene->data.integrator;
/* Add both lights and emissive triangles to this vector for light tree construction. */
emitters_.reserve(kintegrator->num_distribution);
distant_lights_.reserve(kintegrator->num_distant_lights);
uint *object_offsets = dscene->object_lookup_offset.alloc(scene->objects.size());
/* When we keep track of the light index, only contributing lights will be added to the device.
* Therefore, we want to keep track of the light's index on the device.
* However, we also need the light's index in the scene when we're constructing the tree. */
int device_light_index = 0;
int scene_light_index = 0;
for (Light *light : scene->lights) {
if (light->is_enabled) {
if (light->light_type == LIGHT_BACKGROUND || light->light_type == LIGHT_DISTANT) {
distant_lights_.emplace_back(scene, ~device_light_index, scene_light_index);
}
else {
emitters_.emplace_back(scene, ~device_light_index, scene_light_index);
}
device_light_index++;
}
scene_light_index++;
}
max_lights_in_leaf_ = max_lights_in_leaf;
const int num_emitters = emitters.size();
const int num_local_lights = num_emitters - num_distant_lights;
/* Similarly, we also want to keep track of the index of triangles that are emissive. */
int object_id = 0;
for (Object *object : scene->objects) {
if (progress_.get_cancel()) {
return;
}
if (!object->usable_as_light()) {
object_id++;
continue;
}
object_offsets[object_id] = num_triangles;
/* Count emissive triangles. */
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
size_t mesh_num_triangles = mesh->num_triangles();
for (size_t i = 0; i < mesh_num_triangles; i++) {
int shader_index = mesh->get_shader()[i];
Shader *shader = (shader_index < mesh->get_used_shaders().size()) ?
static_cast<Shader *>(mesh->get_used_shaders()[shader_index]) :
scene->default_surface;
if (shader->emission_sampling != EMISSION_SAMPLING_NONE) {
emitters_.emplace_back(scene, i, object_id);
}
}
num_triangles += mesh_num_triangles;
object_id++;
}
/* Copy array to device. */
dscene->object_lookup_offset.copy_to_device();
}
LightTreeNode *LightTree::build(Scene *scene, DeviceScene *dscene)
{
if (emitters_.empty() && distant_lights_.empty()) {
return nullptr;
}
/* At this stage `emitters_` only contains local lights, the distant lights will be merged
* into `emitters_` when Light Tree building is finished. */
const int num_local_lights = emitters_.size();
const int num_distant_lights = distant_lights_.size();
root_ = create_node(LightTreeMeasure::empty, 0);
/* All local lights are grouped to the left child as an inner node. */
recursive_build(left, root_.get(), 0, num_local_lights, &emitters, 0, 1);
recursive_build(left, root_.get(), 0, num_local_lights, emitters_.data(), 0, 1);
task_pool.wait_work();
/* All distant lights are grouped to the right child as a leaf node. */
root_->children[right] = create_node(LightTreeMeasure::empty, 1);
for (int i = num_local_lights; i < num_emitters; i++) {
root_->children[right]->add(emitters[i]);
for (int i = 0; i < num_distant_lights; i++) {
root_->children[right]->add(distant_lights_[i]);
}
root_->children[right]->make_leaf(num_local_lights, num_distant_lights);
/* Append distant lights to the end of `light_prims` */
std::move(distant_lights_.begin(), distant_lights_.end(), std::back_inserter(emitters_));
return root_.get();
}
void LightTree::recursive_build(const Child child,
LightTreeNode *parent,
const int start,
const int end,
vector<LightTreeEmitter> *emitters,
LightTreeEmitter *emitters,
const uint bit_trail,
const int depth)
{
BoundBox centroid_bounds = BoundBox::empty;
for (int i = start; i < end; i++) {
centroid_bounds.grow((*emitters)[i].centroid);
if (progress_.get_cancel()) {
return;
}
parent->children[child] = create_node(LightTreeMeasure::empty, bit_trail);
@@ -253,13 +326,13 @@ void LightTree::recursive_build(const Child child,
/* Find the best place to split the emitters into 2 nodes.
* If the best split cost is no better than making a leaf node, make a leaf instead. */
int split_dim = -1, middle;
if (should_split(*emitters, start, middle, end, node->measure, centroid_bounds, split_dim)) {
if (should_split(emitters, start, middle, end, node->measure, split_dim)) {
if (split_dim != -1) {
/* Partition the emitters between start and end based on the centroids. */
std::nth_element(emitters->begin() + start,
emitters->begin() + middle,
emitters->begin() + end,
std::nth_element(emitters + start,
emitters + middle,
emitters + end,
[split_dim](const LightTreeEmitter &l, const LightTreeEmitter &r) {
return l.centroid[split_dim] < r.centroid[split_dim];
});
@@ -289,16 +362,27 @@ void LightTree::recursive_build(const Child child,
}
}
bool LightTree::should_split(const vector<LightTreeEmitter> &emitters,
bool LightTree::should_split(LightTreeEmitter *emitters,
const int start,
int &middle,
const int end,
LightTreeMeasure &measure,
const BoundBox &centroid_bbox,
int &split_dim)
{
middle = (start + end) / 2;
const int num_emitters = end - start;
if (num_emitters == 1) {
/* Do not try to split if there is only one emitter. */
measure = (emitters + start)->measure;
return false;
}
middle = (start + end) / 2;
BoundBox centroid_bbox = BoundBox::empty;
for (int i = start; i < end; i++) {
centroid_bbox.grow((emitters + i)->centroid);
}
const float3 extent = centroid_bbox.size();
const float max_extent = max4(extent.x, extent.y, extent.z, 0.0f);
@@ -316,15 +400,15 @@ bool LightTree::should_split(const vector<LightTreeEmitter> &emitters,
/* Fill in buckets with emitters. */
std::array<LightTreeBucket, LightTreeBucket::num_buckets> buckets;
for (int i = start; i < end; i++) {
const LightTreeEmitter &emitter = emitters[i];
const LightTreeEmitter *emitter = emitters + i;
/* Place emitter into the appropriate bucket, where the centroid box is split into equal
* partitions. */
int bucket_idx = LightTreeBucket::num_buckets *
(emitter.centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
(emitter->centroid[dim] - centroid_bbox.min[dim]) * inv_extent;
bucket_idx = clamp(bucket_idx, 0, LightTreeBucket::num_buckets - 1);
buckets[bucket_idx].add(emitter);
buckets[bucket_idx].add(*emitter);
}
/* Precompute the left bucket measure cumulatively. */
@@ -338,11 +422,6 @@ bool LightTree::should_split(const vector<LightTreeEmitter> &emitters,
/* Calculate node measure by summing up the bucket measure. */
measure = left_buckets.back().measure + buckets.back().measure;
/* Do not try to split if there are only one emitter. */
if (num_emitters < 2) {
return false;
}
/* Degenerate case with co-located emitters. */
if (is_zero(centroid_bbox.size())) {
break;

View File

@@ -187,37 +187,46 @@ struct LightTreeNode {
* and considers additional orientation and energy information */
class LightTree {
unique_ptr<LightTreeNode> root_;
std::atomic<int> num_nodes_ = 0;
vector<LightTreeEmitter> emitters_;
vector<LightTreeEmitter> distant_lights_;
Progress &progress_;
uint max_lights_in_leaf_;
public:
std::atomic<int> num_nodes = 0;
size_t num_triangles = 0;
/* Left or right child of an inner node. */
enum Child {
left = 0,
right = 1,
};
LightTree(vector<LightTreeEmitter> &emitters,
const int &num_distant_lights,
uint max_lights_in_leaf);
LightTree(Scene *scene, DeviceScene *dscene, Progress &progress, uint max_lights_in_leaf);
int size() const
{
return num_nodes_;
};
LightTreeNode *get_root() const
{
return root_.get();
};
/* Returns a pointer to the root node. */
LightTreeNode *build(Scene *scene, DeviceScene *dscene);
/* NOTE: Always use this function to create a new node so the number of nodes is in sync. */
unique_ptr<LightTreeNode> create_node(const LightTreeMeasure &measure, const uint &bit_trial)
{
num_nodes_++;
num_nodes++;
return make_unique<LightTreeNode>(measure, bit_trial);
}
size_t num_emitters()
{
return emitters_.size();
}
const LightTreeEmitter &get_emitter(int index) const
{
return emitters_.at(index);
}
private:
/* Thread. */
TaskPool task_pool;
@@ -228,16 +237,15 @@ class LightTree {
LightTreeNode *parent,
int start,
int end,
vector<LightTreeEmitter> *emitters,
LightTreeEmitter *emitters,
uint bit_trail,
int depth);
bool should_split(const vector<LightTreeEmitter> &emitters,
bool should_split(LightTreeEmitter *emitters,
const int start,
int &middle,
const int end,
LightTreeMeasure &measure,
const BoundBox &centroid_bbox,
int &split_dim);
};