461 lines
12 KiB
C++
461 lines
12 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#ifndef __LIGHT_TREE_H__
|
|
#define __LIGHT_TREE_H__
|
|
|
|
#include "scene/light.h"
|
|
#include "scene/scene.h"
|
|
|
|
#include "util/boundbox.h"
|
|
#include "util/task.h"
|
|
#include "util/types.h"
|
|
#include "util/vector.h"
|
|
|
|
#include <variant>
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* Orientation Bounds
|
|
*
|
|
* Bounds the normal axis of the lights,
|
|
* along with their emission profiles */
|
|
struct OrientationBounds {
|
|
float3 axis; /* normal axis of the light */
|
|
float theta_o; /* angle bounding the normals */
|
|
float theta_e; /* angle bounding the light emissions */
|
|
|
|
__forceinline OrientationBounds() {}
|
|
|
|
__forceinline OrientationBounds(const float3 &axis_, float theta_o_, float theta_e_)
|
|
: axis(axis_), theta_o(theta_o_), theta_e(theta_e_)
|
|
{
|
|
}
|
|
|
|
enum empty_t { empty = 0 };
|
|
|
|
/* If the orientation bound is set to empty, the values are set to minimums
|
|
* so that merging it with another non-empty orientation bound guarantees that
|
|
* the return value is equal to non-empty orientation bound. */
|
|
__forceinline OrientationBounds(empty_t)
|
|
: axis(make_float3(0, 0, 0)), theta_o(FLT_MIN), theta_e(FLT_MIN)
|
|
{
|
|
}
|
|
|
|
__forceinline bool is_empty() const
|
|
{
|
|
return is_zero(axis);
|
|
}
|
|
|
|
float calculate_measure() const;
|
|
};
|
|
|
|
OrientationBounds merge(const OrientationBounds &cone_a, const OrientationBounds &cone_b);
|
|
|
|
/* --------------------------------------------------------------------
|
|
* Light Tree Construction
|
|
*
|
|
* The light tree construction is based on PBRT's BVH construction.
|
|
*/
|
|
|
|
/* Light Tree uses the bounding box, the orientation bounding cone, and the energy of a cluster to
|
|
* compute the Surface Area Orientation Heuristic (SAOH). */
|
|
struct LightTreeMeasure {
|
|
BoundBox bbox = BoundBox::empty;
|
|
OrientationBounds bcone = OrientationBounds::empty;
|
|
float energy = 0.0f;
|
|
|
|
enum empty_t { empty = 0 };
|
|
|
|
__forceinline LightTreeMeasure() = default;
|
|
|
|
__forceinline LightTreeMeasure(empty_t) {}
|
|
|
|
__forceinline LightTreeMeasure(const BoundBox &bbox,
|
|
const OrientationBounds &bcone,
|
|
const float &energy)
|
|
: bbox(bbox), bcone(bcone), energy(energy)
|
|
{
|
|
}
|
|
|
|
__forceinline LightTreeMeasure(const LightTreeMeasure &other)
|
|
: bbox(other.bbox), bcone(other.bcone), energy(other.energy)
|
|
{
|
|
}
|
|
|
|
__forceinline bool is_zero() const
|
|
{
|
|
return energy == 0;
|
|
}
|
|
|
|
__forceinline void add(const LightTreeMeasure &measure)
|
|
{
|
|
if (!measure.is_zero()) {
|
|
bbox.grow(measure.bbox);
|
|
bcone = merge(bcone, measure.bcone);
|
|
energy += measure.energy;
|
|
}
|
|
}
|
|
|
|
/* Taken from Eq. 2 in the paper. */
|
|
__forceinline float calculate()
|
|
{
|
|
if (is_zero()) {
|
|
return 0.0f;
|
|
}
|
|
|
|
float area = bbox.area();
|
|
float area_measure = area == 0 ? len(bbox.size()) : area;
|
|
return energy * area_measure * bcone.calculate_measure();
|
|
}
|
|
|
|
__forceinline void reset()
|
|
{
|
|
*this = {};
|
|
}
|
|
|
|
bool transform(const Transform &tfm)
|
|
{
|
|
float scale_squared;
|
|
if (transform_uniform_scale(tfm, scale_squared)) {
|
|
bbox = bbox.transformed(&tfm);
|
|
bcone.axis = transform_direction(&tfm, bcone.axis) * inversesqrtf(scale_squared);
|
|
energy *= scale_squared;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
LightTreeMeasure operator+(const LightTreeMeasure &a, const LightTreeMeasure &b);
|
|
|
|
struct LightTreeNode;
|
|
|
|
/* Light Linking. */
|
|
struct LightTreeLightLink {
|
|
/* Bitmask for membership of primitives in this node. */
|
|
uint64_t set_membership = 0;
|
|
|
|
/* When all primitives below this node have identical light set membership, this
|
|
* part of the light tree can be shared between specialized trees. */
|
|
bool shareable = true;
|
|
int shared_node_index = -1;
|
|
|
|
LightTreeLightLink() = default;
|
|
LightTreeLightLink(const uint64_t set_membership) : set_membership(set_membership) {}
|
|
|
|
void add(const uint64_t prim_set_membership)
|
|
{
|
|
if (set_membership == 0) {
|
|
set_membership = prim_set_membership;
|
|
}
|
|
else if (prim_set_membership != set_membership) {
|
|
set_membership |= prim_set_membership;
|
|
shareable = false;
|
|
}
|
|
}
|
|
|
|
void add(const LightTreeLightLink &other)
|
|
{
|
|
if (set_membership == 0) {
|
|
set_membership = other.set_membership;
|
|
shareable = other.shareable;
|
|
}
|
|
else if (other.set_membership != set_membership) {
|
|
set_membership |= other.set_membership;
|
|
shareable = false;
|
|
}
|
|
else if (!other.shareable) {
|
|
shareable = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
LightTreeLightLink operator+(const LightTreeLightLink &a, const LightTreeLightLink &b);
|
|
|
|
/* Light Tree Emitter
|
|
* An emitter is a built-in light, an emissive mesh, or an emissive triangle. */
|
|
struct LightTreeEmitter {
|
|
/* If the emitter is a mesh, point to the root node of its subtree. */
|
|
unique_ptr<LightTreeNode> root;
|
|
|
|
union {
|
|
int light_id; /* Index into device lights array. */
|
|
int prim_id; /* Index into an object's local triangle index. */
|
|
};
|
|
|
|
int object_id;
|
|
float3 centroid;
|
|
uint64_t light_set_membership;
|
|
|
|
LightTreeMeasure measure;
|
|
|
|
LightTreeEmitter(Object *object, int object_id); /* Mesh emitter. */
|
|
LightTreeEmitter(Scene *scene, int prim_id, int object_id, bool with_transformation = false);
|
|
|
|
__forceinline bool is_mesh() const
|
|
{
|
|
return root != nullptr;
|
|
};
|
|
|
|
__forceinline bool is_triangle() const
|
|
{
|
|
return !is_mesh() && prim_id >= 0;
|
|
};
|
|
|
|
__forceinline bool is_light() const
|
|
{
|
|
return !is_mesh() && light_id < 0;
|
|
};
|
|
};
|
|
|
|
/* Light Tree Bucket
|
|
* Struct used to determine splitting costs in the light BVH. */
|
|
struct LightTreeBucket {
|
|
LightTreeMeasure measure;
|
|
LightTreeLightLink light_link;
|
|
int count = 0;
|
|
static const int num_buckets = 12;
|
|
|
|
LightTreeBucket() = default;
|
|
|
|
LightTreeBucket(const LightTreeMeasure &measure,
|
|
const LightTreeLightLink &light_link,
|
|
const int &count)
|
|
: measure(measure), light_link(light_link), count(count)
|
|
{
|
|
}
|
|
|
|
void add(const LightTreeEmitter &emitter)
|
|
{
|
|
measure.add(emitter.measure);
|
|
light_link.add(emitter.light_set_membership);
|
|
count++;
|
|
}
|
|
};
|
|
|
|
LightTreeBucket operator+(const LightTreeBucket &a, const LightTreeBucket &b);
|
|
|
|
/* Light Tree Node */
|
|
struct LightTreeNode {
|
|
LightTreeMeasure measure;
|
|
LightTreeLightLink light_link;
|
|
uint bit_trail;
|
|
int object_id;
|
|
|
|
/* A bitmask of `LightTreeNodeType`, as in the building process an instance node can also be a
|
|
* leaf or an inner node. */
|
|
int type;
|
|
|
|
struct Leaf {
|
|
/* The number of emitters a leaf node stores. */
|
|
int num_emitters = -1;
|
|
/* Index to first emitter. */
|
|
int first_emitter_index = -1;
|
|
};
|
|
|
|
struct Inner {
|
|
/* Inner node has two children. */
|
|
unique_ptr<LightTreeNode> children[2];
|
|
};
|
|
|
|
struct Instance {
|
|
LightTreeNode *reference = nullptr;
|
|
};
|
|
|
|
std::variant<Leaf, Inner, Instance> variant_type;
|
|
|
|
LightTreeNode(const LightTreeMeasure &measure, const uint &bit_trial)
|
|
: measure(measure), bit_trail(bit_trial), variant_type(Inner())
|
|
{
|
|
type = LIGHT_TREE_INNER;
|
|
}
|
|
|
|
~LightTreeNode() = default;
|
|
|
|
__forceinline void add(const LightTreeEmitter &emitter)
|
|
{
|
|
measure.add(emitter.measure);
|
|
light_link.add(emitter.light_set_membership);
|
|
}
|
|
|
|
__forceinline Leaf &get_leaf()
|
|
{
|
|
return std::get<Leaf>(variant_type);
|
|
}
|
|
|
|
__forceinline const Leaf &get_leaf() const
|
|
{
|
|
return std::get<Leaf>(variant_type);
|
|
}
|
|
|
|
__forceinline Inner &get_inner()
|
|
{
|
|
return std::get<Inner>(variant_type);
|
|
}
|
|
|
|
__forceinline const Inner &get_inner() const
|
|
{
|
|
return std::get<Inner>(variant_type);
|
|
}
|
|
|
|
__forceinline Instance &get_instance()
|
|
{
|
|
return std::get<Instance>(variant_type);
|
|
}
|
|
|
|
__forceinline const Instance &get_instance() const
|
|
{
|
|
return std::get<Instance>(variant_type);
|
|
}
|
|
|
|
void make_leaf(const int first_emitter_index, const int num_emitters)
|
|
{
|
|
variant_type = Leaf();
|
|
Leaf &leaf = get_leaf();
|
|
|
|
leaf.first_emitter_index = first_emitter_index;
|
|
leaf.num_emitters = num_emitters;
|
|
type = LIGHT_TREE_LEAF;
|
|
}
|
|
|
|
void make_distant(const int first_emitter_index, const int num_emitters)
|
|
{
|
|
variant_type = Leaf();
|
|
Leaf &leaf = get_leaf();
|
|
|
|
leaf.first_emitter_index = first_emitter_index;
|
|
leaf.num_emitters = num_emitters;
|
|
type = LIGHT_TREE_DISTANT;
|
|
}
|
|
|
|
void make_instance(LightTreeNode *reference, const int object_id)
|
|
{
|
|
variant_type = Instance();
|
|
Instance &instance = get_instance();
|
|
|
|
instance.reference = reference;
|
|
this->object_id = object_id;
|
|
type = LIGHT_TREE_INSTANCE;
|
|
}
|
|
|
|
LightTreeNode *get_reference()
|
|
{
|
|
assert(is_instance());
|
|
if (type == LIGHT_TREE_INSTANCE) {
|
|
return get_instance().reference;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
__forceinline bool is_instance() const
|
|
{
|
|
return type & LIGHT_TREE_INSTANCE;
|
|
}
|
|
|
|
__forceinline bool is_leaf() const
|
|
{
|
|
return type & LIGHT_TREE_LEAF;
|
|
}
|
|
|
|
__forceinline bool is_inner() const
|
|
{
|
|
return type & LIGHT_TREE_INNER;
|
|
}
|
|
|
|
__forceinline bool is_distant() const
|
|
{
|
|
return type == LIGHT_TREE_DISTANT;
|
|
}
|
|
};
|
|
|
|
/* Light BVH
|
|
*
|
|
* BVH-like data structure that keeps track of lights
|
|
* and considers additional orientation and energy information */
|
|
class LightTree {
|
|
unique_ptr<LightTreeNode> root_;
|
|
|
|
/* Local lights, distant lights and mesh lights are added to separate vectors for light tree
|
|
* construction. They are all considered as `emitters_`. */
|
|
vector<LightTreeEmitter> emitters_;
|
|
vector<LightTreeEmitter> local_lights_;
|
|
vector<LightTreeEmitter> distant_lights_;
|
|
vector<LightTreeEmitter> mesh_lights_;
|
|
|
|
std::unordered_map<Mesh *, int> offset_map_;
|
|
|
|
Progress &progress_;
|
|
|
|
uint max_lights_in_leaf_;
|
|
|
|
public:
|
|
std::atomic<int> num_nodes = 0;
|
|
size_t num_triangles = 0;
|
|
|
|
/* Bitmask of receiver light sets used. Default set is always used. */
|
|
uint64_t light_link_receiver_used = 1;
|
|
|
|
/* An inner node itself or its left and right child. */
|
|
enum Child {
|
|
self = -1,
|
|
left = 0,
|
|
right = 1,
|
|
};
|
|
|
|
LightTree(Scene *scene, DeviceScene *dscene, Progress &progress, uint max_lights_in_leaf);
|
|
|
|
/* 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++;
|
|
return make_unique<LightTreeNode>(measure, bit_trial);
|
|
}
|
|
|
|
size_t num_emitters()
|
|
{
|
|
return emitters_.size();
|
|
}
|
|
|
|
const LightTreeEmitter *get_emitters() const
|
|
{
|
|
return emitters_.data();
|
|
}
|
|
|
|
private:
|
|
/* Thread. */
|
|
TaskPool task_pool;
|
|
/* Do not spawn a thread if less than this amount of emitters are to be processed. */
|
|
enum { MIN_EMITTERS_PER_THREAD = 4096 };
|
|
|
|
void recursive_build(Child child,
|
|
LightTreeNode *inner,
|
|
int start,
|
|
int end,
|
|
LightTreeEmitter *emitters,
|
|
uint bit_trail,
|
|
int depth);
|
|
|
|
bool should_split(LightTreeEmitter *emitters,
|
|
const int start,
|
|
int &middle,
|
|
const int end,
|
|
LightTreeMeasure &measure,
|
|
LightTreeLightLink &light_link,
|
|
int &split_dim);
|
|
|
|
/* Check whether the light tree can use this triangle as light-emissive. */
|
|
bool triangle_usable_as_light(Mesh *mesh, int prim_id);
|
|
|
|
/* Add all the emissive triangles of a mesh to the light tree. */
|
|
void add_mesh(Scene *scene, Mesh *mesh, int object_id);
|
|
};
|
|
|
|
CCL_NAMESPACE_END
|
|
|
|
#endif /* __LIGHT_TREE_H__ */
|