gKit2 light
Loading...
Searching...
No Matches
tuto_bvh2_gltf.cpp
Go to the documentation of this file.
1
3
4#include <random>
5#include <algorithm>
6#include <vector>
7#include <cfloat>
8
9#include "vec.h"
10#include "mat.h"
11#include "color.h"
12#include "image.h"
13#include "image_io.h"
14#include "orbiter.h"
15#include "gltf.h"
16
17
18// rayon
19struct Ray
20{
21 Point o; // origine
22 float pad;
23 Vector d; // direction
24 float tmax; // tmax= 1 ou \inf, le rayon est un segment ou une demi droite infinie
25
26 Ray( const Point& _o, const Point& _e ) : o(_o), d(Vector(_o, _e)), tmax(1) {} // segment, t entre 0 et 1
27 Ray( const Point& _o, const Vector& _d ) : o(_o), d(_d), tmax(FLT_MAX) {} // demi droite, t entre 0 et \inf
28 Ray( const Point& _o, const Vector& _d, const float _tmax ) : o(_o), d(_d), tmax(_tmax) {} // explicite
29};
30
31// intersection avec un triangle
32struct 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, pour retrouver la transformation
37 int mesh_id; // indexation globale du triangle dans la scene gltf
38 int primitive_id;
39 int triangle_id;
40 int pad;
41
42 Hit( ) : t(FLT_MAX), u(), v(), instance_id(-1), mesh_id(-1), primitive_id(-1), triangle_id(-1) {}
43 Hit( const Ray& ray ) : t(ray.tmax), u(), v(), instance_id(-1), mesh_id(-1), primitive_id(-1), triangle_id(-1) {}
44
45 Hit( const float _t, const float _u, const float _v, const int _mesh_id, const int _primitive_id, const int _id ) : t(_t), u(_u), v(_v),
46 instance_id(-1), mesh_id(_mesh_id), primitive_id(_primitive_id), triangle_id(_id) {}
47
48 operator bool ( ) { return (triangle_id != -1); } // renvoie vrai si l'intersection est definie / existe
49};
50
51// intersection avec une boite / un englobant
52struct BBoxHit
53{
54 float tmin, tmax;
55
56 BBoxHit() : tmin(FLT_MAX), tmax(-FLT_MAX) {}
57 BBoxHit( const float _tmin, const float _tmax ) : tmin(_tmin), tmax(_tmax) {}
58
59 operator bool( ) const { return tmin <= tmax; } // renvoie vrai si l'intersection est definie / existe
60};
61
62
63// boite englobante
64struct BBox
65{
66 Point pmin, pmax;
67
68 BBox( ) : pmin(), pmax() {}
69
70 BBox( const Point& p ) : pmin(p), pmax(p) {}
71 BBox( const BBox& box ) : pmin(box.pmin), pmax(box.pmax) {}
72 BBox( const BBox& a, const BBox& b ) : pmin(min(a.pmin, b.pmin)), pmax(max(a.pmax, b.pmax)) {}
73
74 BBox& insert( const Point& p ) { pmin= min(pmin, p); pmax= max(pmax, p); return *this; }
75 BBox& insert( const BBox& box ) { pmin= min(pmin, box.pmin); pmax= max(pmax, box.pmax); return *this; }
76
77 float centroid( const int axis ) const { return (pmin(axis) + pmax(axis)) / 2; }
78 Point centroid( ) const { return (pmin + pmax) / 2; }
79
80 BBoxHit intersect( const Ray& ray, const Vector& invd, const float htmax ) const
81 {
82 Point rmin= pmin;
83 Point rmax= pmax;
84 if(ray.d.x < 0) std::swap(rmin.x, rmax.x);
85 if(ray.d.y < 0) std::swap(rmin.y, rmax.y);
86 if(ray.d.z < 0) std::swap(rmin.z, rmax.z);
87 Vector dmin= (rmin - ray.o) * invd;
88 Vector dmax= (rmax - ray.o) * invd;
89
90 float tmin= std::max(dmin.z, std::max(dmin.y, std::max(dmin.x, 0.f)));
91 float tmax= std::min(dmax.z, std::min(dmax.y, std::min(dmax.x, htmax)));
92 return BBoxHit(tmin, tmax);
93 }
94};
95
96
97// construction de l'arbre / BVH
98struct Node
99{
100 BBox bounds;
101 int left;
102 int right;
103
104 bool internal( ) const { return right > 0; } // renvoie vrai si le noeud est un noeud interne
105 int internal_left( ) const { assert(internal()); return left; } // renvoie le fils gauche du noeud interne
106 int internal_right( ) const { assert(internal()); return right; } // renvoie le fils droit
107
108 bool leaf( ) const { return right < 0; } // renvoie vrai si le noeud est une feuille
109 int leaf_begin( ) const { assert(leaf()); return -left; } // renvoie le premier objet de la feuille
110 int leaf_end( ) const { assert(leaf()); return -right; } // renvoie le dernier objet
111};
112
113// creation d'un noeud interne
114Node make_node( const BBox& bounds, const int left, const int right )
115{
116 Node node { bounds, left, right };
117 assert(node.internal()); // verifie que c'est bien un noeud...
118 return node;
119}
120
121// creation d'une feuille
122Node make_leaf( const BBox& bounds, const int begin, const int end )
123{
124 Node node { bounds, -begin, -end };
125 assert(node.leaf()); // verifie que c'est bien une feuille...
126 return node;
127}
128
129
130// bvh parametre par le type des primitives, cf triangle et instance...
131template < typename T >
132struct BVHT
133{
134 // construit un bvh pour l'ensemble de primitives
135 int build( const std::vector<T>& _primitives )
136 {
137 primitives= _primitives; // copie les primitives pour les trier
138 nodes.clear(); // efface les noeuds
139 nodes.reserve(primitives.size());
140
141 // construit l'arbre...
142 root= build(0, primitives.size());
143 return root;
144 }
145
146 // intersection avec un rayon, entre 0 et htmax
147 Hit intersect( const Ray& ray, const float htmax ) const
148 {
149 Hit hit;
150 hit.t= htmax;
151 Vector invd= Vector(1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z);
152 intersect(root, ray, invd, hit);
153 return hit;
154 }
155
156 // intersection avec un rayon, entre 0 et ray.tmax
157 Hit intersect( const Ray& ray ) const { return intersect(ray, ray.tmax); }
158
159protected:
160 std::vector<Node> nodes;
161 std::vector<T> primitives;
162 int root;
163
164 int build( const int begin, const int end )
165 {
166 if(end - begin < 2)
167 {
168 // inserer une feuille et renvoyer son indice
169 int index= nodes.size();
170 nodes.push_back( make_leaf( primitive_bounds(begin, end), begin, end ) );
171 return index;
172 }
173
174 // axe le plus etire de l'englobant des centres des englobants des primitives...
175 BBox cbounds= centroid_bounds(begin, end);
176 Vector d= Vector(cbounds.pmin, cbounds.pmax);
177 int axis;
178 if(d.x > d.y && d.x > d.z) // x plus grand que y et z ?
179 axis= 0;
180 else if(d.y > d.z) // y plus grand que z ? (et que x implicitement)
181 axis= 1;
182 else // x et y ne sont pas les plus grands...
183 axis= 2;
184
185 // coupe l'englobant au milieu
186 float cut= cbounds.centroid(axis);
187
188 // repartit les primitives
189 T *pm= std::partition(primitives.data() + begin, primitives.data() + end,
190 [axis, cut]( const T& primitive )
191 {
192 return primitive.bounds().centroid(axis) < cut;
193 }
194 );
195 int m= std::distance(primitives.data(), pm);
196
197 // la repartition peut echouer, et toutes les primitives sont dans la meme moitiee de l'englobant
198 // forcer quand meme un decoupage en 2 ensembles
199 if(m == begin || m == end)
200 m= (begin + end) / 2;
201 assert(m != begin);
202 assert(m != end);
203
204 // construire le fils gauche, les primtives se trouvent dans [begin .. m)
205 int left= build(begin, m);
206
207 // on recommence pour le fils droit, les primtives se trouvent dans [m .. end)
208 int right= build(m, end);
209
210 // construire le noeud et renvoyer son indice
211 int index= nodes.size();
212 nodes.push_back( make_node( BBox(nodes[left].bounds, nodes[right].bounds), left, right ) );
213 return index;
214 }
215
216 // englobant des primitives
217 BBox primitive_bounds( const int begin, const int end )
218 {
219 BBox bbox= primitives[begin].bounds();
220 for(int i= begin +1; i < end; i++)
221 bbox.insert(primitives[i].bounds());
222
223 return bbox;
224 }
225
226 // englobant des centres des primitives
227 BBox centroid_bounds( const int begin, const int end )
228 {
229 BBox bbox= primitives[begin].bounds().centroid();
230 for(int i= begin +1; i < end; i++)
231 bbox.insert(primitives[i].bounds().centroid());
232
233 return bbox;
234 }
235
236 // intersection et parcours simple
237 void intersect( const int index, const Ray& ray, const Vector& invd, Hit& hit ) const
238 {
239 const Node& node= nodes[index];
240 if(node.bounds.intersect(ray, invd, hit.t))
241 {
242 if(node.leaf())
243 {
244 for(int i= node.leaf_begin(); i < node.leaf_end(); i++)
245 if(Hit h= primitives[i].intersect(ray, hit.t))
246 hit= h;
247 }
248 else // if(node.internal())
249 {
250 intersect(node.internal_left(), ray, invd, hit);
251 intersect(node.internal_right(), ray, invd, hit);
252 }
253 }
254 }
255};
256
257
258// triangle pour le bvh, cf fonction bounds() et intersect()
259struct Triangle
260{
261 Point p; // sommet a du triangle
262 Vector e1, e2; // aretes ab, ac du triangle
263 int mesh_id;
264 int primitive_id;
265 int triangle_id;
266
267 Triangle( const vec3& a, const vec3& b, const vec3& c, const int _mesh_id, const int _primitive_id, const int _id ) :
268 p(a), e1(Vector(a, b)), e2(Vector(a, c)),
269 mesh_id(_mesh_id), primitive_id(_primitive_id), triangle_id(_id) {}
270
271 /* calcule l'intersection ray/triangle
272 cf "fast, minimum storage ray-triangle intersection"
273
274 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.)
275 renvoie vrai + les coordonnees barycentriques (u, v) du point d'intersection + sa position le long du rayon (t).
276 convention barycentrique : p(u, v)= (1 - u - v) * a + u * b + v * c
277 */
278 Hit intersect( const Ray &ray, const float htmax ) const
279 {
280 Vector pvec= cross(ray.d, e2);
281 float det= dot(e1, pvec);
282
283 float inv_det= 1 / det;
284 Vector tvec(p, ray.o);
285
286 float u= dot(tvec, pvec) * inv_det;
287 if(u < 0 || u > 1) return Hit();
288
289 Vector qvec= cross(tvec, e1);
290 float v= dot(ray.d, qvec) * inv_det;
291 if(v < 0 || u + v > 1) return Hit();
292
293 float t= dot(e2, qvec) * inv_det;
294 if(t < 0 || t > htmax) return Hit();
295
296 return Hit(t, u, v, mesh_id, primitive_id, triangle_id);
297 }
298
299 BBox bounds( ) const
300 {
301 BBox box(p);
302 return box.insert(p+e1).insert(p+e2);
303 }
304};
305
306typedef BVHT<Triangle> BVH;
307typedef BVHT<Triangle> BLAS;
308
309
310// instance pour le bvh, cf fonctions bounds() et intersect()
311struct Instance
312{
313 Transform object_transform;
314 BBox world_bounds;
315 BVH *object_bvh;
316 int instance_id;
317
318 Instance( const BBox& bounds, const Transform& model, BVH *bvh, const int id ) :
319 object_transform(Inverse(model)), world_bounds(transform(bounds, model)),
320 object_bvh(bvh),
321 instance_id(id)
322 {}
323
324 BBox bounds( ) const { return world_bounds; }
325
326 Hit intersect( const Ray &ray, const float htmax ) const
327 {
328 // transforme le rayon
329 Ray object_ray(object_transform(ray.o), object_transform(ray.d), htmax);
330 // et intersection dans le bvh de l'objet instancie...
331
332 Hit hit= object_bvh->intersect(object_ray, htmax);
333 if(hit)
334 // si intersection, stocker aussi l'indice de l'instance, cf retrouver la transformation et la matiere associee au mesh/triangle...
335 hit.instance_id= instance_id;
336
337 return hit;
338 }
339
340protected:
341 BBox transform( const BBox& bbox, const Transform& m )
342 {
343 BBox bounds= BBox( m(bbox.pmin) );
344 // enumere les sommets de la bbox
345 for(unsigned i= 1; i < 8; i++)
346 {
347 // chaque sommet de la bbox est soit pmin soit pmax sur chaque axe...
348 Point p= bbox.pmin;
349 if(i & 1) p.x= bbox.pmax.x;
350 if(i & 2) p.y= bbox.pmax.y;
351 if(i & 4) p.z= bbox.pmax.z;
352
353 // transforme le sommet de l'englobant
354 bounds.insert( m(p) );
355 }
356
357 return bounds;
358 }
359};
360
361typedef BVHT<Instance> TLAS;
362
363
364struct Sampler
365{
366 std::uniform_real_distribution<float> u01;
367 std::default_random_engine rng;
368
369 Sampler( const unsigned _seed ) : u01(), rng(_seed) {}
370 void seed( const unsigned _seed ) { rng= std::default_random_engine(_seed); }
371
372 float sample( ) { return u01(rng); }
373
374 int sample_range( const int n ) { return int(sample() * n); }
375};
376
377
378int main( int argc, char **argv )
379{
380 const char *mesh_filename= "data/robot.gltf";
381 const char *orbiter_filename= nullptr;
382
383 if(argc > 1) mesh_filename= argv[1];
384 if(argc > 2) orbiter_filename= argv[2];
385
386 GLTFScene scene= read_gltf_scene(mesh_filename);
387
388 // construit les bvh des objets de la scene, en parallele ! cf BLAS / bvh de triangles
389 std::vector<BVH *> bvhs(scene.meshes.size());
390 {
391 // parcourir les mesh
392 printf("%d meshes\n", int(scene.meshes.size()));
393
394 #pragma omp parallel for
395 for(unsigned mesh_id= 0; mesh_id < scene.meshes.size(); mesh_id++)
396 {
397 const GLTFMesh& mesh= scene.meshes[mesh_id];
398
399 // groupes de triangles du mesh
400 std::vector<Triangle> triangles;
401 for(unsigned primitive_id= 0; primitive_id < mesh.primitives.size(); primitive_id++)
402 {
403 const GLTFPrimitives& primitives= mesh.primitives[primitive_id];
404
405 for(unsigned i= 0; i +2 < primitives.indices.size(); i+= 3)
406 {
407 // extraire les positions des sommets du triangle
408 vec3 a= primitives.positions[primitives.indices[i]];
409 vec3 b= primitives.positions[primitives.indices[i+1]];
410 vec3 c= primitives.positions[primitives.indices[i+2]];
411 triangles.push_back( Triangle(a, b, c, mesh_id, primitive_id, i/3) );
412 // stocke aussi l'indice du triangle
413 }
414 }
415
416 BVH *bvh= new BVH;
417 bvh->build(triangles);
418 bvhs[mesh_id]= bvh;
419 }
420 }
421
422 // instancie les objets de la scene, cf TLAS / bvh d'instances
423 TLAS top_bvh;
424 {
425 printf("%d nodes\n", int(scene.nodes.size()));
426
427 // 1 instance par noeud de la scene gltf
428 std::vector<Instance> instances;
429 for(unsigned node_id= 0; node_id < scene.nodes.size(); node_id++)
430 {
431 const GLTFNode& node= scene.nodes[node_id];
432 const GLTFMesh& mesh= scene.meshes[node.mesh_index];
433
434 instances.push_back( Instance( BBox(mesh.pmin, mesh.pmax), node.model, bvhs[node.mesh_index], node_id ) );
435 }
436
437 top_bvh.build(instances);
438 printf("done. %d instances\n", int(instances.size()));
439 }
440
441 // recupere les matrices de la camera gltf
442 assert(scene.cameras.size());
443 Transform view= scene.cameras[0].view;
444 Transform projection= scene.cameras[0].projection;
445
446 // cree l'image en respectant les proportions largeur/hauteur de la camera gltf
447 int width= 1024;
448 int height= width / scene.cameras[0].aspect;
449 Image image(width, height);
450
451 // transformations
452 Transform model= Identity();
453 Transform viewport= Viewport(image.width(), image.height());
454 Transform inv= Inverse(viewport * projection * view * model);
455
456
457 // calcule l'image en parallele avec openMP
458#pragma omp parallel for
459 for(unsigned y= 0; y < image.height(); y++)
460 for(unsigned x= 0; x < image.width(); x++)
461 {
462 // genere le rayon pour le pixel x,y
463 Point o= inv( Point(x, y, 0) ); // origine
464 Point e= inv( Point(x, y, 1) ); // extremite
465 Ray ray(o, Vector(o, e));
466
467 // intersections !
468 if(Hit hit= top_bvh.intersect(ray))
469 {
470 assert(hit.instance_id != -1);
471 assert(hit.mesh_id != -1);
472 assert(hit.primitive_id != -1);
473 // retrouve le triangle dans le mesh et ses primitives
474 const GLTFMesh& mesh= scene.meshes[hit.mesh_id];
475 const GLTFPrimitives& primitives= mesh.primitives[hit.primitive_id];
476 // indices des sommets du triangle
477 unsigned a= primitives.indices[3*hit.triangle_id];
478 unsigned b= primitives.indices[3*hit.triangle_id+1];
479 unsigned c= primitives.indices[3*hit.triangle_id+2];
480 // c'est a ca que servent les indices ajoutes dans triangle, instance et hit !!
481
482 /* exemple : normale interpolee du point d'intersection
483 assert(primitives.normals.size());
484 Vector na= primitives.normals[a];
485 Vector nb= primitives.normals[b];
486 Vector nc= primitives.normals[c];
487
488 // normale interpolee au point d'intersection, cf coordonnees barycentriques dans le triangle
489 Vector n= (1 - hit.u - hit.v) * na + hit.u * nb + hit.v * nc;
490 // n est dans le repere local de l'objet...
491
492 // changement de repere vers la scene !
493 const Transform& model= scene.nodes[hit.instance_id].model; // recupere la transformation de l'instance...
494
495 // todo : ecrire une fonction utilitaire !!
496 */
497
498 // retrouve la couleur de base du triangle
499 Color diffuse= Yellow();
500 if(primitives.material_index != -1)
501 {
502 const GLTFMaterial& material= scene.materials[primitives.material_index];
503
504 // parametres du modele pbr / gltf : couleur / metal / rugosite
505 Color base= material.color;
506 diffuse= base;
507 }
508
509 image(x, y)= Color(diffuse, 1);
510 }
511 }
512 printf("\n");
513
514 write_image(image, "render.png");
515 return 0;
516}
representation d'une image.
Definition image.h:21
scene glTF.
int mesh_index
indice du maillage.
Definition gltf.h:131
int material_index
indice de la matiere des primitives.
Definition gltf.h:102
Transform model
transformation model pour dessiner le maillage.
Definition gltf.h:130
description d'un maillage.
Definition gltf.h:115
position et orientation d'un maillage dans la scene.
Definition gltf.h:129
groupe de triangles d'un maillage. chaque groupe est associe a une matiere.
Definition gltf.h:99
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
Color Yellow()
utilitaire. renvoie une couleur jaune.
Definition color.cpp:43
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 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
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
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
bvh parametre par le type des primitives, cf triangle et instance...
representation d'une couleur (rgba) transparente ou opaque.
Definition color.h:14
Color color
base color.
Definition gltf.h:60
intersection avec un triangle.
Definition tuto_bvh2.cpp:33
instance pour le bvh, cf fonctions bounds() et intersect().
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
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:67
vecteur generique, utilitaire.
Definition vec.h:169
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.