Refactor: move part of light tree logic from #LightManager to #LightTree
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ class Light : public Node {
|
||||
bool has_contribution(Scene *scene);
|
||||
|
||||
friend class LightManager;
|
||||
friend class LightTree;
|
||||
};
|
||||
|
||||
class LightManager {
|
||||
|
||||
@@ -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 ¢roid_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;
|
||||
|
||||
@@ -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 ¢roid_bbox,
|
||||
int &split_dim);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user