gKit2 light
tuto_bvh.cpp
1 
3 
4 #include <algorithm>
5 #include <vector>
6 #include <cfloat>
7 #include <chrono>
8 
9 #include "vec.h"
10 #include "mat.h"
11 #include "color.h"
12 #include "image.h"
13 #include "image_io.h"
14 #include "image_hdr.h"
15 #include "orbiter.h"
16 #include "mesh.h"
17 #include "wavefront.h"
18 
19 
20 struct RayHit
21 {
22  Point o; // origine
23  float t; // p(t)= o + td, position du point d'intersection sur le rayon
24  Vector d; // direction
25  int triangle_id; // indice du triangle dans le mesh
26  float u, v;
27  int x, y;
28 
29  RayHit( const Point& _o, const Point& _e ) : o(_o), t(1), d(Vector(_o, _e)), triangle_id(-1), u(), v(), x(), y() {}
30  RayHit( const Point& _o, const Point& _e, const int _x, const int _y ) : o(_o), t(1), d(Vector(_o, _e)), triangle_id(-1), u(), v(), x(_x), y(_y) {}
31  operator bool ( ) { return (triangle_id != -1); }
32 };
33 
34 
35 struct BBoxHit
36 {
37  float tmin, tmax;
38 
39  BBoxHit() : tmin(FLT_MAX), tmax(-FLT_MAX) {}
40  BBoxHit( const float _tmin, const float _tmax ) : tmin(_tmin), tmax(_tmax) {}
41  float centroid( ) const { return (tmin + tmax) / 2; }
42  operator bool( ) const { return tmin <= tmax; }
43 };
44 
45 
46 struct BBox
47 {
48  Point pmin, pmax;
49 
50  BBox( ) : pmin(), pmax() {}
51 
52  BBox( const Point& p ) : pmin(p), pmax(p) {}
53  BBox( const BBox& box ) : pmin(box.pmin), pmax(box.pmax) {}
54 
55  BBox& insert( const Point& p ) { pmin= min(pmin, p); pmax= max(pmax, p); return *this; }
56  BBox& insert( const BBox& box ) { pmin= min(pmin, box.pmin); pmax= max(pmax, box.pmax); return *this; }
57 
58  float centroid( const int axis ) const { return (pmin(axis) + pmax(axis)) / 2; }
59 
60  BBoxHit intersect( const RayHit& ray ) const
61  {
62  Vector invd= Vector(1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z);
63  return intersect(ray, invd);
64  }
65 
66  BBoxHit intersect( const RayHit& ray, const Vector& invd ) const
67  {
68  Point rmin= pmin;
69  Point rmax= pmax;
70  if(ray.d.x < 0) std::swap(rmin.x, rmax.x);
71  if(ray.d.y < 0) std::swap(rmin.y, rmax.y);
72  if(ray.d.z < 0) std::swap(rmin.z, rmax.z);
73  Vector dmin= (rmin - ray.o) * invd;
74  Vector dmax= (rmax - ray.o) * invd;
75 
76  float tmin= std::max(dmin.z, std::max(dmin.y, std::max(dmin.x, 0.f)));
77  float tmax= std::min(dmax.z, std::min(dmax.y, std::min(dmax.x, ray.t)));
78  return BBoxHit(tmin, tmax);
79  }
80 };
81 
82 
83 struct Triangle
84 {
85  Point p; // sommet a du triangle
86  Vector e1, e2; // aretes ab, ac du triangle
87  int id;
88 
89  Triangle( const TriangleData& data, const int _id ) : p(data.a), e1(Vector(data.a, data.b)), e2(Vector(data.a, data.c)), id(_id) {}
90 
91  /* calcule l'intersection ray/triangle
92  cf "fast, minimum storage ray-triangle intersection"
93 
94  renvoie faux s'il n'y a pas d'intersection valide (une intersection peut exister mais peut ne pas se trouver dans l'intervalle [0 tmax] du rayon.)
95  renvoie vrai + les coordonnees barycentriques (u, v) du point d'intersection + sa position le long du rayon (t).
96  convention barycentrique : p(u, v)= (1 - u - v) * a + u * b + v * c
97  */
98  void intersect( RayHit &ray ) const
99  {
100  Vector pvec= cross(ray.d, e2);
101  float det= dot(e1, pvec);
102 
103  float inv_det= 1 / det;
104  Vector tvec(p, ray.o);
105 
106  float u= dot(tvec, pvec) * inv_det;
107  if(u < 0 || u > 1) return;
108 
109  Vector qvec= cross(tvec, e1);
110  float v= dot(ray.d, qvec) * inv_det;
111  if(v < 0 || u + v > 1) return;
112 
113  float t= dot(e2, qvec) * inv_det;
114  if(t < 0 || t > ray.t) return;
115 
116  // touche !!
117  ray.t= t;
118  ray.triangle_id= id;
119  ray.u= u;
120  ray.v= v;
121  }
122 
123  BBox bounds( ) const
124  {
125  BBox box(p);
126  return box.insert(p+e1).insert(p+e2);
127  }
128 };
129 
130 
131 // construction de l'arbre / BVH
132 struct Node
133 {
134  BBox bounds;
135  int left;
136  int right;
137 
138  bool internal( ) const { return right > 0; } // renvoie vrai si le noeud est un noeud interne
139  int internal_left( ) const { assert(internal()); return left; } // renvoie le fils gauche du noeud interne
140  int internal_right( ) const { assert(internal()); return right; } // renvoie le fils droit
141 
142  bool leaf( ) const { return right < 0; } // renvoie vrai si le noeud est une feuille
143  int leaf_begin( ) const { assert(leaf()); return -left; } // renvoie le premier objet de la feuille
144  int leaf_end( ) const { assert(leaf()); return -right; } // renvoie le dernier objet
145 };
146 
147 // creation d'un noeud interne
148 Node make_node( const BBox& bounds, const int left, const int right )
149 {
150  Node node { bounds, left, right };
151  assert(node.internal()); // verifie que c'est bien un noeud...
152  return node;
153 }
154 
155 // creation d'une feuille
156 Node make_leaf( const BBox& bounds, const int begin, const int end )
157 {
158  Node node { bounds, -begin, -end };
159  assert(node.leaf()); // verifie que c'est bien une feuille...
160  return node;
161 }
162 
163 
165 {
166  int axis;
167  float cut;
168 
169  triangle_less1( const int _axis, const float _cut ) : axis(_axis), cut(_cut) {}
170 
171  bool operator() ( const Triangle& triangle ) const
172  {
173  // re-construit l'englobant du triangle
174  BBox bounds= triangle.bounds();
175  return bounds.centroid(axis) < cut;
176  }
177 };
178 
179 
180 struct BVH
181 {
182  std::vector<Node> nodes;
183  std::vector<Triangle> triangles;
184  int root;
185 
186  int direct_tests;
187 
188  // construit un bvh pour l'ensemble de triangles
189  int build( const BBox& _bounds, const std::vector<Triangle>& _triangles )
190  {
191  triangles= _triangles; // copie les triangles pour les trier
192  nodes.clear(); // efface les noeuds
193  nodes.reserve(triangles.size());
194 
195  // construit l'arbre...
196  root= build(_bounds, 0, triangles.size());
197  // et renvoie la racine
198  return root;
199  }
200 
201  void intersect( RayHit& ray ) const
202  {
203  Vector invd= Vector(1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z);
204  intersect(root, ray, invd);
205  }
206 
207  void intersect_fast( RayHit& ray ) const
208  {
209  Vector invd= Vector(1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z);
210  intersect_fast(root, ray, invd);
211  }
212 
213 protected:
214  // construction d'un noeud
215  int build( const BBox& bounds, const int begin, const int end )
216  {
217  if(end - begin < 2)
218  {
219  // inserer une feuille et renvoyer son indice
220  int index= nodes.size();
221  nodes.push_back(make_leaf(bounds, begin, end));
222  return index;
223  }
224 
225  // axe le plus etire de l'englobant
226  Vector d= Vector(bounds.pmin, bounds.pmax);
227  int axis;
228  if(d.x > d.y && d.x > d.z) // x plus grand que y et z ?
229  axis= 0;
230  else if(d.y > d.z) // y plus grand que z ? (et que x implicitement)
231  axis= 1;
232  else // x et y ne sont pas les plus grands...
233  axis= 2;
234 
235  // coupe l'englobant au milieu
236  float cut= bounds.centroid(axis);
237 
238  // repartit les triangles
239  Triangle *pm= std::partition(triangles.data() + begin, triangles.data() + end, triangle_less1(axis, cut));
240  int m= std::distance(triangles.data(), pm);
241 
242  // la repartition des triangles peut echouer, et tous les triangles sont dans la meme partie...
243  // forcer quand meme un decoupage en 2 ensembles
244  if(m == begin || m == end)
245  m= (begin + end) / 2;
246  assert(m != begin);
247  assert(m != end);
248 
249  // construire le fils gauche
250  // les triangles se trouvent dans [begin .. m)
251  BBox bounds_left= triangle_bounds(begin, m);
252  int left= build(bounds_left, begin, m);
253 
254  // on recommence pour le fils droit
255  // les triangles se trouvent dans [m .. end)
256  BBox bounds_right= triangle_bounds(m, end);
257  int right= build(bounds_right, m, end);
258 
259  int index= nodes.size();
260  nodes.push_back(make_node(bounds, left, right));
261  return index;
262  }
263 
264  BBox triangle_bounds( const int begin, const int end )
265  {
266  BBox bbox= triangles[begin].bounds();
267  for(int i= begin +1; i < end; i++)
268  bbox.insert(triangles[i].bounds());
269 
270  return bbox;
271  }
272 
273  void intersect( const int index, RayHit& ray, const Vector& invd ) const
274  {
275  const Node& node= nodes[index];
276  if(node.bounds.intersect(ray, invd))
277  {
278  if(node.leaf())
279  {
280  for(int i= node.leaf_begin(); i < node.leaf_end(); i++)
281  triangles[i].intersect(ray);
282  }
283  else // if(node.internal())
284  {
285  intersect(node.internal_left(), ray, invd);
286  intersect(node.internal_right(), ray, invd);
287  }
288  }
289  }
290 
291  void intersect_fast( const int index, RayHit& ray, const Vector& invd ) const
292  {
293  const Node& node= nodes[index];
294  if(node.leaf())
295  {
296  for(int i= node.leaf_begin(); i < node.leaf_end(); i++)
297  triangles[i].intersect(ray);
298  }
299  else // if(node.internal())
300  {
301  const Node& left_node= nodes[node.left];
302  const Node& right_node= nodes[node.right];
303 
304  BBoxHit left= left_node.bounds.intersect(ray, invd);
305  BBoxHit right= right_node.bounds.intersect(ray, invd);
306  if(left && right) // les 2 fils sont touches par le rayon...
307  {
308  if(left.centroid() < right.centroid()) // parcours de gauche a droite
309  {
310  intersect_fast(node.internal_left(), ray, invd);
311  intersect_fast(node.internal_right(), ray, invd);
312  }
313  else // parcours de droite a gauche
314  {
315  intersect_fast(node.internal_right(), ray, invd);
316  intersect_fast(node.internal_left(), ray, invd);
317  }
318  }
319  else if(left) // uniquement le fils gauche
320  intersect_fast(node.internal_left(), ray, invd);
321  else if(right)
322  intersect_fast(node.internal_right(), ray, invd); // uniquement le fils droit
323  }
324  }
325 };
326 
327 
328 int main( const int argc, const char **argv )
329 {
330  const char *mesh_filename= "data/cornell.obj";
331  if(argc > 1)
332  mesh_filename= argv[1];
333 
334  const char *orbiter_filename= "data/cornell_orbiter.txt";
335  if(argc > 2)
336  orbiter_filename= argv[2];
337 
338  Orbiter camera;
339  if(camera.read_orbiter(orbiter_filename) < 0)
340  return 1;
341 
342  Mesh mesh= read_mesh(mesh_filename);
343  BBox bounds;
344  mesh.bounds(bounds.pmin, bounds.pmax);
345 
346  // recupere les triangles
347  std::vector<Triangle> triangles;
348  {
349  int n= mesh.triangle_count();
350  for(int i= 0; i < n; i++)
351  triangles.emplace_back(mesh.triangle(i), i);
352  }
353 
354  Image image(1024, 768);
355  //~ Image image(4096, 2048);
356 
357  // recupere les transformations
358  camera.projection(image.width(), image.height(), 45);
359  Transform model= Identity();
360  Transform view= camera.view();
361  Transform projection= camera.projection();
362  Transform viewport= camera.viewport();
363  Transform inv= Inverse(viewport * projection * view * model);
364 
365  // genere un rayon par pixel de l'image
366  std::vector<RayHit> rays;
367  for(int y= 0; y < image.height(); y++)
368  for(int x= 0; x < image.width(); x++)
369  {
370  // generer le rayon
371  Point origine= inv(Point(x + .5f, y + .5f, 0));
372  Point extremite= inv(Point(x + .5f, y + .5f, 1));
373 
374  rays.emplace_back(origine, extremite, x, y);
375  }
376 
377 // mesure les temps d'execution
378  {
379  BVH bvh;
380 
381  {
382  auto start= std::chrono::high_resolution_clock::now();
383  // construction
384  bvh.build(bounds, triangles);
385 
386  auto stop= std::chrono::high_resolution_clock::now();
387  int cpu= std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
388  printf("build %dms\n", cpu);
389  }
390 
391  {
392  auto start= std::chrono::high_resolution_clock::now();
393 
394  // intersection
395  const int n= int(rays.size());
396  for(int i= 0; i < n; i++)
397  bvh.intersect(rays[i]);
398 
399  auto stop= std::chrono::high_resolution_clock::now();
400  int cpu= std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
401  printf("bvh %dms\n", cpu);
402  }
403 
404  {
405  auto start= std::chrono::high_resolution_clock::now();
406 
407  // intersection
408  const int n= int(rays.size());
409  #pragma omp parallel for schedule(dynamic, 1024)
410  for(int i= 0; i < n; i++)
411  bvh.intersect(rays[i]);
412 
413  auto stop= std::chrono::high_resolution_clock::now();
414  int cpu= std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
415  printf("bvh mt %dms\n", cpu);
416  }
417 
418  {
419  auto start= std::chrono::high_resolution_clock::now();
420 
421  // intersection
422  const int n= int(rays.size());
423  #pragma omp parallel for schedule(dynamic, 1024)
424  for(int i= 0; i < n; i++)
425  bvh.intersect_fast(rays[i]);
426 
427  auto stop= std::chrono::high_resolution_clock::now();
428  int cpu= std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
429  printf("bvh fast mt %dms\n", cpu);
430  }
431  }
432 
433  // reconstruit l'image
434  for(int i= 0; i < rays.size(); i++)
435  {
436  if(rays[i])
437  {
438  int x= rays[i].x;
439  int y= rays[i].y;
440  float u= rays[i].u;
441  float v= rays[i].v;
442  float w= 1 - u - v;
443  image(x, y)= Color(w, u, v);
444  }
445  }
446  write_image(image, "bvh.png");
447 
448  return 0;
449 }
representation d'une image.
Definition: image.h:21
representation d'un objet / maillage.
Definition: mesh.h:112
void bounds(Point &pmin, Point &pmax) const
renvoie min et max les coordonnees des extremites des positions des sommets de l'objet (boite engloba...
Definition: mesh.cpp:503
Mesh & triangle(const unsigned int a, const unsigned int b, const unsigned int c)
Definition: mesh.cpp:192
int triangle_count() const
renvoie le nombre de triangles.
Definition: mesh.cpp:435
representation de la camera, type orbiter, placee sur une sphere autour du centre de l'objet.
Definition: orbiter.h:17
int read_orbiter(const char *filename)
relit la position de l'orbiter depuis un fichier texte.
Definition: orbiter.cpp:116
Transform viewport() const
renvoie la transformation viewport actuelle. doit etre initialise par projection(width,...
Definition: orbiter.cpp:83
Transform projection(const int width, const int height, const float fov)
fixe la projection reglee pour une image d'aspect width / height, et une demi ouverture de fov degres...
Definition: orbiter.cpp:47
Transform view() const
renvoie la transformation vue.
Definition: orbiter.cpp:40
void begin(Widgets &w)
debut de la description des elements de l'interface graphique.
Definition: widgets.cpp:29
void printf(Text &text, const int px, const int py, const char *format,...)
affiche un texte a la position x, y. meme utilisation que printf().
Definition: text.cpp:140
void end(Widgets &w)
termine la description des elements de l'interface graphique.
Definition: widgets.cpp:404
int write_image(const Image &image, const char *filename)
enregistre une image dans un fichier png.
Definition: image_io.cpp:85
Transform Inverse(const Transform &m)
renvoie l'inverse de la matrice.
Definition: mat.cpp:197
Point max(const Point &a, const Point &b)
renvoie la plus grande composante de chaque point. x, y, z= max(a.x, b.x), max(a.y,...
Definition: vec.cpp:35
Transform Identity()
construit la transformation identite.
Definition: mat.cpp:187
float distance(const Point &a, const Point &b)
renvoie la distance etre 2 points.
Definition: vec.cpp:14
Point min(const Point &a, const Point &b)
renvoie la plus petite composante de chaque point. x, y, z= min(a.x, b.x), min(a.y,...
Definition: vec.cpp:30
float dot(const Vector &u, const Vector &v)
renvoie le produit scalaire de 2 vecteurs.
Definition: vec.cpp:137
Vector cross(const Vector &u, const Vector &v)
renvoie le produit vectoriel de 2 vecteurs.
Definition: vec.cpp:129
Mesh read_mesh(const char *filename)
charge un fichier wavefront .obj et renvoie un mesh compose de triangles non indexes....
Definition: wavefront.cpp:14
void bounds(const MeshData &data, Point &pmin, Point &pmax)
renvoie l'englobant.
Definition: mesh_data.cpp:290
intersection avec une boite / un englobant.
Definition: tuto_bvh.cpp:36
boite englobante.
Definition: tuto_bvh.cpp:47
representation d'une couleur (rgba) transparente ou opaque.
Definition: color.h:14
construction de l'arbre / BVH.
Definition: tuto_bvh.cpp:133
representation d'un point 3d.
Definition: vec.h:21
representation d'une transformation, une matrice 4x4, organisee par ligne / row major.
Definition: mat.h:21
representation d'un triangle.
Definition: mesh.h:95
vec3 c
positions
Definition: mesh.h:96
triangle pour le bvh, cf fonction bounds() et intersect().
Definition: tuto_bvh.cpp:84
representation d'un vecteur 3d.
Definition: vec.h:59
Node make_leaf(const BBox &bounds, const int begin, const int end)
creation d'une feuille.
Node make_node(const BBox &bounds, const int left, const int right)
creation d'un noeud interne.