sources:


website:
more info here


screenshot:
studies/grafik2/Computergrafik-Code6/Aufgabe17/cgraytracer.cpp
download file

  1 #include "cgraytracer.h"
  2 #include <stdlib.h>
  3 #include <time.h>
  4
  5
  6 // watermark
  7 #pragma comment(exestr, "(C)2001-2002 by Stephan Brumme")
  8
  9
 10 // my parent's GPU driver is much too slow ... definitely !!!
 11 //#define DONT_DRAW
 12
 13
 14 static double PI = 3.1415926;
 15 static double INTERSECTION_OFFSET = 0.0001;
 16 static Color  clrBackground(0.05, 0.05, 0.1);
 17 inline double frand() { return double(rand()) / (RAND_MAX); }
 18
 19 //
 20 // CGRayTracer Application
 21 //
 22
 23
 24 CGRayTracer::CGRayTracer(unsigned int x, unsigned int y) {
 25    
 26     // general settings
 27     // use shadows ?
 28     shadows_ = true;
 29     // recursion depth
 30     maxDepth_ = 7;
 31     // no. of intersection tests
 32     nTests_ = 0;
 33     // initial detail level (no. of pixels per ray)
 34     detail_ = 1<<8;
 35     antialias_ = false;
 36
 37     // allocate memory to store the image
 38     imagex_ = x;
 39     imagey_ = y;
 40     image_  = new Color[(imagex_+1)*(imagey_+1)];
 41     for (unsigned int i=0; i<imagey_; i++)
 42         for (unsigned int j=0; j<imagex_; j++)
 43             image_[i*imagex_+j] = Color(-0.1,0,0);
 44
 45     // number of shapes
 46     shapeSize_ = 0; // incremented by AddObject
 47
 48     // create shape and material array
 49     shape_ = new Shape*[100];   
 50     material_ = new Material*[100];
 51
 52
 53
 54     // background
 55     AddObject(new Box(Vector(-1000, -1000, 4), Vector(1000, 1000, 4.1)),
 56               new Material (0.2, 0.5, 0.0, 10, 1, 1, Color(0.5,0.5,0.9)))
;
 57
 58     // lower surface
 59     AddObject(new Box(Vector(-1, -0.1, -1), Vector(1, 0, 1)),
 60               new Material (0.2, 0.5, 0.8, 10, 1, 1, Color(0.1,0.4,0.1)))
;
 61
 62     // main sphere
 63     AddObject(new Sphere (Vector(0,0,0), 0.5),
 64               new Material (0.2, 0.5, 0.7, 32, 0.3, 1, Color(0.9,0.9,0.9)))
;
 65
 66     // pyramid
 67     AddObject(new Triangle(Vector(0, 0, -0.4), Vector(0.4, 0, 0), Vector(0, 0.4, 0)),
 68               new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 69     AddObject(new Triangle(Vector(0, 0, -0.4), Vector(-0.4, 0, 0), Vector(0, 0.4, 0)),
 70               new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 71     AddObject(new Triangle(Vector(0, 0, +0.4), Vector(0.4, 0, 0), Vector(0, 0.4, 0)),
 72               new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 73     AddObject(new Triangle(Vector(0, 0, +0.4), Vector(-0.4, 0, 0), Vector(0, 0.4, 0)),
 74               new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 75
 76     // sphere on top of the pyramid
 77     AddObject(new Sphere (Vector(0,0.45,0), 0.08),
 78               new Material (0.2, 0.5, 0.99, 32, 1, 1, Color(1.0,0.3,0.3)))
;
 79
 80     // two arcs
 81     const int ARCSIZE = 14;
 82     for (i=0; i<ARCSIZE; i++)
 83     {
 84         const double arc_angle = ((i+0.5)*180.0 / (ARCSIZE-1)) * PI/180;
 85
 86         // arc 1
 87         AddObject(new Sphere (Vector(+cos(arc_angle)*0.6, sin(arc_angle)*0.8, cos(arc_angle)*0.6), 0.05),
 88                   new Material (0.2, 0.5, 0.7, 32, 0.3, 1.3, Color(0.8,0.8,0.8)))
;
 89         AddObject(new Sphere (Vector(+cos(arc_angle)*0.6, sin(arc_angle)*0.8, cos(arc_angle)*0.6), 0.01),
 90                   new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 91
 92         // arc 2
 93         AddObject(new Sphere (Vector(-cos(arc_angle)*0.6, sin(arc_angle)*0.8, cos(arc_angle)*0.6), 0.05),
 94                   new Material (0.2, 0.5, 0.7, 32, 0.3, 1.3, Color(0.8,0.8,0.8)))
;
 95         AddObject(new Sphere (Vector(-cos(arc_angle)*0.6, sin(arc_angle)*0.8, cos(arc_angle)*0.6), 0.01),
 96                   new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8)))
;
 97     }
 98
 99     // ring
100     const int RINGSIZE = 25;
101     for (i=0; i<RINGSIZE; i++)
102     {
103         const double ring_angle = (i * 360 / RINGSIZE) * PI/180;
104         AddObject(new Sphere (Vector(cos(ring_angle)*0.5, 0.07, sin(ring_angle)*0.5), 0.03),
105                   new Material (0.2, 0.5, 0.9, 32, 1, 1, Color(0.2,0.2,0.2)))
;
106     }
107
108
109     // set camera
110     camera_ = new Camera(Vector(0.4, 2, -2), // from
111                          Vector(0, 0, 0) , // to
112                          Vector(0, 1, 0)// up
113                          90)
;              // fov
114
115     // set light sources
116     lightSize_ = 2;
117     light_ = new Light*[lightSize_];
118     light_[0] = new Light( Vector(2, 2.3, -1.5), 0.4, 0.1 );
119     light_[1] = new Light( Vector(-1, 1, -1.5), 0.6, 0.1 );
120
121
122     // INITIAL TEST SCENE, NOT USED ANYMORE !!!
123
124     // main sphere
125 /* AddObject(new Sphere (Vector(0,0,0), 0.4), new Material (0.2, 0.5, 0.7, 32, 0.3, 1, Color(0.8,0.8,0.8))); AddObject(new Sphere (Vector(0,0.1,0), 0.1), new Material (0.2, 0.5, 0.7, 32, 1, 1, Color(0.8,0.8,0.8))); // upper right sphere // AddObject(new Sphere (Vector(0.5,0.5,-0.5), 0.15), // new Material (0.2, 0.6, 0.8, 100, 0.5, 1.3, Color(0.6,0.6,1.0))); // ground AddObject(new Box (Vector(-0.7,-0.2,-0.7), Vector(0.7,-0.15,0.7)), new Material (0.2, 0.5, 0.5, 10, 1, 1, Color(0.5,0.5,0.9))); // upper right box AddObject(new Box (Vector(-0.05,0.3,-0.6), Vector(0.1,0.4,-0.4)), new Material (0.2, 0.5, 0.5, 10, 1, 1, Color(0.5,0.9,0.5))); // upper box AddObject(new Box (Vector(-0.25,0.3,-0.4), Vector(-0.15,0.7,-0.3)), new Material (0.2, 0.3, 0.5, 10, 1, 1, Color(0.5,0.5,0.5))); // back plane AddObject(new Triangle(Vector (-2, -2, 0.7), Vector ( 2, 1.8, 0.7), Vector ( 2, -2, 0.7)), new Material (0.2, 0.3, 0.7, 10, 1, 1, Color(0.5,0.5,0.5))); // bottom plane AddObject(new Box (Vector(-1, -1, -1), Vector(1,-0.9,1)), new Material (0.2, 0.3, 0.5, 10, 1, 1, Color(0.8,0.5,0.5))); AddObject(new Triangle(Vector(-0.8, -0.5, -1), Vector(0.8,-0.5,1), Vector(0.8,-0.5,-1)), new Material (0.2, 0.3, 0.5, 10, 1, 1, Color(0.5,0.9,0.5))); // mid-scene triangles AddObject(new Triangle(Vector (0.4, -0.1, -0.6), Vector (0.6, -0.1, -0.6), Vector (0.6, -0.1, -0.4)), new Material (0.2, 0.3, 0.7, 10, 0.8, 1, Color(0.5,0.9,0.5))); AddObject(new Triangle(Vector (0.5, 0.0, -0.7), Vector (0.7, 0.0, -0.7), Vector (0.7, 0.0, -0.5)), new Material (0.2, 0.3, 0.7, 10, 0.5, 1, Color(0.5,0.9,0.5))); // back plane // AddObject(new Box(Vector(-2, -2, 1.2), Vector(2, 2, 1.3)), // new Material (0.1, 0.2, 0.6, 50, 1, 1, Color(0.9,0.9,0.9))); // sphere on da right side AddObject(new Sphere (Vector(0.6,-0.05,-0.1), 0.05), new Material (0.2, 0.2, 0.8, 100, 1, 1, Color(1,0.5,0.5))); */
126 }
127
128
129 CGRayTracer::~CGRayTracer() {   
130     // clean up
131     for (int i = 0; i < shapeSize_; i++) {
132         delete shape_[i];
133         delete material_[i];
134     }
135     for (int j = 0; j < lightSize_; j++) {
136         delete light_[j];   
137     }
138
139     delete[] shape_;
140     delete[] material_;
141     delete[] light_;
142 }
143
144 void CGRayTracer::AddObject(Shape* shape, Material* material)
145 {
146     shape_[shapeSize_] = shape;
147     material_[shapeSize_] = material;
148
149     shapeSize_++;
150 }
151
152 inline double max(double x, double y) {
153     return (x<y) ? y : x;
154 }
155 inline double min(double x, double y) {
156     return (x>y) ? y : x;
157 }
158
159 Color CGRayTracer::phongIlluminationColor(const Vector& point, const Vector& normal, Material* material) {
160     // calculate Phong illumination
161
162     // code is a slightly modified copy of my Phong homework
163
164     // define intensity variables
165     double dIntensityAmbient  = 0;
166     double dIntensityDiffuse  = 0;
167     double dIntensitySpecular = 0;
168
169     // N = normal
170     const Vector N_norm = normal.normalized();
171     // V = view vector
172     const Vector V = camera_->from - point;
173     const Vector V_norm = V.normalized();
174
175     // process all light sources
176     for (int nLight=0; nLight < lightSize_; nLight++)
177     {
178         // get a single light source
179         const Light* const light = light_[nLight];
180
181         // ALWAYS ambient intensity, even if shadowed
182         dIntensityAmbient  += light->ambient;
183
184         // L = light vector
185         const Vector L = light->pos - point;
186         const Vector L_norm = L.normalized();
187
188         // N*L
189         const double NdotL = dotProduct(N_norm, L_norm);
190
191         // intensity reaching the surface (attenuation)
192         const double dLightDistance = abs(L);
193         double dMaxIntensity = 1.0 / (light->att_constant + dLightDistance*light->att_linear
194                                          + dLightDistance*dLightDistance*light->att_quadric)
;
195         if (dMaxIntensity > 1)
196             dMaxIntensity = 1;
197
198         // get shadow
199         if (shadows_)
200         {
201             // shadow ray must not start at the object's surface !
202             // (else we get in trouble with intersection problems)
203             const Ray shadowray(point+(light->pos - point)*INTERSECTION_OFFSET, light->pos - point);
204             bool shadow = false;
205
206             Shape** ppShape = shape_;
207             for (int shape=0; shape<shapeSize_; shape++)
208             {
209                 // get values of each intersection
210                 double distance;
211                 static Vector _normal, _point;
212
213                 // does the ray intersect the object ?
214                 shadow = shape_[shape]->intersect(shadowray, _point, _normal, distance);
215                 nTests_++;
216
217                 // intersection between light source and surface point ?
218                 if (shadow && distance < dLightDistance)
219                 {
220                     dMaxIntensity *= 1-material_[shape]->opacity;
221                     if (dMaxIntensity > 0.01)
222                         shadow = false;
223                     else                         // 100% shadow
224                         break;
225                 }
226             }
227             // shadowed ! no diffuse or specular light possible
228             if (shadow)
229                 continue;
230         }
231
232         // R*V = (2*N*(N*L)-L)*V
233         const double RdotV = max(dotProduct((2*NdotL*N_norm - L_norm).normalized(), V_norm), 0);
234
235         // diffuse intensity
236         dIntensityDiffuse  += dMaxIntensity * material->kd * NdotL;
237         // specular intensity
238         dIntensitySpecular += dMaxIntensity * material->ks * pow(RdotV, material->n);
239     }
240
241     // return clamped color
242     return material->color * min(dIntensityAmbient+dIntensityDiffuse+dIntensitySpecular,1);
243 }
244
245 Color CGRayTracer::rayTrace(const Ray& r, int nDepth) {
246     // Check each shape for intersektion.
247     // Return phong color of nearest shape
248     // that is hit.
249
250     if (nDepth++ > maxDepth_)
251         return clrBackground;
252
253     // shape that we found
254     int closestShape = -1;
255     // its parameters
256     double closestDistance = 99999999;
257     Vector closestPoint;
258     Vector closestNormal;
259
260     Shape** ppShape = shape_;
261     for (int shape=0; shape<shapeSize_; shape++)
262     {
263         // get values of each intersection
264         double distance;
265         static Vector normal;
266         static Vector point;
267
268         // internal counter
269         nTests_++;
270
271         // does the ray intersect the object ?
272         if (shape_[shape]->intersect(r, point, normal, distance))
273             // closer than any intersection we tested before ?
274             if (distance < closestDistance)
275             {
276                 closestDistance = distance;
277                 closestShape    = shape;
278                 closestPoint    = point;
279                 closestNormal   = normal;
280             }
281     }
282
283     // no intersection found
284     if (closestShape < 0)
285         return clrBackground;
286     Material& material = *(material_[closestShape]);
287
288    
289     // object itself
290     Color color = phongIlluminationColor(closestPoint, closestNormal, &material);
291
292     // reflection
293     if (material.ks >= 0.001)
294     {
295         // get reflected ray
296         double reflected_angle     = -dotProduct(closestNormal, r.getDirection());
297         if (reflected_angle < 0)
298         {
299             closestNormal   *= -1;
300             reflected_angle *= -1;
301         }
302
303         const Vector reflected_direction = r.getDirection() + 2*reflected_angle*closestNormal;
304
305         // reflected ray must not start at the object's surface !
306         // (else we get in trouble with intersection problems)
307         const Ray reflected_ray(closestPoint+closestNormal*INTERSECTION_OFFSET,
308                                 reflected_direction, material.lightspeed)
;
309
310         // get color
311         const Color reflected_color = rayTrace(reflected_ray, nDepth) * material.ks * material.opacity;
312
313         // add reflective part
314         color += reflected_color;
315     }
316
317     // refraction
318     if (material.opacity < 1)
319     {
320         double lightspeedratio = r.getLightspeed() / material.lightspeed;
321
322         double refracted_angle     = -dotProduct(closestNormal, r.getDirection());
323         if (refracted_angle < 0)
324         {
325             closestNormal   *= -1;
326             lightspeedratio = 1/lightspeedratio;
327         }
328
329         // compute refraction vector (its direction)
330         const Vector L       = -r.getDirection();
331         const double NdotL   = dotProduct(closestNormal, L);
332         // Snell's Law (splitted up for better readibility)
333         const double Root    = 1 - lightspeedratio*lightspeedratio*(1 - NdotL*NdotL);
334         const double Bracket = lightspeedratio*NdotL - sqrt(Root);
335         const Vector T       = Bracket*closestNormal - lightspeedratio*L;
336
337         // slight shift of new ray origin
338         Vector T_origin      = closestPoint;
339         if (NdotL < 0)
340             T_origin += closestNormal*10*INTERSECTION_OFFSET;
341         else             T_origin -= closestNormal*10*INTERSECTION_OFFSET;
342
343         const Ray refracted_ray(T_origin, T, material.lightspeed);
344                                 //r.getDirection(), material.lightspeed);
345        
346         // get color
347         const Color refracted_color = rayTrace(refracted_ray, nDepth) * (1-material.opacity);
348
349         // add reflective part
350         color *= material.opacity;
351         color += refracted_color;
352     }
353
354     // clamp color
355     color[0] = min(color[0], 1);
356     color[1] = min(color[1], 1);
357     color[2] = min(color[2], 1);
358 // color[0] = max(color[0], 0);
359 // color[1] = max(color[1], 0);
360 // color[2] = max(color[2], 0);
361
362     return color;
363 }
364
365 void CGRayTracer::writePPMPixel(const Color& c) {
366     // write pixel to file
367     (*out_) << ((unsigned char)(c[0]*255))
368             << ((unsigned char)(c[1]*255))
369             << ((unsigned char)(c[2]*255));
370 }
371    
372 void CGRayTracer::writeImage(char* fileName) {
373
374     cout << "saving " << fileName << " - ";
375     // create output stream
376     out_ = new ofstream(fileName);
377
378     // write PPM header
379 #ifdef _MSC_VER
380     (*out_) << binary;
381 #endif
382     (*out_) << "P6" << endl
383             << width_ << " " << height_ << endl             << 255 << endl;
384    
385     // write all pixel
386     for(unsigned int i=0; i<imagey_; i++)
387         for(unsigned int j=0; j<imagex_; j++)
388             writePPMPixel(image_[i*imagex_+j]);
389
390     // delete output stream
391     delete out_;
392
393     cout << "done." << endl;
394 }
395
396
397 void CGRayTracer::createImage(int width, int height) {
398     // convert field-of-view angle to rad
399     const double fovy_rad = camera_->fovy * PI/180;
400
401     // look vector
402     const Vector Look  = camera_->to - camera_->from;
403
404     // delta angles
405     const double up_angle_delta    = fovy_rad / height;
406     const double right_angle_delta = fovy_rad / height;//width;
407
408     // normalized vectors that describe the view plane (right handed !)
409     const Vector Right = camera_->up.normalized() * Look.normalized();
410     const Vector Up    = Right * Look.normalized();
411
412     // image
413     const double widthoffset  = imagex_/(double)width;
414     const double heightoffset = imagey_/(double)height;
415     const double pixelsize = detail_;
416
417     for(int i=0; i<height; i++)
418     {
419         for(int j=0; j<width; j++)
420         {
421             // create rays for each pixel and send through scene
422
423             // skip if already calculated
424             const int truex = int(j*widthoffset);
425             const int truey = int(i*heightoffset);
426             Color& clrPixel = image_[truey*imagex_+truex];
427             if (clrPixel[0] >= 0)
428             {
429                 // draw already calculated pixels
430 #ifndef DONT_DRAW
431                 glColor3dv(clrPixel.rep());
432                 glRectd(truex, height_-truey,
433                         truex+pixelsize, height_-(truey+pixelsize))
;
434 #endif
435                 continue;
436             }
437
438             // angles
439             const double up_angle    = (i-height/2.0) * up_angle_delta;
440             const double right_angle = (j-width /2.0) * right_angle_delta;
441
442             // correspending vectors
443             const Vector CurrentUp    = Up    * abs(Look) * tan(up_angle);
444             const Vector CurrentRight = Right * abs(Look) * tan(right_angle);
445
446             // put it all together
447             const Vector LookAt = Look + CurrentUp + CurrentRight;
448
449             // ray tracing
450             clrPixel = rayTrace(Ray(camera_->from, LookAt));
451
452             // draw pixel
453 #ifndef DONT_DRAW
454             glColor3dv(clrPixel.rep());
455             glRectd(truex, height_-truey,
456                     truex+pixelsize, height_-(truey+pixelsize))
;
457 #endif
458         }
459     }
460 }
461
462
463 void CGRayTracer::onInit() {
464     // no perspective drawing, just pure 2D display
465     glMatrixMode(GL_PROJECTION);
466     glLoadIdentity();
467     gluOrtho2D(0, imagex_-1, 0, imagey_-1);
468
469     glClearColor(clrBackground[0], clrBackground[1], clrBackground[2],1);
470     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
471
472     // start timer
473     start_ = clock();
474 }
475
476 void CGRayTracer::onSize(unsigned int newWidth,unsigned int newHeight) {         
477     width_ = newWidth;
478     height_ = newHeight; 
479    
480     glViewport(0,0, width_-1, height_-1);
481     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
482     onDraw();
483 }
484
485 void CGRayTracer::onKey(unsigned char key) {
486     switch (key) {
487     case 27: { exit(0); break; }                       
488     case 'a': antialias_ = !antialias_; onDraw(); break;
489     }
490 }
491
492 void CGRayTracer::onIdle() {
493     if (detail_ > 1)
494     {
495         // next detail level
496         detail_ /= 2;
497         createImage(imagex_ / detail_, imagey_ / detail_);
498
499         if (detail_ > 1)
500         {
501             // detail level completed
502             cout << detail_ << "x" << detail_ << " level done" << endl;
503         }
504         else         {
505             // stop timer
506             clock_t finish = clock();
507             cout << "... image completed !" << endl;
508
509             writeImage("raytracer.ppm");
510            
511             double seconds = (finish - start_)/(double)CLOCKS_PER_SEC;
512             // show some statistics
513             cout << endl                  << imagey_ << "x" << imagex_ << " pixels rendered using " << nTests_ << " intersection tests in "
514                  << seconds << " seconds" << endl                  << "=> " << int(imagex_*imagey_/seconds) << " pixels/sec and "
515                  << nTests_/double(imagex_*imagey_) << " tests/pixel (ray depth=" << maxDepth_ << ")" << endl;
516
517         }
518     }
519 }
520
521 void CGRayTracer::onDraw() {
522 // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
523
524     // "pixel" size
525     const double size = detail_;
526
527     if (!antialias_ || detail_ > 1)
528         // process all pixels
529         for (unsigned int i=0; i<imagey_; i++)
530             for (unsigned int j=0; j<imagex_; j++)
531             {
532                 // draw only if already calculated
533                 const Color& color = image_[i*imagex_+j];
534                 if (color[0] >= 0)
535                 {
536                     glColor3dv(color.rep());
537                     glRectd(j, imagey_-i, j+size, imagey_-(i+size));
538                 }
539             }
540     else         // process all pixels
541         for (unsigned int i=0; i<imagey_; i++)
542             for (unsigned int j=0; j<imagex_; j++)
543             {
544                 if (i==0 || j==0)
545                 {
546                     glColor3dv(image_[i*imagex_+j].rep());
547                     glRectd(j, imagey_-i, j+size, imagey_-(i+size));
548                 }
549                 else                 {
550                     // weight neighbours
551                     const Color color = (image_[(i-1)*imagex_+j] +
552                                          image_[(i-1)*imagex_+j-1] +
553                                          image_[(i-1)*imagex_+j+1] +
554                                        4*image_[ i   *imagex_+j] +
555                                          image_[ i   *imagex_+j-1] +
556                                          image_[ i   *imagex_+j+1] +
557                                          image_[(i+1)*imagex_+j] +
558                                          image_[(i+1)*imagex_+j-1] +
559                                          image_[(i+1)*imagex_+j+1])
/ 12;
560                     glColor3dv(color.rep());
561                     glRectd(j, imagey_-i, j+size, imagey_-(i+size));
562                 }
563             }
564 }
565
566
567 // Hauptprogramm
568 int main(int argc, char* argv[]) {
569
570     unsigned int maxx;
571     unsigned int maxy;
572
573     cout << "Bildformat bitte eingeben !" << endl          << "Breite: ";
574     cin  >> maxx;
575     cout << "Hoehe : ";
576     cin  >> maxy;
577    
578     // Erzeuge eine Instanz der Beispiel-Anwendung:
579     CGRayTracer sample(maxx, maxy);
580
581     cout << endl          << "Tastenbelegung:" << endl          << "ESC Programm beenden" << endl // << "a Antialiasing ein/aus" << endl
582          << endl          << "Das Bild wird automatisch unter \"raytracer.ppm===>\<===" gespeichert." << endl << endl;
583
584     // Starte die Beispiel-Anwendung:
585    
586     sample.start("Stephan Brumme, 702544", false, maxx, maxy);
587
588     return 0;
589 }
590
591