#include "Scene.h" #include #include #include #include /* pair */ #include #include /* sort() */ #include /* binary_function */ #include /* typeid operator support */ #include #include "BMP.h" #include "util/Color.h" #include "shapes/Shape.h" #include "Light.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 = true; m_data = NULL; m_ambient_light = Color(0.1, 0.1, 0.1); m_max_depth = 10; m_transforms.push(Transform()); 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::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) { return traceRayRecurse(ray, m_max_depth, 1.0); } /** * factor: the proportion of the final color that this computation is worth */ Color Scene::traceRayRecurse(const Ray & ray, int depth, double factor) { Color color; ShapeDistance hit = getRayClosestHit(ray); if ( ! hit.shape.isNull() ) { /* compute the Phong lighting for each hit */ refptr material = hit.shape->getMaterial(); Vector surfacePoint = ray[hit.dist]; Vector surfaceNormal = hit.shape->getNormalAt(surfacePoint); color = computePhong(material, ray, surfacePoint, surfaceNormal); if (depth > 0 && factor > SCENE_FACTOR_THRESHOLD) { double reflectance = material->getReflectance(); if (factor * reflectance > SCENE_FACTOR_THRESHOLD) { color *= (1.0 - reflectance); Vector reflected_direction = (-ray.getDirection()).reflect(surfaceNormal); Ray newRay(surfacePoint, reflected_direction); Vector jitter_surface_point = newRay[0.0001]; Ray jitterNewRay(jitter_surface_point, reflected_direction); Color c = traceRayRecurse(jitterNewRay, depth - 1, factor * reflectance); color += c * reflectance; } double transparency = material->getTransparency(); if (factor * transparency > SCENE_FACTOR_THRESHOLD) { color *= (1.0 - transparency); Vector jitter_surface_point = ray[hit.dist + 0.0001]; Ray newRay(jitter_surface_point, ray.getDirection()); Color c = traceRayRecurse(newRay, depth - 1, factor * transparency); color += c * transparency; } } } return color; } Scene::ShapeDistance Scene::getRayClosestHit(const Ray & ray) { ShapeDistance hit; bool foundOne = false; /* loop through all shapes in the scene */ for (vector< refptr >::iterator it = m_shapes.begin(); it != m_shapes.end(); it++) { Shape::IntersectionList intersections = (*it)->intersect(*it, ray); for (int i = 0, num_results = intersections.size(); i < num_results; i++) { refptr shape = intersections[i].shape; const Vector & isect_point = intersections[i].vector; Vector normal = shape->getNormalAt(isect_point); double intersect_dist = (isect_point - ray.getOrigin()).mag(); if (foundOne == false || intersect_dist < hit.dist) { hit.shape = shape; hit.dist = intersect_dist; foundOne = true; } } } return hit; } Color Scene::computePhong(const refptr material, const Ray & viewRay, const Vector & surfacePoint, const Vector & surfaceNormal) { Color result = m_ambient_light * material->getAmbientColor(); Vector viewDirection = -viewRay.getDirection(); double shininess = material->getShininess(); const Color & diffuseColor = material->getDiffuseColor(); const Color & specularColor = material->getSpecularColor(); for (std::vector< refptr >::const_iterator it = m_lights.begin(); it != m_lights.end(); it++) { Vector directionToLight = (*it)->getPosition() - surfacePoint; directionToLight.normalize(); Vector reflectedLightDirection = directionToLight.reflect(surfaceNormal); Ray surfaceToLight(surfacePoint, directionToLight); double light_contribution = calculateLightContribution(surfaceToLight.shift(0.0001), *it); if (light_contribution > 0.0) { /* calculate the diffuse term */ double diffuse_coef = directionToLight % surfaceNormal; if (diffuse_coef > 0.0) { result += diffuseColor * (*it)->getDiffuseColor() * diffuse_coef * light_contribution; } /* calculate the specular term */ double specular_coef = reflectedLightDirection % viewDirection; if (specular_coef > 0.0) { result += specularColor * (*it)->getSpecularColor() * pow(specular_coef, shininess) * light_contribution; } } } /* TODO: figure out better scaling */ if (result.r > 1.0) result.r = 1.0; if (result.g > 1.0) result.g = 1.0; if (result.b > 1.0) result.b = 1.0; return result; } double Scene::calculateLightContribution(const Ray & toLight, refptr light) { double contrib = 1.0; double dist_to_light = toLight.getOrigin().dist_to(light->getPosition()); double dist_so_far = 0.0; Ray currentRay = toLight; for (;;) { ShapeDistance hit = getRayClosestHit(currentRay); if ( hit.shape.isNull() ) break; if ( dist_so_far + hit.dist > dist_to_light ) break; contrib *= hit.shape->getMaterial()->getTransparency(); if ( contrib < SCENE_FACTOR_THRESHOLD ) break; dist_so_far += hit.dist + 0.0001; } return contrib; } bool operator<(const Scene::ShapeDistance & sd1, const Scene::ShapeDistance & sd2) { return sd1.dist < sd2.dist; }