gKit2 light
tuto_bvh2.cpp
Go to the documentation of this file.
1 
3 
4 #include <algorithm>
5 #include <vector>
6 #include <cfloat>
7 
8 #include "vec.h"
9 #include "mat.h"
10 #include "color.h"
11 #include "image.h"
12 #include "image_io.h"
13 #include "orbiter.h"
14 #include "mesh.h"
15 #include "wavefront.h"
16 
17 
18 // rayon
19 struct Ray
20 {
21  Point o; // origine
22  float pad;
23  Vector d; // direction
24  float tmax;
25 
26  Ray( const Point& _o, const Point& _e ) : o(_o), d(Vector(_o, _e)), tmax(1) {}
27  Ray( const Point& _o, const Vector& _d ) : o(_o), d(_d), tmax(FLT_MAX) {}
28  Ray( const Point& _o, const Vector& _d, const float _tmax ) : o(_o), d(_d), tmax(_tmax) {}
29 };
30 
31 // intersection avec un triangle
32 struct Hit
33 {
34  float t; // p(t)= o + td, position du point d'intersection sur le rayon
35  float u, v; // p(u, v), position du point d'intersection sur le triangle
36  int instance_id; // indice de l'instance
37  int triangle_id; // indice du triangle dans le mesh
38 
39  Hit( ) : t(FLT_MAX), u(), v(), instance_id(-1), triangle_id(-1) {}
40  Hit( const Ray& ray ) : t(ray.tmax), u(), v(), instance_id(-1), triangle_id(-1) {}
41  Hit( const float _t, const float _u, const float _v, const int _id ) : t(_t), u(_u), v(_v), instance_id(-1), triangle_id(_id) {}
42 
43  operator bool ( ) { return (triangle_id != -1); }
44 };
45 
46 // intersection avec une boite / un englobant
47 struct BBoxHit
48 {
49  float tmin, tmax;
50 
51  BBoxHit() : tmin(FLT_MAX), tmax(-FLT_MAX) {}
52  BBoxHit( const float _tmin, const float _tmax ) : tmin(_tmin), tmax(_tmax) {}
53 
54  operator bool( ) const { return tmin <= tmax; }
55 };
56 
57 
58 // boite englobante
59 struct BBox
60 {
61  Point pmin, pmax;
62 
63  BBox( ) : pmin(), pmax() {}
64 
65  BBox( const Point& p ) : pmin(p), pmax(p) {}
66  BBox( const BBox& box ) : pmin(box.pmin), pmax(box.pmax) {}
67  BBox( const BBox& a, const BBox& b ) : pmin(min(a.pmin, b.pmin)), pmax(max(a.pmax, b.pmax)) {}
68 
69  BBox& insert( const Point& p ) { pmin= min(pmin, p); pmax= max(pmax, p); return *this; }
70  BBox& insert( const BBox& box ) { pmin= min(pmin, box.pmin); pmax= max(pmax, box.pmax); return *this; }
71 
72  float centroid( const int axis ) const { return (pmin(axis) + pmax(axis)) / 2; }
73  Point centroid( ) const { return (pmin + pmax) / 2; }
74 
75  BBoxHit intersect( const Ray& ray, const Vector& invd, const float htmax ) const
76  {
77  Point rmin= pmin;
78  Point rmax= pmax;
79  if(ray.d.x < 0) std::swap(rmin.x, rmax.x);
80  if(ray.d.y < 0) std::swap(rmin.y, rmax.y);
81  if(ray.d.z < 0) std::swap(rmin.z, rmax.z);
82  Vector dmin= (rmin - ray.o) * invd;
83  Vector dmax= (rmax - ray.o) * invd;
84 
85  float tmin= std::max(dmin.z, std::max(dmin.y, std::max(dmin.x, 0.f)));
86  float tmax= std::min(dmax.z, std::min(dmax.y, std::min(dmax.x, htmax)));
87  return BBoxHit(tmin, tmax);
88  }
89 };
90 
91 
92 // construction de l'arbre / BVH
93 struct Node
94 {
95  BBox bounds;
96  int left;
97  int right;
98 
99  bool internal( ) const { return right > 0; } // renvoie vrai si le noeud est un noeud interne
100  int internal_left( ) const { assert(internal()); return left; } // renvoie le fils gauche du noeud interne
101  int internal_right( ) const { assert(internal()); return right; } // renvoie le fils droit
102 
103  bool leaf( ) const { return right < 0; } // renvoie vrai si le noeud est une feuille
104  int leaf_begin( ) const { assert(leaf()); return -left; } // renvoie le premier objet de la feuille
105  int leaf_end( ) const { assert(leaf()); return -right; } // renvoie le dernier objet
106 };
107 
108 // creation d'un noeud interne
109 Node make_node( const BBox& bounds, const int left, const int right )
110 {
111  Node node { bounds, left, right };
112  assert(node.internal()); // verifie que c'est bien un noeud...
113  return node;
114 }
115 
116 // creation d'une feuille
117 Node make_leaf( const BBox& bounds, const int begin, const int end )
118 {
119  Node node { bounds, -begin, -end };
120  assert(node.leaf()); // verifie que c'est bien une feuille...
121  return node;
122 }
123 
124 
125 // bvh parametre par le type des primitives, cf triangle et instance...
126 template < typename T >
127 struct BVHT
128 {
129  // construit un bvh pour l'ensemble de primitives
130  int build( const std::vector<T>& _primitives )
131  {
132  primitives= _primitives; // copie les primitives pour les trier
133  nodes.clear(); // efface les noeuds
134  nodes.reserve(primitives.size());
135 
136  // construit l'arbre...
137  root= build(0, primitives.size());
138  return root;
139  }
140 
141  // intersection avec un rayon, entre 0 et htmax
142  Hit intersect( const Ray& ray, const float htmax ) const
143  {
144  Hit hit;
145  hit.t= htmax;
146  Vector invd= Vector(1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z);
147  intersect(root, ray, invd, hit);
148  return hit;
149  }
150 
151  // intersection avec un rayon, entre 0 et ray.tmax
152  Hit intersect( const Ray& ray ) const { return intersect(ray, ray.tmax); }
153 
154 protected:
155  std::vector<Node> nodes;
156  std::vector<T> primitives;
157  int root;
158 
159  int build( const int begin, const int end )
160  {
161  if(end - begin < 2)
162  {
163  // inserer une feuille et renvoyer son indice
164  int index= nodes.size();
165  nodes.push_back( make_leaf( primitive_bounds(begin, end), begin, end ) );
166  return index;
167  }
168 
169  // axe le plus etire de l'englobant des centres des englobants des primitives...
170  BBox cbounds= centroid_bounds(begin, end);
171  Vector d= Vector(cbounds.pmin, cbounds.pmax);
172  int axis;
173  if(d.x > d.y && d.x > d.z) // x plus grand que y et z ?
174  axis= 0;
175  else if(d.y > d.z) // y plus grand que z ? (et que x implicitement)
176  axis= 1;
177  else // x et y ne sont pas les plus grands...
178  axis= 2;
179 
180  // coupe l'englobant au milieu
181  float cut= cbounds.centroid(axis);
182 
183  // repartit les primitives
184  T *pm= std::partition(primitives.data() + begin, primitives.data() + end,
185  [axis, cut]( const T& primitive )
186  {
187  return primitive.bounds().centroid(axis) < cut;
188  }
189  );
190  int m= std::distance(primitives.data(), pm);
191 
192  // la repartition peut echouer, et toutes les primitives sont dans la meme moitiee de l'englobant
193  // forcer quand meme un decoupage en 2 ensembles
194  if(m == begin || m == end)
195  m= (begin + end) / 2;
196  assert(m != begin);
197  assert(m != end);
198 
199  // construire le fils gauche, les triangles se trouvent dans [begin .. m)
200  int left= build(begin, m);
201 
202  // on recommence pour le fils droit, les triangles se trouvent dans [m .. end)
203  int right= build(m, end);
204 
205  // construire le noeud et renvoyer son indice
206  int index= nodes.size();
207  nodes.push_back( make_node( BBox(nodes[left].bounds, nodes[right].bounds), left, right ) );
208  return index;
209  }
210 
211  // englobant des primitives
212  BBox primitive_bounds( const int begin, const int end )
213  {
214  BBox bbox= primitives[begin].bounds();
215  for(int i= begin +1; i < end; i++)
216  bbox.insert(primitives[i].bounds());
217 
218  return bbox;
219  }
220 
221  // englobant des centres des primitives
222  BBox centroid_bounds( const int begin, const int end )
223  {
224  BBox bbox= primitives[begin].bounds().centroid();
225  for(int i= begin +1; i < end; i++)
226  bbox.insert(primitives[i].bounds().centroid());
227 
228  return bbox;
229  }
230 
231  // intersection et parcours simple
232  void intersect( const int index, const Ray& ray, const Vector& invd, Hit& hit ) const
233  {
234  const Node& node= nodes[index];
235  if(node.bounds.intersect(ray, invd, hit.t))
236  {
237  if(node.leaf())
238  {
239  for(int i= node.leaf_begin(); i < node.leaf_end(); i++)
240  if(Hit h= primitives[i].intersect(ray, hit.t))
241  hit= h;
242  }
243  else // if(node.internal())
244  {
245  intersect(node.internal_left(), ray, invd, hit);
246  intersect(node.internal_right(), ray, invd, hit);
247  }
248  }
249  }
250 };
251 
252 
253 // triangle pour le bvh, cf fonction bounds() et intersect()
254 struct Triangle
255 {
256  Point p; // sommet a du triangle
257  Vector e1, e2; // aretes ab, ac du triangle
258  int id;
259 
260  Triangle( const TriangleData& data, const int _id ) : p(data.a), e1(Vector(data.a, data.b)), e2(Vector(data.a, data.c)), id(_id) {}
261 
262  /* calcule l'intersection ray/triangle
263  cf "fast, minimum storage ray-triangle intersection"
264 
265  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.)
266  renvoie vrai + les coordonnees barycentriques (u, v) du point d'intersection + sa position le long du rayon (t).
267  convention barycentrique : p(u, v)= (1 - u - v) * a + u * b + v * c
268  */
269  Hit intersect( const Ray &ray, const float htmax ) const
270  {
271  Vector pvec= cross(ray.d, e2);
272  float det= dot(e1, pvec);
273 
274  float inv_det= 1 / det;
275  Vector tvec(p, ray.o);
276 
277  float u= dot(tvec, pvec) * inv_det;
278  if(u < 0 || u > 1) return Hit();
279 
280  Vector qvec= cross(tvec, e1);
281  float v= dot(ray.d, qvec) * inv_det;
282  if(v < 0 || u + v > 1) return Hit();
283 
284  float t= dot(e2, qvec) * inv_det;
285  if(t < 0 || t > htmax) return Hit();
286 
287  return Hit(t, u, v, id);
288  }
289 
290  BBox bounds( ) const
291  {
292  BBox box(p);
293  return box.insert(p+e1).insert(p+e2);
294  }
295 };
296 
297 typedef BVHT<Triangle> BVH;
298 typedef BVHT<Triangle> BLAS;
299 
300 
301 // instance pour le bvh, cf fonctions bounds() et intersect()
302 struct Instance
303 {
304  Transform object_transform;
305  BBox world_bounds;
306  BVH *object_bvh;
307  int instance_id;
308 
309  Instance( const BBox& bounds, const Transform& model, BVH& bvh, const int id ) :
310  object_transform(Inverse(model)), world_bounds(transform(bounds, model)),
311  object_bvh(&bvh),
312  instance_id(id)
313  {}
314 
315  BBox bounds( ) const { return world_bounds; }
316 
317  Hit intersect( const Ray &ray, const float htmax ) const
318  {
319  // transforme le rayon
320  Ray object_ray(object_transform(ray.o), object_transform(ray.d), htmax);
321  // et intersection dans le bvh de l'objet instancie...
322 
323  Hit hit= object_bvh->intersect(object_ray, htmax);
324  if(hit)
325  // si intersection, stocker aussi l'indice de l'instance, cf retrouver la transformation et la matiere associee au mesh/triangle...
326  hit.instance_id= instance_id;
327 
328  return hit;
329  }
330 
331 protected:
332  BBox transform( const BBox& bbox, const Transform& m )
333  {
334  BBox bounds= BBox( m(bbox.pmin) );
335  // enumere les sommets de la bbox
336  for(unsigned i= 1; i < 8; i++)
337  {
338  // chaque sommet de la bbox est soit pmin soit pmax sur chaque axe...
339  Point p= bbox.pmin;
340  if(i & 1) p.x= bbox.pmax.x;
341  if(i & 2) p.y= bbox.pmax.y;
342  if(i & 4) p.z= bbox.pmax.z;
343 
344  // transforme le sommet de l'englobant
345  bounds.insert( m(p) );
346  }
347 
348  return bounds;
349  }
350 };
351 
352 typedef BVHT<Instance> TLAS;
353 
354 
355 int main( int argc, char **argv )
356 {
357  const char *mesh_filename= "data/robot.obj";
358  const char *orbiter_filename= nullptr;
359 
360  if(argc > 1) mesh_filename= argv[1];
361  if(argc > 2) orbiter_filename= argv[2];
362 
363  Mesh mesh= read_mesh(mesh_filename);
364  if(mesh.triangle_count() == 0)
365  return 1;
366 
367  BBox mesh_bounds;
368  mesh.bounds(mesh_bounds.pmin, mesh_bounds.pmax);
369 
370  // construit le bvh de l'objet
371  BVH bvh;
372  {
373  // recupere les triangles du mesh
374  std::vector<Triangle> triangles;
375  for(int i= 0; i <mesh.triangle_count(); i++)
376  {
377  TriangleData data= mesh.triangle(i);
378  triangles.push_back( Triangle(data, triangles.size()) );
379  }
380 
381  // construit le bvh...
382  bvh.build(triangles);
383  }
384 
385  // instancie l'objet
386  TLAS top_bvh;
387  {
388  std::vector<Instance> instances;
389  for(int i= -2; i <= 2; i++)
390  {
391  Transform model= Translation(i*10, 0, 0);
392  instances.push_back( Instance(mesh_bounds, model, bvh, instances.size()) );
393  }
394 
395  // construit le bvh...
396  top_bvh.build(instances);
397  }
398 
399  // regle la camera
400  Orbiter camera;
401  if(orbiter_filename == nullptr || camera.read_orbiter(orbiter_filename) < 0)
402  // les objets sont instancies sur une ligne, agrandir la zone observee par la camera...
403  camera.lookat(mesh_bounds.pmin * 5, mesh_bounds.pmax * 5);
404 
405  // image resultat
406  Image image(1024, 640);
407 
408  // transformations
409  Transform model= Identity();
410  Transform view= camera.view();
411  Transform projection= camera.projection();
412  Transform viewport= Viewport(image.width(), image.height());
413  Transform inv= Inverse(viewport * projection * view * model);
414 
415  // calcule l'image en parallele avec openMP
416 #pragma omp parallel for
417  for(int y= 0; y < image.height(); y++)
418  for(int x= 0; x < image.width(); x++)
419  {
420  // genere le rayon pour le pixel x,y
421  Point o= inv( Point(x, y, 0) ); // origine
422  Point e= inv( Point(x, y, 1) ); // extremite
423  Ray ray(o, e);
424 
425  // intersections !
426  if(Hit hit= top_bvh.intersect(ray))
427  image(x, y)= Red(); // touche !
428  }
429 
430  write_image(image, "render.png");
431  return 0;
432 }
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:501
Mesh & triangle(const unsigned int a, const unsigned int b, const unsigned int c)
Definition: mesh.cpp:190
int triangle_count() const
renvoie le nombre de triangles.
Definition: mesh.cpp:433
representation de la camera, type orbiter, placee sur une sphere autour du centre de l'objet.
Definition: orbiter.h:17
void lookat(const Point &center, const float size)
observe le point center a une distance size.
Definition: orbiter.cpp:7
int read_orbiter(const char *filename)
relit la position de l'orbiter depuis un fichier texte.
Definition: orbiter.cpp:116
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 end(Widgets &w)
termine la description des elements de l'interface graphique.
Definition: widgets.cpp:404
Color Red()
utilitaire. renvoie une couleur rouge.
Definition: color.cpp:41
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 Viewport(const float width, const float height)
renvoie la matrice representant une transformation viewport.
Definition: mat.cpp:357
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
Transform Translation(const Vector &v)
renvoie la matrice representant une translation par un vecteur.
Definition: mat.cpp:216
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
bvh parametre par le type des primitives, cf triangle et instance...
Definition: tuto_bvh2.cpp:128
intersection avec un triangle.
Definition: tuto_bvh2.cpp:33
instance pour le bvh, cf fonctions bounds() et intersect().
Definition: tuto_bvh2.cpp:303
construction de l'arbre / BVH.
Definition: tuto_bvh.cpp:133
representation d'un point 3d.
Definition: vec.h:21
rayon.
Definition: tuto_bvh2.cpp:20
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.