Coder Social home page Coder Social logo

madmann91 / bvh Goto Github PK

View Code? Open in Web Editor NEW
831.0 17.0 80.0 1.04 MB

A modern C++ BVH construction and traversal library

License: MIT License

CMake 1.14% C++ 98.35% C 0.51%
header-only bvh construction-algorithms traversal-algorithms cpp20 raytracing

bvh's People

Contributors

aodq avatar jeffamstutz avatar madmann91 avatar newr5 avatar tay10r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bvh's Issues

MSVC bit field packing

Bit field packing is implementation dependent. Unfortunately, you could argue MSVC is not as smart as GCC (or whatever you developed for) in terms of packing: https://docs.microsoft.com/en-us/cpp/cpp/cpp-bit-fields?redirectedfrom=MSDN&view=msvc-160

For instance, sizeof(Bvh::Node) ends up at 72 instead of 64 bytes because the packed in bool effectively becomes an unpacked IndexType. Switching to a uniform type across the bit fields will fix the issue:

IndexType is_leaf : 1;
IndexType primitive_count : sizeof(IndexType) * CHAR_BIT - 1;
IndexType first_child_or_primitive;

Obviously has the draw back of losing the explicit bool type. There may be another way to get the desired packing out of MSVC, but I am not aware of it.

Not sure if/how you want to handle this, so didn't do a PR.

Indexed Meshes

Are they not supported? I use indexed meshes because it's faster applying transformations to them. The difficulty is that this library seems to require that each primitive is an instanced class.

Specifically, it's arranged like this.

template <typename Float, typename Index>
struct Mesh final {
  Eigen::Matrix<Float, 3, Eigen::Dynamic> vertices;
  Eigen::Matrix<Float, 3, Eigen::Dynamic> normals;
  Eigen::Matrix<Float, 2, Eigen::Dynamic> tex_coords;

  /* +3 vertices, +3 normals, +3 texture coordinates */
  Eigen::Matrix<Index, 9, Eigen::Dynamic> faces;
};

I don't think this is too uncommon, it's how libigl seems to handle meshes as well.

Numerical issue at triangle edges?

image

Here I'm using 32bit floats. The test code generates a depth image using SingleRayTraverser. Seems that a few pixels missed intersections at triangle edges. I'm not very sure the is issue is with bvh. Just trying to ask and see if there's any suggestion. Thanks.

Two questions: MB and primitive splitting

  1. Is there a way to deal with motion blur or deforming primitives?

  2. For spatial splits builder, what is the general approach to splitting an arbitrary primitive? Just split bbox
    at split point?

Ability to re-use BVH allocations?

Currently, if I'm reading the builders correctly, they always allocate a new bvh instance when building. It would be nice to have the option of re-using allocations.

Normalizing a bvh::Triangle's normal...

Hi,
I'm a little further in my ray tracing application and now want to use the normals of the triangles to calculate the angle of incidence of a ray.

auto dirDotN = dot(rayDirection, triangle.n);
auto incidenceAngle = std::acos(dirDotN);

I discovered that the normal inside the bvh::Triangle is not normalized after it has been calculated.
I was under the impression that when a vector is used to express a direction (and a triangle's normal is such a vector), it should always be normalized...
I tried to add a normalize() call within the Triangle's constructor:

n = normalize(LeftHandedNormal ? cross(e1, e2) : cross(e2, e1));

but then the triangle's intersection routine doesn't work anymore.
(I used the same test program as I did in #18, and it doesn't report any hits).

Of course, I can add another member that contains the normalized normal, but I would like to understand why the intersection routine apparently cannot cope with a normalized normal...

Could you please enlighten me on this subject?

No intersections when a ray direction value is 0

Right now I see that if I cast rays where ray.x = 0 or ray.z = 0, the BVH tree does not return a triangle. This could affect ray.y but I have not really tried that yet. Adding epsilon in those corner cases fixes the problem but there's probably a better solution.

BVH tree
no BVH tree, just raycast against all triangles
intersection test always returns true, closest intersector
intersection test always returns true, any intersector

In the benchmark, if I force dir.x = 0 (then normalize afterwards), nothing at all renders to the screen:

benchmark.cpp:121

            Ray ray(camera.eye, bvh::normalize(image_u * u + image_v * v + dir));
            ray.direction.values[0] = 0.0f;
            ray.direction = bvh::normalize(ray.direction);

without modification
setting value to 0.000001f
setting value to 0.0f

This seems to vary by each model, models with high triangle density don't seem to suffer from this issue.

traverse_in_parallel

The below function processes from node of leaf in order.
However, it looks like it is continuing when is_leaf is true.
Is this correct?

bottom_up_algorithm.hpp
void traverse_in_parallel
...
// Only process leaves
if (MaintainChildIndices ? children[i] != 0 : bvh.nodes[i].is_leaf)
continue;
...

Optimize node index serialization

Each node index can be packed into a single Node::Index::Type like so:

using Node_t = decltype(node);

//Serialize:
Node_t::Index::Type serialized = (node.index.first_id << Node_t::prim_count_bits) |
                                 node.index.prim_count;

//Deserialize:
node.index.first_id = serialized >> Node_t::prim_count_bits;
node.index.prim_count = serialized & ((1 << Node_t::prim_count_bits) - 1);

If you're worried about backwards compatibility or users who prefer speed over size, you can hide the optimization behind a bool template parameter.

Question: Which construction method for best single core performance?

I'm writing a JavaScript BVH library and using your implementation as a reference. Not for ray tracing, but for picking and collisions. The library would be intended to run in a Worker (a separate long-lived process, much heavier than a thread and only one "CPU" for it)

I'd rather just use WASM and your library directly, but lack of easy shared memory between WASM and JS means copying all the vertex data over from JS to WASM (since the WebGL stuff is in JS) which is tricky because browsers are more memory-constrained than desktop software.

Do you have a suggestion for which construction method to use as a reference? Construction speed is a priority. I'm thinking bvh::LocallyOrderedClusteringBuilder, but what do you think?

Data Race in bvh.hpp

Using the Coderrect Scanner coderrect.com I discovered a data race in bvh.hpp between line 42 and itself.

 40|
 41| BoundingBoxProxy& operator = (const BoundingBox<Scalar>& bbox) {
>42|     node.bounds[0] = bbox.min[0];
 43|     node.bounds[1] = bbox.max[0];
 44|     node.bounds[2] = bbox.min[1];

no intersection in some cases when there is a -0.0 direction value

Hi,
there seems to be a problem when there is a -0.0 value in the direction vector. I slightly modified your example from simple_example.cpp to reproduce this error:

I just copied the first triangle and moved it a bit further away (in the z-direction) and also changed the origin of the ray so that there should be a clear intersection:

    std::vector<Triangle> triangles;
    triangles.emplace_back(
        Vector3( 1.0, -1.0, 1.0),
        Vector3( 1.0,  1.0, 1.0),
        Vector3(-1.0,  1.0, 1.0)
    );
    triangles.emplace_back(
        Vector3( 1.0, -1.0, 2.1),
        Vector3( 1.0,  1.0, 2.1),
        Vector3(-1.0,  1.0, 2.1)
    );

    // Version 1: hit: triangle:0, distance:1, u:0.25, v:0.375
    Ray ray(
        Vector3(0.25, 0.25, 0.0),
        Vector3(0.0, 0.0, 1.0), 
        0.0,
        100.0
    );

    // Version 2: no hit
    Ray ray2(
        Vector3(0.25, 0.25, 0.0),
        Vector3(0.0, -0.0, 1.0), 
        0.0,
        100.0
    );

But there is still a hit in the second version when the second triangle is at z=2.0. I also tried RobustNodeIntersector, but with no luck :/

Adding and Removing elements

I'd like to use this library for something more akin to collision/nearest neighbors detection rather than ray-tracing. But this would require a bit more dynamism. I propose adding at least a DefaultAdder, DefaultRemover, and maybe even DefaultUpdater, possibly with alternative versions; in the style of DefaultBuilder and its alternatives. Adding and removing elements dynamically while maintaining a good tree is a hard problem I'm sure, but even a simplistic implementation would be nicer than having to roll a bad implementation myself.

Face culling options?

Are there face culling options in bvh? It seems to me that a ray can intersect a triangle no matter which direction it comes from.
I hope there can be options to skip triangles based on their winding directions. Is that easy to do?
Thanks.

Fast-BVH Updates

I ran your benchmark using a sponza model and compared it to the latest Fast-BVH after I PR I made there. I think results are a bit different at this point. Fast-BVH is now a template library and built the sponza model I had in 70 ms. In your library, it built the same model in 133 ms. Maybe you could verify the results. In my patch to Fast-BVH, I also included an .obj file renderer.

Instances

Hello!
How to use library with instances?
I need BLAS and TLAS.
Can you create example for it?
Thank you

`build()` in debug build of v2 feels slower than v1

When I updated my code to use v2, I feel that build() in debug build got slower.
Is there anything I could do to make build() faster in debug builds? I am already using low quality setting for the default builder. Thanks!

2D Traverser

This library can easily be used for 2D contexts as well. The only additional feature it would need is a traverser that takes advantage of the simpler AABB test. Here's the AABB test, where x and y are the pixel coordinates.

float intersect(float x, float y, const Bvh<float>::Node& node)
{
  if ((x < node.bounds[0])
   || (x > node.bounds[1])
   || (y < node.bounds[2])
   || (y > node.bounds[3])) {
    return std::numeric_limits<float>::infinity();
  }
  return std::min(node.bounds[4], node.bounds[5]);
}

This is how I currently use this library for a 2D renderer that I've been working on. Would you be interested in a PR for this? The only thing I'd need to do is add support for the Intersector template parameter and Statistics parameter, the rest is already written and quite simple.

Other traversal types

As it stands now, there's only one traversal algorithm. I'd like to implement the following algorithms:

  • A frustum-based ray traversal algorithm,
  • A aligned/oriented bounding-box intersection algorithm,
  • A BVH/BVH intersection algorithm.
  • Closest point detection.

pragma STDC FP_CONTRACT ON in utilities.hpp is not supported in Visual Studio 2022

The default usage of #pragma STDC FP_CONTRACT ON in utilities.hpp causes warnings in MSVC 2022:

bvh\include\bvh/utilities.hpp(41,13): error C2220: the following warning is treated as an error
bvh\include\bvh/utilities.hpp(41,13): warning C4068: unknown pragma 'STDC'
bvh\include\bvh/utilities.hpp(50,13): warning C4068: unknown pragma 'STDC'

Undefined behavior in morton_split

Hello!
There is an undefined behavior in file bvh/include/bvh/morton.hpp, at this function

template <typename Morton>
Morton morton_split(Morton x) {
    constexpr size_t log_bits = round_up_log2(sizeof(Morton) * CHAR_BIT);
    auto mask = Morton(-1);
    for (size_t i = log_bits, n = 1 << log_bits; i > 0; --i, n >>= 1) {
        mask = (mask | (mask << n)) & ~(mask << (n / 2));
        x = (x | (x << n)) & mask;
    }
    return x;
}

mask << n and x << n is a shift of n-bit operand on n bits. This is an undefined behavior, because of

if the value of the right operand is negative or is greater or equal to the number of bits in the promoted left operand, the behavior is undefined.
(https://en.cppreference.com/w/cpp/language/operator_arithmetic)

I found it when i tried to run this code with a clang. All my morton codes were 0.

LocallyOrderedClustering does not terminate

Unfortunately the LocallyOrderedClustering does not terminate with the lucy 3D model, maybe some race condition?! I am on Linux with gcc 10.2. I'll will debug this a bit more, but in case you want to try to reproduce, I have uploaded a minimal reproducer here.

Measuring BVH Quality

Just out of curiosity, is there any metrics that this library can calculate that indicate the quality of the generated BVHs aside from the traversal performance? Perhaps like the accumulative amount of empty space between parent and sub nodes? I don't think I've seen this measured before in other projects, but it seems like it would be a useful metric for comparisons between algorithms.

Library does not handle rays parallel to axes

I get very good performance out of your library for general ray tracing usage. But when ray tracing with vertical rays to get height of a mesh at given X,Y position, performance is terrible. Instead of visiting just a few leafs algorithm often visits almost all leafs.

Do you have any suggestions how to fix or mitigate this?

bvh_always_inline inline

MSVC gives this warning from bvh/vector.hpp:

warning C4141: 'inline': used more than once when it encounters bvh_always_inline inline.

To me bvh_always_inline inline seems redundant, would it be okay to make it just bvh_always_inline?

Slow performance with axis aligned ray

Great lib!

However I get very low performance when casting axis-aligned rays.
It takes 7 seconds or so, instead of ~10ms (~30k rays on a ~3M meshes, the mesh has axis aligned triangles as well).

When I add a small epsilon to the ray to make sure all components are non null, things become fast again.

I didn't dig the issue further.
I can just say there is no division by 0 here

auto inv_det = Scalar(1.0) / dot(n, ray.direction);

Dot Product Elimination in Sphere Intersector

auto a = dot(ray.direction, ray.direction);

I believe there's a potential optimization here. If you assume the ray direction is normalized, the length of the vector will always equal 1. Since sqrt(1) is always 1, you can probably just leave it as:

const Scalar a = 1;

Embree still takes the dot product, perhaps because they don't assume the ray direction is normalized. Since there's some template magic at work here, you could probably do something like:

template <typename Scalar, bool NormalizedRayDir>
struct Sphere final {
  /* ... */
  static Scalar get_ray_direction_squared_length(const Ray<Scalar> &ray);
};

template <typename Scalar>
Scalar Sphere<Scalar, true>::get_ray_direction_squared_length(const Ray<Scalar>&)
{
  return 1;
}

template <typename Scalar>
Scalar Sphere<Scalar, false>::get_ray_direction_squared_length(const Ray<Scalar>& ray)
{
  return dot(ray.direction, ray.direction);
}

Maybe it would also do well as a template parameter for Ray instead, hard to say.

Closest Point

Hi, is there any way to adapt this library to do closest point / distance queries in addition to pure ray casting?

Make OpenMP optional

OpenMP requires separate redistributable for MSVC, an option to remove the dependency or use C++11 threads would be appreciated (even at the cost of slower BVH building).

global bounding box does not seem to work

Hey, so ever since commit d2f5e2f , which added the global bounding box, my renderer does not seem to render anything under the same scene/camera settings. I'm using bvh::compute_bounding_boxes_union, yet despite this, I can't get it to properly intersect with any triangle.

  auto globalBbox =
    bvh::compute_bounding_boxes_union(
      boundingBoxes.data()
    , self->triangles.size()
    );

  { // -- build tree
    bvh::BinnedSahBuilder<decltype(self->boundingVolume), 32>
      boundingVolumeBuilder { self->boundingVolume };

    boundingVolumeBuilder.build(
      globalBbox, boundingBoxes.data(), centers.data(), self->triangles.size()
    );
  }

This code works (without globalBbox references) before the aforementioned commit. Let me know if I'm doing something wrong here.

std::fill performance issues under visual c++

Hello, sorry for polluting the other thread: I just subscribed to github or this very issue and didn't realize I was replying to another issue :D

Here my original message.

Hello, great library!
I just got back to my old raytracing project (abandoned 10 years ago) and decided that it would have been nice to work on the performances, horrible at best. I've started replacing my quick BIH implementation with a BVH but decided to switch to an external lib and found this one. I could make it work, but performances were exactly the same as with my BIH:

bvh::LocallyOrderedClusteringBuilder<bvh::Bvh, int> builder(bvh);
builder.build(bvh::BoundingBox(MasterBVHPrimitive::toVector3(worldbb.min), MasterBVHPrimitive::toVector3(worldbb.max)), bboxes, centers, shapes.size());
bvh::LeafCollapserbvh::Bvh collapser(bvh);
collapser.collapse();

I've started profiling the execution and found that 35% of the time was being spent inside std::fill, and I could track it back to
struct Vector {
Scalar values[N];

Vector() = default;
bvh__always_inline__ Vector(Scalar s) { std::fill(values, values + N, s); }
As I'm working with 3-dimensional vectors, I then replaced the call to fill with:
values[0] = values[1] = values[2] = s;

My test scene rendering time went from 8 seconds to 3!

Am I using the lib the wrong way? Is there an option that would disable/improve that initialization? Would my change break something somewhere?

Thank you!

Comparison with fcpw

Hey,

first, great work with this lib, really like using it!

I think it would be great to have a benchmark comparing with fcpw,
which claims to be a very fast BVH implementation. It claims to achieve very fast query times,
by (presumably) vectorizing the slab test with enoki. My feeling is that the tree quality is much more
important, since queries should be latency bound.

Missing triangles in output

Hello, me again!
In the test scene I'm currently using there are a few triangles apparently missing. I was sure it was a problem with the scene file (exported from blender), with Assimp (the lib I use to load the scene) or my code. I've started to play a bit with the mesh in blender (simplify, remove vertices, triangulate etc), then by exporting to different file formats. Nothing changed. Then I tried to render the scene with my BIH, and, to my surprise, the scene was fine.
That's how it looks with my BIH
BIH

and with the BVH:
BVH
(sorry for the bad quality, hope will do anyway). Please note the fireplace, or the corner opposite to the point of view. There are a couple more, but you can't see it in the that image (I can of course provide you with better ones).
I understand that this is not much to work with, but thought to report it should you be interested in analyzing the issue. Let mw know if you want the collada/gltf/blend file of the scene.

That's how I create the bvh:

	void Preprocess()
	{
		//bvh::SweepSahBuilder<bvh::Bvh<real>> builder(bvh);
		bvh::LocallyOrderedClusteringBuilder<bvh::Bvh<real>, int>  builder(bvh);

		//Adapt data format
		bvh::BoundingBox<real>* bboxes = new bvh::BoundingBox<real>[shapes.size()];
		bvh::Vector3<real>* centers = new bvh::Vector3<real>[shapes.size()];

		qlaabox<real> worldbb;
		for (int i = 0; i < shapes.size(); ++i) {
			qlaabox<real> bb = shapes[i].origShape->GetBoundingBox();
			bboxes[i] = bvh::BoundingBox<real>(MasterBVHPrimitive::toVector3(bb.min), MasterBVHPrimitive::toVector3(bb.max));
			worldbb.Union(bb);
			centers[i] = MasterBVHPrimitive::toVector3(bb.Center());
		}
		
		builder.build(bvh::BoundingBox<real>(MasterBVHPrimitive::toVector3(worldbb.min), MasterBVHPrimitive::toVector3(worldbb.max)), bboxes, centers, shapes.size());
		bvh::LeafCollapser<bvh::Bvh<real>> collapser(bvh);
		collapser.collapse();
		std::cout << "Radius: " << builder.search_radius << std::endl;
		std::cout << "Shapes count : " << shapes.size() << std::endl;
		//std::cout << "Tree depth : " << TreeDepth(root) << std::endl;
		std::cout << "Node count : " << bvh.node_count << std::endl;
		//std::cout << "Leaf count : " << LeafCount(root) << std::endl;
		/*std::cout << "Average primitives/leaf : " << ((real)ShapesCount(root)) / LeafCount(root) << std::endl;
		std::cout << "Empty leaves : " << EmptyLeaves(root) << std::endl;
		std::cout << "Max shapes : " << MaxShapes(root) << std::endl;*/
	}

And the traversal code:

	bool Intersect(const ray<vector4<real>>& r, SurfacePoint& hitdesc) const
	{
		// Intersect a ray with the data structure
		Ray ray = MasterBVHPrimitive::toRay(r);
		
		bvh::ClosestPrimitiveIntersector<bvh::Bvh<real>, MasterBVHPrimitive> primitive_intersector(bvh, shapes.data());
		bvh::SingleRayTraverser<bvh::Bvh<real>> traverser(bvh);

		auto hit = traverser.traverse(ray, primitive_intersector);
		if (hit)
		{
			hitdesc = hit->intersection.hitdesc;
			return true;
		}
		else
			return false;
	}

Thank you and regards,
Alessandro

Insert primitives into BVH

Hi, I'm wondering if the BVH created using this library can insert primitives after construction, or whether the BVH would need to be reconstructed from scratch if new primitives are added to the scene?

Additionally, I'm curious whether it's possible to query the BVH with an AABB to find which nodes overlap with the AABB (I think this may be in the plans in #30)

raw, bvh node data to upload to the gpu

I am currently workin on a raytrcer in opengl, do ou have a api to get the raw bvh node data (and any other essential data) to be uploaded to the gpu as a ssbo ?

Height Field Support

Any interest in supporting acceleration structures for height fields?

There's a paper that goes into describing the process. I'm currently using this library for calculating ambient occlusion in terrains, and this approach may lead to a performance boost.

Even so much as a dedicated BVH builder for height fields would be really cool to see.

CMake Version

I noticed that the CMake version got bumped up again to version 3.13.5
This breaks the build for anyone who has the standard distribution of CMake on the latest Ubuntu (19.10), which is CMake version 3.13.4.

Was this bumped up because of target_link_options? That's supported by 3.13.4, even though the documentation doesn't specify it.

It might also make the Travis build simpler, since you won't have to install a new CMake version.

Calculation of the normal of bvh::Triangle

Hi,
I'm trying to understand why the normal of bvh::Triangle is calculated differently than the 'standard' way...
By 'standard', I am referring to the way of calculating the normal of a trial that I found described on many internet sources...
If a triangle is given by vertices p0, p1, p2, the standard way states that the normal is calculated as follows:
normal = cross(p1 - p0, p2 - p0)
If I understand correctly, in bvh::Triangle the edges are stored e1 = (p0 - p1) and e2 = (p2 - p0).
In the constructor n is calculated as cross(e1,e2), which makes the normal point in the opposite direction than the normal of the 'standard' case.

Could you please enlighten me on this subject?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.