gKit2 light
Loading...
Searching...
No Matches
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
20struct 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
35struct 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
46struct 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
83struct 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
132struct 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
148Node 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
156Node 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
164struct triangle_less1
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
180struct 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
213protected:
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
328int 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(unsigned y= 0; y < image.height(); y++)
368 for(unsigned 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(unsigned 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:121
representation de la camera, type orbiter, placee sur une sphere autour du centre de l'objet.
Definition orbiter.h:17
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
bool write_image(const Image &image, const char *filename, const bool flipY)
enregistre une image au format .png
Definition image_io.cpp:225
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 { max(a.x, b.x), max(a.y, b.y), max(a....
Definition vec.cpp:35
Transform Identity()
construit la transformation identite.
Definition mat.cpp:187
Point min(const Point &a, const Point &b)
renvoie la plus petite composante de chaque point { min(a.x, b.x), min(a.y, b.y), min(a....
Definition vec.cpp:30
float dot(const Vector &u, const Vector &v)
renvoie le produit scalaire de 2 vecteurs.
Definition vec.cpp:181
Vector cross(const Vector &u, const Vector &v)
renvoie le produit vectoriel de 2 vecteurs.
Definition vec.cpp:173
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.
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
triangle pour le bvh, cf fonction bounds() et intersect().
Definition tuto_bvh.cpp:84
representation d'un vecteur 3d.
Definition vec.h:67