blender/intern/cycles/bvh/bvh_build.cpp
Ton Roosendaal da376e0237 Cycles render engine, initial commit. This is the engine itself, blender modifications and build instructions will follow later.
Cycles uses code from some great open source projects, many thanks them:

* BVH building and traversal code from NVidia's "Understanding the Efficiency of Ray Traversal on GPUs":
http://code.google.com/p/understanding-the-efficiency-of-ray-traversal-on-gpus/
* Open Shading Language for a large part of the shading system:
http://code.google.com/p/openshadinglanguage/
* Blender for procedural textures and a few other nodes.
* Approximate Catmull Clark subdivision from NVidia Mesh tools:
http://code.google.com/p/nvidia-mesh-tools/
* Sobol direction vectors from:
http://web.maths.unsw.edu.au/~fkuo/sobol/
* Film response functions from:
http://www.cs.columbia.edu/CAVE/software/softlib/dorf.php
2011-04-27 11:58:34 +00:00

546 lines
15 KiB
C++

/*
* Adapted from code copyright 2009-2010 NVIDIA Corporation
* Modifications Copyright 2011, Blender Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bvh_build.h"
#include "bvh_node.h"
#include "bvh_params.h"
#include "bvh_sort.h"
#include "mesh.h"
#include "object.h"
#include "scene.h"
#include "util_algorithm.h"
#include "util_foreach.h"
#include "util_progress.h"
#include "util_time.h"
CCL_NAMESPACE_BEGIN
/* Constructor / Destructor */
BVHBuild::BVHBuild(const vector<Object*>& objects_,
vector<int>& prim_index_, vector<int>& prim_object_,
const BVHParams& params_, Progress& progress_)
: objects(objects_),
prim_index(prim_index_),
prim_object(prim_object_),
params(params_),
progress(progress_),
progress_start_time(0.0)
{
spatial_min_overlap = 0.0f;
progress_num_duplicates = 0;
}
BVHBuild::~BVHBuild()
{
}
/* Adding References */
void BVHBuild::add_reference_mesh(NodeSpec& root, Mesh *mesh, int i)
{
for(uint j = 0; j < mesh->triangles.size(); j++) {
Mesh::Triangle t = mesh->triangles[j];
Reference ref;
ref.prim_index = j;
ref.prim_object = i;
for(int k = 0; k < 3; k++) {
float3 pt = mesh->verts[t.v[k]];
ref.bounds.grow(pt);
}
references.push_back(ref);
root.bounds.grow(ref.bounds);
}
}
void BVHBuild::add_reference_object(NodeSpec& root, Object *ob, int i)
{
Reference ref;
ref.prim_index = -1;
ref.prim_object = i;
ref.bounds = ob->bounds;
references.push_back(ref);
root.bounds.grow(ref.bounds);
}
void BVHBuild::add_references(NodeSpec& root)
{
/* init root spec */
root.num = 0;
root.bounds = BoundBox();
/* add objects */
int i = 0;
foreach(Object *ob, objects) {
if(params.top_level) {
if(ob->mesh->transform_applied)
add_reference_mesh(root, ob->mesh, i);
else
add_reference_object(root, ob, i);
}
else
add_reference_mesh(root, ob->mesh, i);
i++;
if(progress.get_cancel()) return;
}
/* happens mostly on empty meshes */
if(!root.bounds.valid())
root.bounds.grow(make_float3(0.0f, 0.0f, 0.0f));
root.num = references.size();
}
/* Build */
BVHNode* BVHBuild::run()
{
NodeSpec root;
/* add references */
add_references(root);
if(progress.get_cancel()) return NULL;
/* init spatial splits */
if(params.top_level) /* todo: get rid of this */
params.use_spatial_split = false;
spatial_min_overlap = root.bounds.area() * params.spatial_split_alpha;
spatial_right_bounds.clear();
spatial_right_bounds.resize(max(root.num, (int)BVHParams::NUM_SPATIAL_BINS) - 1);
/* init progress updates */
progress_num_duplicates = 0;
progress_start_time = time_dt();
/* build recursively */
return build_node(root, 0, 0.0f, 1.0f);
}
void BVHBuild::progress_update(float progress_start, float progress_end)
{
if(time_dt() - progress_start_time < 0.25f)
return;
float duplicates = (float)progress_num_duplicates/(float)references.size();
string msg = string_printf("Building BVH %.0f%%, duplicates %.0f%%",
progress_start*100.0f, duplicates*100.0f);
progress.set_substatus(msg);
progress_start_time = time_dt();
}
BVHNode* BVHBuild::build_node(const NodeSpec& spec, int level, float progress_start, float progress_end)
{
/* progress update */
progress_update(progress_start, progress_end);
if(progress.get_cancel()) return NULL;
/* small enough or too deep => create leaf. */
if(spec.num <= params.min_leaf_size || level >= BVHParams::MAX_DEPTH)
return create_leaf_node(spec);
/* find split candidates. */
float area = spec.bounds.area();
float leafSAH = area * params.triangle_cost(spec.num);
float nodeSAH = area * params.node_cost(2);
ObjectSplit object = find_object_split(spec, nodeSAH);
SpatialSplit spatial;
if(params.use_spatial_split && level < BVHParams::MAX_SPATIAL_DEPTH) {
BoundBox overlap = object.left_bounds;
overlap.intersect(object.right_bounds);
if(overlap.area() >= spatial_min_overlap)
spatial = find_spatial_split(spec, nodeSAH);
}
/* leaf SAH is the lowest => create leaf. */
float minSAH = min(min(leafSAH, object.sah), spatial.sah);
if(minSAH == leafSAH && spec.num <= params.max_leaf_size)
return create_leaf_node(spec);
/* perform split. */
NodeSpec left, right;
if(params.use_spatial_split && minSAH == spatial.sah)
do_spatial_split(left, right, spec, spatial);
if(!left.num || !right.num)
do_object_split(left, right, spec, object);
/* create inner node. */
progress_num_duplicates += left.num + right.num - spec.num;
float progress_mid = lerp(progress_start, progress_end, (float)right.num / (float)(left.num + right.num));
BVHNode* rightNode = build_node(right, level + 1, progress_start, progress_mid);
if(progress.get_cancel()) {
if(rightNode) rightNode->deleteSubtree();
return NULL;
}
BVHNode* leftNode = build_node(left, level + 1, progress_mid, progress_end);
if(progress.get_cancel()) {
if(leftNode) leftNode->deleteSubtree();
return NULL;
}
return new InnerNode(spec.bounds, leftNode, rightNode);
}
BVHNode *BVHBuild::create_object_leaf_nodes(const Reference *ref, int num)
{
if(num == 0) {
BoundBox bounds;
return new LeafNode(bounds, 0, 0);
}
else if(num == 1) {
prim_index.push_back(ref[0].prim_index);
prim_object.push_back(ref[0].prim_object);
return new LeafNode(ref[0].bounds, prim_index.size()-1, prim_index.size());
}
else {
int mid = num/2;
BVHNode *leaf0 = create_object_leaf_nodes(ref, mid);
BVHNode *leaf1 = create_object_leaf_nodes(ref+mid, num-mid);
BoundBox bounds;
bounds.grow(leaf0->m_bounds);
bounds.grow(leaf1->m_bounds);
return new InnerNode(bounds, leaf0, leaf1);
}
}
BVHNode* BVHBuild::create_leaf_node(const NodeSpec& spec)
{
vector<int>& p_index = prim_index;
vector<int>& p_object = prim_object;
BoundBox bounds;
int num = 0;
for(int i = 0; i < spec.num; i++) {
if(references.back().prim_index != -1) {
p_index.push_back(references.back().prim_index);
p_object.push_back(references.back().prim_object);
bounds.grow(references.back().bounds);
references.pop_back();
num++;
}
}
BVHNode *leaf = NULL;
if(num > 0) {
leaf = new LeafNode(bounds, p_index.size() - num, p_index.size());
if(num == spec.num)
return leaf;
}
/* while there may be multiple triangles in a leaf, for object primitives
* we want them to be the only one, so we */
int ob_num = spec.num - num;
BVHNode *oleaf = create_object_leaf_nodes(&references.back() - (ob_num - 1), ob_num);
for(int i = 0; i < ob_num; i++)
references.pop_back();
if(leaf)
return new InnerNode(spec.bounds, leaf, oleaf);
else
return oleaf;
}
/* Object Split */
BVHBuild::ObjectSplit BVHBuild::find_object_split(const NodeSpec& spec, float nodeSAH)
{
ObjectSplit split;
const Reference *ref_ptr = &references[references.size() - spec.num];
for(int dim = 0; dim < 3; dim++) {
/* sort references */
bvh_reference_sort(references.size() - spec.num, references.size(), &references[0], dim);
/* sweep right to left and determine bounds. */
BoundBox right_bounds;
for(int i = spec.num - 1; i > 0; i--) {
right_bounds.grow(ref_ptr[i].bounds);
spatial_right_bounds[i - 1] = right_bounds;
}
/* sweep left to right and select lowest SAH. */
BoundBox left_bounds;
for(int i = 1; i < spec.num; i++) {
left_bounds.grow(ref_ptr[i - 1].bounds);
right_bounds = spatial_right_bounds[i - 1];
float sah = nodeSAH +
left_bounds.area() * params.triangle_cost(i) +
right_bounds.area() * params.triangle_cost(spec.num - i);
if(sah < split.sah) {
split.sah = sah;
split.dim = dim;
split.num_left = i;
split.left_bounds = left_bounds;
split.right_bounds = right_bounds;
}
}
}
return split;
}
void BVHBuild::do_object_split(NodeSpec& left, NodeSpec& right, const NodeSpec& spec, const ObjectSplit& split)
{
/* sort references according to split */
int start = references.size() - spec.num;
int end = references.size(); /* todo: is this right? */
bvh_reference_sort(start, end, &references[0], split.dim);
/* split node specs */
left.num = split.num_left;
left.bounds = split.left_bounds;
right.num = spec.num - split.num_left;
right.bounds = split.right_bounds;
}
/* Spatial Split */
BVHBuild::SpatialSplit BVHBuild::find_spatial_split(const NodeSpec& spec, float nodeSAH)
{
/* initialize bins. */
float3 origin = spec.bounds.min;
float3 binSize = (spec.bounds.max - origin) * (1.0f / (float)BVHParams::NUM_SPATIAL_BINS);
float3 invBinSize = 1.0f / binSize;
for(int dim = 0; dim < 3; dim++) {
for(int i = 0; i < BVHParams::NUM_SPATIAL_BINS; i++) {
SpatialBin& bin = spatial_bins[dim][i];
bin.bounds = BoundBox();
bin.enter = 0;
bin.exit = 0;
}
}
/* chop references into bins. */
for(unsigned int refIdx = references.size() - spec.num; refIdx < references.size(); refIdx++) {
const Reference& ref = references[refIdx];
float3 firstBinf = (ref.bounds.min - origin) * invBinSize;
float3 lastBinf = (ref.bounds.max - origin) * invBinSize;
int3 firstBin = make_int3(firstBinf.x, firstBinf.y, firstBinf.z);
int3 lastBin = make_int3(lastBinf.x, lastBinf.y, lastBinf.z);
firstBin = clamp(firstBin, 0, BVHParams::NUM_SPATIAL_BINS - 1);
lastBin = clamp(lastBin, firstBin, BVHParams::NUM_SPATIAL_BINS - 1);
for(int dim = 0; dim < 3; dim++) {
Reference currRef = ref;
for(int i = firstBin[dim]; i < lastBin[dim]; i++) {
Reference leftRef, rightRef;
split_reference(leftRef, rightRef, currRef, dim, origin[dim] + binSize[dim] * (float)(i + 1));
spatial_bins[dim][i].bounds.grow(leftRef.bounds);
currRef = rightRef;
}
spatial_bins[dim][lastBin[dim]].bounds.grow(currRef.bounds);
spatial_bins[dim][firstBin[dim]].enter++;
spatial_bins[dim][lastBin[dim]].exit++;
}
}
/* select best split plane. */
SpatialSplit split;
for(int dim = 0; dim < 3; dim++) {
/* sweep right to left and determine bounds. */
BoundBox right_bounds;
for(int i = BVHParams::NUM_SPATIAL_BINS - 1; i > 0; i--) {
right_bounds.grow(spatial_bins[dim][i].bounds);
spatial_right_bounds[i - 1] = right_bounds;
}
/* sweep left to right and select lowest SAH. */
BoundBox left_bounds;
int leftNum = 0;
int rightNum = spec.num;
for(int i = 1; i < BVHParams::NUM_SPATIAL_BINS; i++) {
left_bounds.grow(spatial_bins[dim][i - 1].bounds);
leftNum += spatial_bins[dim][i - 1].enter;
rightNum -= spatial_bins[dim][i - 1].exit;
float sah = nodeSAH +
left_bounds.area() * params.triangle_cost(leftNum) +
spatial_right_bounds[i - 1].area() * params.triangle_cost(rightNum);
if(sah < split.sah) {
split.sah = sah;
split.dim = dim;
split.pos = origin[dim] + binSize[dim] * (float)i;
}
}
}
return split;
}
void BVHBuild::do_spatial_split(NodeSpec& left, NodeSpec& right, const NodeSpec& spec, const SpatialSplit& split)
{
/* Categorize references and compute bounds.
*
* Left-hand side: [left_start, left_end[
* Uncategorized/split: [left_end, right_start[
* Right-hand side: [right_start, refs.size()[ */
vector<Reference>& refs = references;
int left_start = refs.size() - spec.num;
int left_end = left_start;
int right_start = refs.size();
left.bounds = right.bounds = BoundBox();
for(int i = left_end; i < right_start; i++) {
if(refs[i].bounds.max[split.dim] <= split.pos) {
/* entirely on the left-hand side */
left.bounds.grow(refs[i].bounds);
swap(refs[i], refs[left_end++]);
}
else if(refs[i].bounds.min[split.dim] >= split.pos) {
/* entirely on the right-hand side */
right.bounds.grow(refs[i].bounds);
swap(refs[i--], refs[--right_start]);
}
}
/* duplicate or unsplit references intersecting both sides. */
while(left_end < right_start) {
/* split reference. */
Reference lref, rref;
split_reference(lref, rref, refs[left_end], split.dim, split.pos);
/* compute SAH for duplicate/unsplit candidates. */
BoundBox lub = left.bounds; // Unsplit to left: new left-hand bounds.
BoundBox rub = right.bounds; // Unsplit to right: new right-hand bounds.
BoundBox ldb = left.bounds; // Duplicate: new left-hand bounds.
BoundBox rdb = right.bounds; // Duplicate: new right-hand bounds.
lub.grow(refs[left_end].bounds);
rub.grow(refs[left_end].bounds);
ldb.grow(lref.bounds);
rdb.grow(rref.bounds);
float lac = params.triangle_cost(left_end - left_start);
float rac = params.triangle_cost(refs.size() - right_start);
float lbc = params.triangle_cost(left_end - left_start + 1);
float rbc = params.triangle_cost(refs.size() - right_start + 1);
float unsplitLeftSAH = lub.area() * lbc + right.bounds.area() * rac;
float unsplitRightSAH = left.bounds.area() * lac + rub.area() * rbc;
float duplicateSAH = ldb.area() * lbc + rdb.area() * rbc;
float minSAH = min(min(unsplitLeftSAH, unsplitRightSAH), duplicateSAH);
if(minSAH == unsplitLeftSAH) {
/* unsplit to left */
left.bounds = lub;
left_end++;
}
else if(minSAH == unsplitRightSAH) {
/* unsplit to right */
right.bounds = rub;
swap(refs[left_end], refs[--right_start]);
}
else {
/* duplicate */
left.bounds = ldb;
right.bounds = rdb;
refs[left_end++] = lref;
refs.push_back(rref);
}
}
left.num = left_end - left_start;
right.num = refs.size() - right_start;
}
void BVHBuild::split_reference(Reference& left, Reference& right, const Reference& ref, int dim, float pos)
{
/* initialize references. */
left.prim_index = right.prim_index = ref.prim_index;
left.prim_object = right.prim_object = ref.prim_object;
left.bounds = right.bounds = BoundBox();
/* loop over vertices/edges. */
Object *ob = objects[ref.prim_object];
const Mesh *mesh = ob->mesh;
const int *inds = mesh->triangles[ref.prim_index].v;
const float3 *verts = &mesh->verts[0];
const float3* v1 = &verts[inds[2]];
for(int i = 0; i < 3; i++) {
const float3* v0 = v1;
int vindex = inds[i];
v1 = &verts[vindex];
float v0p = (*v0)[dim];
float v1p = (*v1)[dim];
/* insert vertex to the boxes it belongs to. */
if(v0p <= pos)
left.bounds.grow(*v0);
if(v0p >= pos)
right.bounds.grow(*v0);
/* edge intersects the plane => insert intersection to both boxes. */
if((v0p < pos && v1p > pos) || (v0p > pos && v1p < pos)) {
float3 t = lerp(*v0, *v1, clamp((pos - v0p) / (v1p - v0p), 0.0f, 1.0f));
left.bounds.grow(t);
right.bounds.grow(t);
}
}
/* intersect with original bounds. */
left.bounds.max[dim] = pos;
right.bounds.min[dim] = pos;
left.bounds.intersect(ref.bounds);
right.bounds.intersect(ref.bounds);
}
CCL_NAMESPACE_END