readme.md 3.59 KB
Newer Older
ashawkey's avatar
init  
ashawkey committed
1
2
3
4
5
6
# cuBVH

A CUDA Mesh BVH acceleration toolkit.

### Install

ashawkey's avatar
ashawkey committed
7
```bash
ashawkey's avatar
ashawkey committed
8
9
10
pip install git+https://github.com/ashawkey/cubvh

# or locally
ashawkey's avatar
ashawkey committed
11
git clone --recursive https://github.com/ashawkey/cubvh
ashawkey's avatar
init  
ashawkey committed
12
13
14
cd cubvh
pip install .
```
ashawkey's avatar
ashawkey committed
15
It will take several minutes to build the CUDA dependency.
ashawkey's avatar
init  
ashawkey committed
16

kiui's avatar
kiui committed
17
#### Trouble Shooting
18
19
20
21
22
23
24
25
**`fatal error: eigen/matrix.h: No such file or directory`**

This is a known issue for `torch==2.1.0` and `torch==2.1.1` (https://github.com/pytorch/pytorch/issues/112841). 
To patch up these two versions, clone this repository, and copy `patch/eigen` to your pytorch include directory:
```bash
# for example, if you are using anaconda (assume base env)
cp -r patch/eigen ~/anaconda3/lib/python3.9/site-packages/torch/include/pybind11/
```
kiui's avatar
kiui committed
26

ashawkey's avatar
ashawkey committed
27
28
29
30
31
32
**`fatal error: Eigen/Dense: No such file or directory`**

Please make sure [`eigen >= 3.3`](https://eigen.tuxfamily.org/index.php?title=Main_Page) is installed. 
We have included it as a submodule in this repository, but you can also install it in your system include path.
(For example, ubuntu systems can use `sudo apt install libeigen3-dev`.)

ashawkey's avatar
init  
ashawkey committed
33
34
### Usage

ashawkey's avatar
ashawkey committed
35
36
**Basics:**

ashawkey's avatar
init  
ashawkey committed
37
38
39
40
```python
import numpy as np
import trimesh
import torch
ashawkey's avatar
ashawkey committed
41

ashawkey's avatar
init  
ashawkey committed
42
43
import cubvh

ashawkey's avatar
ashawkey committed
44
### build BVH from mesh
ashawkey's avatar
init  
ashawkey committed
45
mesh = trimesh.load('example.ply')
ashawkey's avatar
ashawkey committed
46
# NOTE: you need to normalize the mesh first, since the max distance is hard-coded to 10.
ashawkey's avatar
init  
ashawkey committed
47
48
BVH = cubvh.cuBVH(mesh.vertices, mesh.faces) # build with numpy.ndarray/torch.Tensor

ashawkey's avatar
ashawkey committed
49
### query ray-mesh intersection
ashawkey's avatar
init  
ashawkey committed
50
rays_o, rays_d = get_ray(pose, intrinsics, H, W) # [N, 3], [N, 3], query with torch.Tensor (cuda)
ashawkey's avatar
ashawkey committed
51
intersections, face_id, depth = BVH.ray_trace(rays_o, rays_d) # [N, 3], [N,], [N,]
ashawkey's avatar
init  
ashawkey committed
52

ashawkey's avatar
ashawkey committed
53
### query unsigned distance
ashawkey's avatar
init  
ashawkey committed
54
points # [N, 3]
ashawkey's avatar
ashawkey committed
55
# uvw is the barycentric corrdinates of the closest point on the closest face (None if `return_uvw` is False).
ashawkey's avatar
init  
ashawkey committed
56
distances, face_id, uvw = BVH.unsigned_distance(points, return_uvw=True) # [N], [N], [N, 3]
ashawkey's avatar
ashawkey committed
57

ashawkey's avatar
ashawkey committed
58
### query signed distance (INNER is NEGATIVE!)
ashawkey's avatar
ashawkey committed
59
60
61
62
# for watertight meshes (default)
distances, face_id, uvw = BVH.signed_distance(points, return_uvw=True, mode='watertight') # [N], [N], [N, 3]
# for non-watertight meshes:
distances, face_id, uvw = BVH.signed_distance(points, return_uvw=True, mode='raystab') # [N], [N], [N, 3]
ashawkey's avatar
init  
ashawkey committed
63
64
```

ashawkey's avatar
ashawkey committed
65
66
67
68
69
**Robust Mesh Occupancy:**

UDF + flood-fill for possibly non-watertight/single-layer meshes:

```python
ashawkey's avatar
ashawkey committed
70
71
72
73
74
import torch
import cubvh
import numpy as np
from skimage import morphology

ashawkey's avatar
ashawkey committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
resolution = 512
device = torch.device('cuda')

BVH = cubvh.cuBVH(vertices, faces)

grid_points = torch.stack(
    torch.meshgrid(
        torch.linspace(-1, 1, resolution, device=device),
        torch.linspace(-1, 1, resolution, device=device),
        torch.linspace(-1, 1, resolution, device=device),
        indexing="ij",
    ), dim=-1,
) # [N, N, N, 3]

udf, _, _ = BVH.unsigned_distance(grid_points.view(-1, 3), return_uvw=False)
udf = udf.cpu().numpy().reshape(resolution, resolution, resolution)
occ = udf < 2 / resolution # tolerance 2 voxels

empty_mask = morphology.flood(occ, (0, 0, 0), connectivity=1) # flood from the corner, which is for sure empty
occ = ~empty_mask
```
96
Check [`test/extract_mesh_watertight.py`](test/extract_mesh_watertight.py) for more details.
ashawkey's avatar
ashawkey committed
97
98
99


**Renderer:**
ashawkey's avatar
init  
ashawkey committed
100

ashawkey's avatar
ashawkey committed
101
102
103
104
105
106
107
108
109
110
Example for a mesh normal renderer by `ray_trace`:

```bash
python test/renderer.py # default, show a dodecahedron
python test/renderer.py --mesh example.ply # show any mesh file
```

https://user-images.githubusercontent.com/25863658/183238748-7ac82808-6cd3-4bb6-867a-9c22f8e3f7dd.mp4


ashawkey's avatar
init  
ashawkey committed
111
112
113
### Acknowledgement

* Credits to [Thomas Müller](https://tom94.net/)'s amazing [tiny-cuda-nn](https://github.com/NVlabs/tiny-cuda-nn) and [instant-ngp](https://github.com/NVlabs/instant-ngp)!