#include "Scene.h" #include #include #include #include /* pair */ #include #include /* sort() */ #include /* binary_function */ #include #include "BMP.h" #include "util/Color.h" #include "shapes/Shape.h" #include "PointLight.h" #include "Lighting.h" using namespace std; Scene::Scene(const map & options, const char * filename) { m_width = 800; m_height = 600; m_multisample_level = 1; m_output_file_name = "fart.bmp"; m_vfov = 60.0; m_verbose = false; m_data = NULL; m_ambient_light = Color(0.1, 0.1, 0.1); load(filename); /* after loading the scene file, apply any command-line render options */ for (map::const_iterator it = options.begin(); it != options.end(); it++) { if (it->first == "width") { m_width = atoi(it->second); } else if (it->first == "height") { m_height = atoi(it->second); } else if (it->first == "multisample") { m_multisample_level = atoi(it->second); } else if (it->first == "field-of-view") { m_vfov = atof(it->second); } else if (it->first == "output-file") { m_output_file_name = it->second; } else if (it->first == "verbose") { m_verbose = true; } } /* view plane distance is calculated based on the field of view */ m_view_plane_dist = (m_height / 2.0) / tan(M_PI * m_vfov / 360.0); m_sample_span = 1.0 / m_multisample_level; m_half_sample_span = m_sample_span / 2.0; m_multisample_level_squared = m_multisample_level * m_multisample_level; } Scene::~Scene() { if (m_data != NULL) delete m_data; } void Scene::load(const char * filename) { /* TODO: parse file somehow */ refptr plane = new Plane(0, 0, 1, -2); m_shapes.push_back(plane); Material * m = new Material(); m->setDiffuseColor(Color::red); m->setAmbientColor(Color::red); refptr shape = new Sphere(1.0); m_transform.translate(1.0, 5.0, 0.5); shape->setTransform(m_transform); shape->setMaterial(m); m_shapes.push_back(shape); refptr light = new PointLight(); light->setPosition(Vector(2, -1, 2)); m_lights.push_back(light); } void Scene::render() { if (m_verbose) { cout << " *** Beginning scene render ***" << endl; cout << "Parameters:" << endl; cout << "----------------------------------------" << endl; cout << " Width: " << m_width << endl; cout << " Height: " << m_height << endl; cout << " Multisample Level: " << m_multisample_level << endl; cout << " Vertical Field of View: " << m_vfov << endl; cout << "----------------------------------------" << endl; } m_data = new unsigned char[m_width * m_height * 3]; for (int i = 0; i < m_height; i++) { for (int j = 0; j < m_width; j++) { renderPixel(j, i, &m_data[3 * (m_width * i + j)]); } } if (m_verbose) { cout << " *** Ending scene render ***" << endl; cout << "Writing output file '" << m_output_file_name << '\'' << endl; } BMP outputImage(m_output_file_name.c_str(), m_width, m_height, m_data); } void Scene::renderPixel(int x, int y, unsigned char * pixel) { /* calculate the ray going from the camera through this pixel */ Color finalColor; for (int i = 0; i < m_multisample_level; i++) { for (int j = 0; j < m_multisample_level; j++) { double rx = (x + i * m_sample_span + m_half_sample_span) - (m_width / 2.0); double rz = (m_height / 2.0) - (y + j * m_sample_span + m_half_sample_span); Ray ray(Vector(0, 0, 0), Vector(rx, m_view_plane_dist, rz)); finalColor += traceRay(ray); } } /* take the average of all the samples as the final pixel value */ pixel[BMP_RED] = (unsigned char) (0xFF * finalColor.r / m_multisample_level_squared); pixel[BMP_GREEN] = (unsigned char) (0xFF * finalColor.g / m_multisample_level_squared); pixel[BMP_BLUE] = (unsigned char) (0xFF * finalColor.b / m_multisample_level_squared); } Color Scene::traceRay(const Ray & ray) { Color color; vector hits = getRayHits(ray); for (vector::const_iterator it = hits.begin(); it != hits.end(); it++) { /* compute the Phong lighting for each hit */ refptr material = it->first->getMaterial(); Vector surfacePoint = ray[it->second]; color = Lighting::computePhong(material, m_lights, ray, surfacePoint, it->first->getNormalAt(surfacePoint), m_ambient_light); } return color; } vector Scene::getRayHits(const Ray & ray) { vector hits; double minSolidDist = 0.0; /* loop through all shapes in the scene */ for (vector< refptr >::iterator it = m_shapes.begin(); it != m_shapes.end(); it++) { Shape::IntersectList intersections = (*it)->intersect(ray); for (int i = 0, num_results = intersections.size(); i < num_results; i++) { Vector normal = (*it)->getNormalAt(intersections[i]); double dot = normal % ray.getDirection(); double intersect_dist = (intersections[i] - ray.getOrigin()).mag(); if (dot < 0.0) /* cull back faces */ { double transparency = (*it)->getTransparency(); if (transparency == 0.0 && (minSolidDist == 0.0 || minSolidDist > intersect_dist)) { minSolidDist = intersect_dist; } if (minSolidDist == 0.0 || minSolidDist >= intersect_dist) { hits.push_back(ShapeDistance(*it, intersect_dist)); } } } } /* now that we have ALL the hits, sort them by distance */ sort(hits.begin(), hits.end()); double transparency = 1.0; /* now go through them all until we get to the maximum number * of hits, or until the transparency left is below the threshold */ for (unsigned int hits_index = 0, sz = hits.size(); hits_index < sz; hits_index++) { transparency *= hits[hits_index].first->getTransparency(); if (hits_index >= (SCENE_MAX_TRANSPARENT_HITS - 1) || transparency < SCENE_TRANSPARENCY_THRESHOLD) { /* delete the rest of the hits */ for (int i = 0, num_to_del = sz - hits_index - 1; i < num_to_del; i++) { hits.pop_back(); } break; } } return hits; } bool operator<(const Scene::ShapeDistance & sd1, const Scene::ShapeDistance & sd2) { return sd1.second < sd2.second; }