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