gKit2 light
pipeline.cpp
1 
2 #include <cstdio>
3 #include <cmath>
4 
5 #include "vec.h"
6 #include "mat.h"
7 
8 #include "mesh.h"
9 #include "image.h"
10 #include "image_io.h"
11 #include "orbiter.h"
12 
13 #include "wavefront.h"
14 
15 
16 struct ZBuffer
17 {
18  std::vector<float> data;
19  int width;
20  int height;
21 
22  ZBuffer( const int w, const int h, const float z= 1 ) : data(w*h, z), width(w), height(h) {}
23 
24  void clear( const float value= 1 ) { data.assign(width * height, value); }
25 
26  float& operator() ( const int x, const int y )
27  {
28  std::size_t offset= y * width + x;
29  return data[offset];
30  }
31 };
32 
33 
34 struct Fragment
35 {
36  float x, y, z; // coordonnees espace image
37  float u, v, w; // coordonnees barycentriques du fragment dans le triangle abc, p(u, v, w) = u * c + v * a + w * b;
38 };
39 
40 
41 // interface
42 struct Pipeline
43 {
44  Pipeline( ) {}
45  virtual ~Pipeline( ) {}
46 
47  // vertex shader, doit renvoyer les coordonnees du sommet dans le repere projectif
48  virtual Point vertex_shader( const int vertex_id ) const = 0;
49 
50  // fragment shader, doit renvoyer la couleur du fragment de la primitive
51  // doit interpoler lui meme les "varyings", fragment.uvw definissent les coefficients.
52  virtual Color fragment_shader( const int primitive_id, const Fragment fragment ) const = 0;
53  // pour simplifier le code, les varyings n'existent pas dans cette version,
54  // il faut recuperer les infos des sommets de la primitive et faire l'interpolation.
55  // remarque : les gpu amd gcn fonctionnent comme ca...
56 };
57 
58 // pipeline simple
59 struct BasicPipeline : public Pipeline
60 {
61  const Mesh& mesh;
62  Transform model;
63  Transform view;
64  Transform projection;
65  Transform mvp;
66  Transform mv;
67 
68  BasicPipeline( const Mesh& _mesh, const Transform& _model, const Transform& _view, const Transform& _projection )
69  : Pipeline(), mesh(_mesh), model(_model), view(_view), projection(_projection)
70  {
71  mvp= projection * view * model;
72  mv= Normal(view * model);
73  }
74 
75  Point vertex_shader( const int vertex_id ) const
76  {
77  // recupere la position du sommet
78  Point p= Point( mesh.positions().at(vertex_id) );
79  // renvoie les coordonnees dans le repere projectif
80  return mvp(p);
81  }
82 
83  Color fragment_shader( const int primitive_id, const Fragment fragment ) const
84  {
85  // recuperer les normales des sommets de la primitive
86  Vector a= mv( Vector( mesh.normals().at(primitive_id * 3) ));
87  Vector b= mv( Vector( mesh.normals().at(primitive_id * 3 +1) ));
88  Vector c= mv( Vector( mesh.normals().at(primitive_id * 3 +2) ));
89 
90  // interpoler la normale
91  Vector n= fragment.u * c + fragment.v * a + fragment.w * b;
92  // et la normaliser, l'interpolation ne conserve pas la longueur des vecteurs
93  n= normalize(n);
94 
95  // calcule une couleur qui depend de l'orientation de la primitive par rapport a la camera
96  return White() * std::abs(n.z);
97 
98  // on peut faire autre chose, par exemple, afficher directement la normale...
99  // return Color(std::abs(n.x), std::abs(n.y), std::abs(n.z));
100  }
101 };
102 
103 
104 // cf http://geomalgorithms.com/a01-_area.html, section modern triangles
105 float area( const Point p, const Point a, const Point b )
106 {
107  Vector pa= Vector(p, a); pa.z= 0;
108  Vector pb= Vector(p, b); pb.z= 0;
109  return cross(pa, pb).z;
110 }
111 
112 
113 bool visible( const Point p )
114 {
115  if(p.x < -1 || p.x > 1) return false;
116  if(p.y < -1 || p.y > 1) return false;
117  if(p.z < -1 || p.z > 1) return false;
118  return true;
119 }
120 
121 
122 int main( int argc, char **argv )
123 {
124  Image color(640, 320);
125  ZBuffer depth(color.width(), color.height());
126 
127  Mesh mesh= read_mesh("data/bigguy.obj");
128  if(mesh == Mesh::error())
129  return 1;
130  printf(" %d positions\n", mesh.vertex_count());
131  printf(" %d indices\n", mesh.index_count());
132 
133  // regle le point de vue de la camera pour observer l'objet
134  Point pmin, pmax;
135  mesh.bounds(pmin, pmax);
136  Orbiter camera(pmin, pmax);
137 
138  BasicPipeline pipeline(
139  mesh,
140  Identity(),
141  camera.view(),
142  camera.projection(color.width(), color.height(), 45) );
143 
144  Transform viewport= Viewport(color.width(), color.height());
145 
146  // draw(pipeline, mesh.vertex_count());
147  for(unsigned int i= 0; i +2 < (unsigned int) mesh.vertex_count(); i= i +3)
148  {
149  // transforme les 3 sommets du triangle
150  Point a= pipeline.vertex_shader(i);
151  Point b= pipeline.vertex_shader(i+1);
152  Point c= pipeline.vertex_shader(i+2);
153 
154  // visibilite
155  if(visible(a) == false && visible(b) == false && visible(c) == false)
156  continue;
157  // faux dans pas mal de cas...
158  // question : comment faire un test correct ?
159  // indication : si tous les sommets sont du meme cote d'une face de la region observee par la camera, on est sur que le triangle n'est pas visible.
160  // comment definir la region observee par la camera ? quelle est sa forme (dans quel repere) ? les coordonnees de ses sommets ?
161 
162  // passage dans le repere image
163  a= viewport(a);
164  b= viewport(b);
165  c= viewport(c);
166 
167  // question: comment ne pas dessiner le triangle s'il est mal oriente ?
168  // aire du triangle abc
169  float n= area(a, b, c);
170  if(n < 0)
171  continue;
172 
173  // dessiner le triangle
174  // solution naive, parcours tous les pixels de l'image
175  // question : comment eviter de tester tous les pixels ?
176  // indice : il est sans doute possible de determiner que le triangle ne touche pas un bloc de pixels en ne testant que les 4 coins du bloc...
177  // question : dessiner de tout petits triangles est tres long. comment gagner du temps en determinant qu'un triangle est suffisament petit/etire pour "passer" entre les pixels ?
178  // (remarque: oui, ca arrive tout le temps...)
179  for(int y= 0; y < color.height(); y++)
180  for(int x= 0; x < color.width(); x++)
181  {
182  // fragment
183  Fragment frag;
184  frag.u= area(Point(x, y, 0), a, b); // distance c / ab
185  frag.v= area(Point(x, y, 0), b, c); // distance a / bc
186  frag.w= area(Point(x, y, 0), c, a); // distance b / ac
187 
188  if(frag.u > 0 && frag.v > 0 && frag.w > 0)
189  {
190  // normalise les coordonnees barycentriques du fragment
191  frag.u= frag.u / n;
192  frag.v= frag.v / n;
193  frag.w= frag.w / n;
194 
195  frag.x= x;
196  frag.y= y;
197  // interpole z
198  frag.z= frag.u * c.z + frag.v * a.z + frag.w * b.z;
199 
200  // evalue la couleur du fragment du triangle
201  Color frag_color= pipeline.fragment_shader(i/3, frag);
202 
203  // ztest
204  if(frag.z < depth(x, y))
205  {
206  color(x, y)= Color(frag_color, 1);
207  depth(x, y)= frag.z;
208  }
209 
210  // question : pour quelle raison le ztest est-il fait apres l'execution du fragment shader ? est-ce obligatoire ?
211  // question : peut on eviter d'executer le fragment shader sur un bloc de pixels couverts par le triangle ?
212  // dans quelles conditions sait-on qu'il n'y a rien a dessiner dans un bloc de pixels ?
213  // == aucun fragment du triangle appartenant au bloc, ne peut modifier l'image et le zbuffer ?
214  }
215  }
216  }
217 
218  write_image(color, "render.png");
219  return 0;
220 }
representation de la camera, type orbiter, placee sur une sphere autour du centre de l'objet...
Definition: orbiter.h:16
Vector normalize(const Vector &v)
renvoie un vecteur unitaire / longueur == 1.
Definition: vec.cpp:79
representation d'un objet / maillage.
Definition: mesh.h:88
representation d'une couleur (rgba) transparente ou opaque.
Definition: color.h:13
Transform Identity()
construit la transformation identite.
Definition: mat.cpp:103
Transform Viewport(const float width, const float height)
renvoie la matrice representant une transformation viewport.
Definition: mat.cpp:221
Transform Normal(const Transform &m)
renvoie la transformation a appliquer aux normales d'un objet transforme par la matrice m...
Definition: mat.cpp:118
Transform view() const
renvoie la transformation vue.
Definition: orbiter.cpp:40
representation d'un vecteur 3d.
Definition: vec.h:42
Color White()
utilitaire. renvoie une couleur blanche.
Definition: color.cpp:10
Transform projection(const float width, const float height, const float fov) const
renvoie la projection reglee pour une image d'aspect width / height, et une ouverture de fov degres...
Definition: orbiter.cpp:47
static Mesh & error()
Definition: mesh.h:250
bool value(Widgets &w, const char *label, int &value, const int value_min, const int value_max, const int value_step)
valeur editable par increment.
Definition: widgets.cpp:191
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
representation d'une transformation, une matrice 4x4, organisee par ligne / row major.
Definition: mat.h:20
int write_image(const Image &image, const char *filename)
enregistre une image dans un fichier png.
Definition: image_io.cpp:88
representation d'une image.
Definition: image.h:18
representation d'un point 3d.
Definition: vec.h:19
Vector cross(const Vector &u, const Vector &v)
renvoie le produit vectoriel de 2 vecteurs.
Definition: vec.cpp:85
Mesh read_mesh(const char *filename)
charge un fichier wavefront .obj et renvoie un mesh compose de triangles non indexes. utiliser glDrawArrays pour l'afficher. a detruire avec Mesh::release( ).
Definition: wavefront.cpp:8