#include "cgraytracer.h" #include #include // watermark #pragma comment(exestr, "(C)2001-2002 by Stephan Brumme") // my parent's GPU driver is much too slow ... definitely !!! //#define DONT_DRAW static double PI = 3.1415926; static double INTERSECTION_OFFSET = 0.0001; static Color clrBackground(0.05, 0.05, 0.1); inline double frand() { return double(rand()) / (RAND_MAX); } // // CGRayTracer Application // CGRayTracer::CGRayTracer(unsigned int x, unsigned int y) { // general settings // use shadows ? shadows_ = true; // recursion depth maxDepth_ = 7; // no. of intersection tests nTests_ = 0; // initial detail level (no. of pixels per ray) detail_ = 1<<8; antialias_ = false; // allocate memory to store the image imagex_ = x; imagey_ = y; image_ = new Color[(imagex_+1)*(imagey_+1)]; for (unsigned int i=0; iy) ? y : x; } Color CGRayTracer::phongIlluminationColor(const Vector& point, const Vector& normal, Material* material) { // calculate Phong illumination // code is a slightly modified copy of my Phong homework // define intensity variables double dIntensityAmbient = 0; double dIntensityDiffuse = 0; double dIntensitySpecular = 0; // N = normal const Vector N_norm = normal.normalized(); // V = view vector const Vector V = camera_->from - point; const Vector V_norm = V.normalized(); // process all light sources for (int nLight=0; nLight < lightSize_; nLight++) { // get a single light source const Light* const light = light_[nLight]; // ALWAYS ambient intensity, even if shadowed dIntensityAmbient += light->ambient; // L = light vector const Vector L = light->pos - point; const Vector L_norm = L.normalized(); // N*L const double NdotL = dotProduct(N_norm, L_norm); // intensity reaching the surface (attenuation) const double dLightDistance = abs(L); double dMaxIntensity = 1.0 / (light->att_constant + dLightDistance*light->att_linear + dLightDistance*dLightDistance*light->att_quadric); if (dMaxIntensity > 1) dMaxIntensity = 1; // get shadow if (shadows_) { // shadow ray must not start at the object's surface ! // (else we get in trouble with intersection problems) const Ray shadowray(point+(light->pos - point)*INTERSECTION_OFFSET, light->pos - point); bool shadow = false; Shape** ppShape = shape_; for (int shape=0; shapeintersect(shadowray, _point, _normal, distance); nTests_++; // intersection between light source and surface point ? if (shadow && distance < dLightDistance) { dMaxIntensity *= 1-material_[shape]->opacity; if (dMaxIntensity > 0.01) shadow = false; else // 100% shadow break; } } // shadowed ! no diffuse or specular light possible if (shadow) continue; } // R*V = (2*N*(N*L)-L)*V const double RdotV = max(dotProduct((2*NdotL*N_norm - L_norm).normalized(), V_norm), 0); // diffuse intensity dIntensityDiffuse += dMaxIntensity * material->kd * NdotL; // specular intensity dIntensitySpecular += dMaxIntensity * material->ks * pow(RdotV, material->n); } // return clamped color return material->color * min(dIntensityAmbient+dIntensityDiffuse+dIntensitySpecular,1); } Color CGRayTracer::rayTrace(const Ray& r, int nDepth) { // Check each shape for intersektion. // Return phong color of nearest shape // that is hit. if (nDepth++ > maxDepth_) return clrBackground; // shape that we found int closestShape = -1; // its parameters double closestDistance = 99999999; Vector closestPoint; Vector closestNormal; Shape** ppShape = shape_; for (int shape=0; shapeintersect(r, point, normal, distance)) // closer than any intersection we tested before ? if (distance < closestDistance) { closestDistance = distance; closestShape = shape; closestPoint = point; closestNormal = normal; } } // no intersection found if (closestShape < 0) return clrBackground; Material& material = *(material_[closestShape]); // object itself Color color = phongIlluminationColor(closestPoint, closestNormal, &material); // reflection if (material.ks >= 0.001) { // get reflected ray double reflected_angle = -dotProduct(closestNormal, r.getDirection()); if (reflected_angle < 0) { closestNormal *= -1; reflected_angle *= -1; } const Vector reflected_direction = r.getDirection() + 2*reflected_angle*closestNormal; // reflected ray must not start at the object's surface ! // (else we get in trouble with intersection problems) const Ray reflected_ray(closestPoint+closestNormal*INTERSECTION_OFFSET, reflected_direction, material.lightspeed); // get color const Color reflected_color = rayTrace(reflected_ray, nDepth) * material.ks * material.opacity; // add reflective part color += reflected_color; } // refraction if (material.opacity < 1) { double lightspeedratio = r.getLightspeed() / material.lightspeed; double refracted_angle = -dotProduct(closestNormal, r.getDirection()); if (refracted_angle < 0) { closestNormal *= -1; lightspeedratio = 1/lightspeedratio; } // compute refraction vector (its direction) const Vector L = -r.getDirection(); const double NdotL = dotProduct(closestNormal, L); // Snell's Law (splitted up for better readibility) const double Root = 1 - lightspeedratio*lightspeedratio*(1 - NdotL*NdotL); const double Bracket = lightspeedratio*NdotL - sqrt(Root); const Vector T = Bracket*closestNormal - lightspeedratio*L; // slight shift of new ray origin Vector T_origin = closestPoint; if (NdotL < 0) T_origin += closestNormal*10*INTERSECTION_OFFSET; else T_origin -= closestNormal*10*INTERSECTION_OFFSET; const Ray refracted_ray(T_origin, T, material.lightspeed); //r.getDirection(), material.lightspeed); // get color const Color refracted_color = rayTrace(refracted_ray, nDepth) * (1-material.opacity); // add reflective part color *= material.opacity; color += refracted_color; } // clamp color color[0] = min(color[0], 1); color[1] = min(color[1], 1); color[2] = min(color[2], 1); // color[0] = max(color[0], 0); // color[1] = max(color[1], 0); // color[2] = max(color[2], 0); return color; } void CGRayTracer::writePPMPixel(const Color& c) { // write pixel to file (*out_) << ((unsigned char)(c[0]*255)) << ((unsigned char)(c[1]*255)) << ((unsigned char)(c[2]*255)); } void CGRayTracer::writeImage(char* fileName) { cout << "saving " << fileName << " - "; // create output stream out_ = new ofstream(fileName); // write PPM header #ifdef _MSC_VER (*out_) << binary; #endif (*out_) << "P6" << endl << width_ << " " << height_ << endl << 255 << endl; // write all pixel for(unsigned int i=0; ifovy * PI/180; // look vector const Vector Look = camera_->to - camera_->from; // delta angles const double up_angle_delta = fovy_rad / height; const double right_angle_delta = fovy_rad / height;//width; // normalized vectors that describe the view plane (right handed !) const Vector Right = camera_->up.normalized() * Look.normalized(); const Vector Up = Right * Look.normalized(); // image const double widthoffset = imagex_/(double)width; const double heightoffset = imagey_/(double)height; const double pixelsize = detail_; for(int i=0; i= 0) { // draw already calculated pixels #ifndef DONT_DRAW glColor3dv(clrPixel.rep()); glRectd(truex, height_-truey, truex+pixelsize, height_-(truey+pixelsize)); #endif continue; } // angles const double up_angle = (i-height/2.0) * up_angle_delta; const double right_angle = (j-width /2.0) * right_angle_delta; // correspending vectors const Vector CurrentUp = Up * abs(Look) * tan(up_angle); const Vector CurrentRight = Right * abs(Look) * tan(right_angle); // put it all together const Vector LookAt = Look + CurrentUp + CurrentRight; // ray tracing clrPixel = rayTrace(Ray(camera_->from, LookAt)); // draw pixel #ifndef DONT_DRAW glColor3dv(clrPixel.rep()); glRectd(truex, height_-truey, truex+pixelsize, height_-(truey+pixelsize)); #endif } } } void CGRayTracer::onInit() { // no perspective drawing, just pure 2D display glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, imagex_-1, 0, imagey_-1); glClearColor(clrBackground[0], clrBackground[1], clrBackground[2],1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // start timer start_ = clock(); } void CGRayTracer::onSize(unsigned int newWidth,unsigned int newHeight) { width_ = newWidth; height_ = newHeight; glViewport(0,0, width_-1, height_-1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); onDraw(); } void CGRayTracer::onKey(unsigned char key) { switch (key) { case 27: { exit(0); break; } case 'a': antialias_ = !antialias_; onDraw(); break; } } void CGRayTracer::onIdle() { if (detail_ > 1) { // next detail level detail_ /= 2; createImage(imagex_ / detail_, imagey_ / detail_); if (detail_ > 1) { // detail level completed cout << detail_ << "x" << detail_ << " level done" << endl; } else { // stop timer clock_t finish = clock(); cout << "... image completed !" << endl; writeImage("raytracer.ppm"); double seconds = (finish - start_)/(double)CLOCKS_PER_SEC; // show some statistics cout << endl << imagey_ << "x" << imagex_ << " pixels rendered using " << nTests_ << " intersection tests in " << seconds << " seconds" << endl << "=> " << int(imagex_*imagey_/seconds) << " pixels/sec and " << nTests_/double(imagex_*imagey_) << " tests/pixel (ray depth=" << maxDepth_ << ")" << endl; } } } void CGRayTracer::onDraw() { // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // "pixel" size const double size = detail_; if (!antialias_ || detail_ > 1) // process all pixels for (unsigned int i=0; i= 0) { glColor3dv(color.rep()); glRectd(j, imagey_-i, j+size, imagey_-(i+size)); } } else // process all pixels for (unsigned int i=0; i> maxx; cout << "Hoehe : "; cin >> maxy; // Erzeuge eine Instanz der Beispiel-Anwendung: CGRayTracer sample(maxx, maxy); cout << endl << "Tastenbelegung:" << endl << "ESC Programm beenden" << endl // << "a Antialiasing ein/aus" << endl << endl << "Das Bild wird automatisch unter \"raytracer.ppm\" gespeichert." << endl << endl; // Starte die Beispiel-Anwendung: sample.start("Stephan Brumme, 702544", false, maxx, maxy); return 0; }