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
224 // 100% shadow
225 break;
226 }
227 }
228 // shadowed ! no diffuse or specular light possible
229 if (shadow)
230 continue;
231 }
232
233 // R*V = (2*N*(N*L)-L)*V
234 const double RdotV = max(dotProduct((2*NdotL*N_norm - L_norm).normalized(), V_norm), 0);
235
236 // diffuse intensity
237 dIntensityDiffuse += dMaxIntensity * material->kd * NdotL;
238 // specular intensity
239 dIntensitySpecular += dMaxIntensity * material->ks * pow(RdotV, material->n);
240 }
241
242 // return clamped color
243 return material->color * min(dIntensityAmbient+dIntensityDiffuse+dIntensitySpecular,1);
244 }
245
246 Color CGRayTracer::rayTrace(const Ray& r, int nDepth) {
247 // Check each shape for intersektion.
248 // Return phong color of nearest shape
249 // that is hit.
250
251 if (nDepth++ > maxDepth_)
252 return clrBackground;
253
254 // shape that we found
255 int closestShape = -1;
256 // its parameters
257 double closestDistance = 99999999;
258 Vector closestPoint;
259 Vector closestNormal;
260
261 Shape** ppShape = shape_;
262 for (int shape=0; shape<shapeSize_; shape++)
263 {
264 // get values of each intersection
265 double distance;
266 static Vector normal;
267 static Vector point;
268
269 // internal counter
270 nTests_++;
271
272 // does the ray intersect the object ?
273 if (shape_[shape]->intersect(r, point, normal, distance))
274 // closer than any intersection we tested before ?
275 if (distance < closestDistance)
276 {
277 closestDistance = distance;
278 closestShape = shape;
279 closestPoint = point;
280 closestNormal = normal;
281 }
282 }
283
284 // no intersection found
285 if (closestShape < 0)
286 return clrBackground;
287 Material& material = *(material_[closestShape]);
288
289
290 // object itself
291 Color color = phongIlluminationColor(closestPoint, closestNormal, &material);
292
293 // reflection
294 if (material.ks >= 0.001)
295 {
296 // get reflected ray
297 double reflected_angle = -dotProduct(closestNormal, r.getDirection());
298 if (reflected_angle < 0)
299 {
300 closestNormal *= -1;
301 reflected_angle *= -1;
302 }
303
304 const Vector reflected_direction = r.getDirection() + 2*reflected_angle*closestNormal;
305
306 // reflected ray must not start at the object's surface !
307 // (else we get in trouble with intersection problems)
308 const Ray reflected_ray(closestPoint+closestNormal*INTERSECTION_OFFSET,
309 reflected_direction, material.lightspeed);
310
311 // get color
312 const Color reflected_color = rayTrace(reflected_ray, nDepth) * material.ks * material.opacity;
313
314 // add reflective part
315 color += reflected_color;
316 }
317
318 // refraction
319 if (material.opacity < 1)
320 {
321 double lightspeedratio = r.getLightspeed() / material.lightspeed;
322
323 double refracted_angle = -dotProduct(closestNormal, r.getDirection());
324 if (refracted_angle < 0)
325 {
326 closestNormal *= -1;
327 lightspeedratio = 1/lightspeedratio;
328 }
329
330 // compute refraction vector (its direction)
331 const Vector L = -r.getDirection();
332 const double NdotL = dotProduct(closestNormal, L);
333 // Snell's Law (splitted up for better readibility)
334 const double Root = 1 - lightspeedratio*lightspeedratio*(1 - NdotL*NdotL);
335 const double Bracket = lightspeedratio*NdotL - sqrt(Root);
336 const Vector T = Bracket*closestNormal - lightspeedratio*L;
337
338 // slight shift of new ray origin
339 Vector T_origin = closestPoint;
340 if (NdotL < 0)
341 T_origin += closestNormal*10*INTERSECTION_OFFSET;
342 else
343 T_origin -= closestNormal*10*INTERSECTION_OFFSET;
344
345 const Ray refracted_ray(T_origin, T, material.lightspeed);
346 //r.getDirection(), material.lightspeed);
347
348 // get color
349 const Color refracted_color = rayTrace(refracted_ray, nDepth) * (1-material.opacity);
350
351 // add reflective part
352 color *= material.opacity;
353 color += refracted_color;
354 }
355
356 // clamp color
357 color[0] = min(color[0], 1);
358 color[1] = min(color[1], 1);
359 color[2] = min(color[2], 1);
360 // color[0] = max(color[0], 0);
361 // color[1] = max(color[1], 0);
362 // color[2] = max(color[2], 0);
363
364 return color;
365 }
366
367 void CGRayTracer::writePPMPixel(const Color& c) {
368 // write pixel to file
369 (*out_) << ((unsigned char)(c[0]*255))
370 << ((unsigned char)(c[1]*255))
371 << ((unsigned char)(c[2]*255));
372 }
373
374 void CGRayTracer::writeImage(char* fileName) {
375
376 cout << "saving " << fileName << " - ";
377 // create output stream
378 out_ = new ofstream(fileName);
379
380 // write PPM header
381 #ifdef _MSC_VER
382 (*out_) << binary;
383 #endif
384 (*out_) << "P6" << endl
385 << width_ << " " << height_ << endl
386 << 255 << endl;
387
388 // write all pixel
389 for(unsigned int i=0; i<imagey_; i++)
390 for(unsigned int j=0; j<imagex_; j++)
391 writePPMPixel(image_[i*imagex_+j]);
392
393 // delete output stream
394 delete out_;
395
396 cout << "done." << endl;
397 }
398
399
400 void CGRayTracer::createImage(int width, int height) {
401 // convert field-of-view angle to rad
402 const double fovy_rad = camera_->fovy * PI/180;
403
404 // look vector
405 const Vector Look = camera_->to - camera_->from;
406
407 // delta angles
408 const double up_angle_delta = fovy_rad / height;
409 const double right_angle_delta = fovy_rad / height;//width;
410
411 // normalized vectors that describe the view plane (right handed !)
412 const Vector Right = camera_->up.normalized() * Look.normalized();
413 const Vector Up = Right * Look.normalized();
414
415 // image
416 const double widthoffset = imagex_/(double)width;
417 const double heightoffset = imagey_/(double)height;
418 const double pixelsize = detail_;
419
420 for(int i=0; i<height; i++)
421 {
422 for(int j=0; j<width; j++)
423 {
424 // create rays for each pixel and send through scene
425
426 // skip if already calculated
427 const int truex = int(j*widthoffset);
428 const int truey = int(i*heightoffset);
429 Color& clrPixel = image_[truey*imagex_+truex];
430 if (clrPixel[0] >= 0)
431 {
432 // draw already calculated pixels
433 #ifndef DONT_DRAW
434 glColor3dv(clrPixel.rep());
435 glRectd(truex, height_-truey,
436 truex+pixelsize, height_-(truey+pixelsize));
437 #endif
438 continue;
439 }
440
441 // angles
442 const double up_angle = (i-height/2.0) * up_angle_delta;
443 const double right_angle = (j-width /2.0) * right_angle_delta;
444
445 // correspending vectors
446 const Vector CurrentUp = Up * abs(Look) * tan(up_angle);
447 const Vector CurrentRight = Right * abs(Look) * tan(right_angle);
448
449 // put it all together
450 const Vector LookAt = Look + CurrentUp + CurrentRight;
451
452 // ray tracing
453 clrPixel = rayTrace(Ray(camera_->from, LookAt));
454
455 // draw pixel
456 #ifndef DONT_DRAW
457 glColor3dv(clrPixel.rep());
458 glRectd(truex, height_-truey,
459 truex+pixelsize, height_-(truey+pixelsize));
460 #endif
461 }
462 }
463 }
464
465
466 void CGRayTracer::onInit() {
467 // no perspective drawing, just pure 2D display
468 glMatrixMode(GL_PROJECTION);
469 glLoadIdentity();
470 gluOrtho2D(0, imagex_-1, 0, imagey_-1);
471
472 glClearColor(clrBackground[0], clrBackground[1], clrBackground[2],1);
473 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
474
475 // start timer
476 start_ = clock();
477 }
478
479 void CGRayTracer::onSize(unsigned int newWidth,unsigned int newHeight) {
480 width_ = newWidth;
481 height_ = newHeight;
482
483 glViewport(0,0, width_-1, height_-1);
484 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
485 onDraw();
486 }
487
488 void CGRayTracer::onKey(unsigned char key) {
489 switch (key) {
490 case 27: { exit(0); break; }
491 case 'a': antialias_ = !antialias_; onDraw(); break;
492 }
493 }
494
495 void CGRayTracer::onIdle() {
496 if (detail_ > 1)
497 {
498 // next detail level
499 detail_ /= 2;
500 createImage(imagex_ / detail_, imagey_ / detail_);
501
502 if (detail_ > 1)
503 {
504 // detail level completed
505 cout << detail_ << "x" << detail_ << " level done" << endl;
506 }
507 else
508 {
509 // stop timer
510 clock_t finish = clock();
511 cout << "... image completed !" << endl;
512
513 writeImage("raytracer.ppm");
514
515 double seconds = (finish - start_)/(double)CLOCKS_PER_SEC;
516 // show some statistics
517 cout << endl
518 << imagey_ << "x" << imagex_ << " pixels rendered using " << nTests_ << " intersection tests in "
519 << seconds << " seconds" << endl
520 << "=> " << int(imagex_*imagey_/seconds) << " pixels/sec and "
521 << nTests_/double(imagex_*imagey_) << " tests/pixel (ray depth=" << maxDepth_ << ")" << endl;
522
523 }
524 }
525 }
526
527 void CGRayTracer::onDraw() {
528 // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
529
530 // "pixel" size
531 const double size = detail_;
532
533 if (!antialias_ || detail_ > 1)
534 // process all pixels
535 for (unsigned int i=0; i<imagey_; i++)
536 for (unsigned int j=0; j<imagex_; j++)
537 {
538 // draw only if already calculated
539 const Color& color = image_[i*imagex_+j];
540 if (color[0] >= 0)
541 {
542 glColor3dv(color.rep());
543 glRectd(j, imagey_-i, j+size, imagey_-(i+size));
544 }
545 }
546 else
547 // process all pixels
548 for (unsigned int i=0; i<imagey_; i++)
549 for (unsigned int j=0; j<imagex_; j++)
550 {
551 if (i==0 || j==0)
552 {
553 glColor3dv(image_[i*imagex_+j].rep());
554 glRectd(j, imagey_-i, j+size, imagey_-(i+size));
555 }
556 else
557 {
558 // weight neighbours
559 const Color color = (image_[(i-1)*imagex_+j] +
560 image_[(i-1)*imagex_+j-1] +
561 image_[(i-1)*imagex_+j+1] +
562 4*image_[ i *imagex_+j] +
563 image_[ i *imagex_+j-1] +
564 image_[ i *imagex_+j+1] +
565 image_[(i+1)*imagex_+j] +
566 image_[(i+1)*imagex_+j-1] +
567 image_[(i+1)*imagex_+j+1]) / 12;
568 glColor3dv(color.rep());
569 glRectd(j, imagey_-i, j+size, imagey_-(i+size));
570 }
571 }
572 }
573
574
575 // Hauptprogramm
576 int main(int argc, char* argv[]) {
577
578 unsigned int maxx;
579 unsigned int maxy;
580
581 cout << "Bildformat bitte eingeben !" << endl
582 << "Breite: ";
583 cin >> maxx;
584 cout << "Hoehe : ";
585 cin >> maxy;
586
587 // Erzeuge eine Instanz der Beispiel-Anwendung:
588 CGRayTracer sample(maxx, maxy);
589
590 cout << endl
591 << "Tastenbelegung:" << endl
592 << "ESC Programm beenden" << endl
593 // << "a Antialiasing ein/aus" << endl
594 << endl
595 << "Das Bild wird automatisch unter \"raytracer.ppm\" gespeichert." << endl << endl;
596
597 // Starte die Beispiel-Anwendung:
598
599 sample.start("Stephan Brumme, 702544", false, maxx, maxy);
600
601 return 0;
602 }
603