/*********************************************************** CSC418, SPRING 2005 raytracer.cpp author: Jack Wang Implementations of functions in raytracer.h, and the main function which specifies the scene to be rendered. ***********************************************************/ #include "raytracer.h" #include "bmp_io.h" #include #include Raytracer::Raytracer() : _lightSource(NULL) { _root = new SceneDagNode(); initMatrix(_modelToWorld); initMatrix(_worldToModel); } Raytracer::~Raytracer() { delete _root; } SceneDagNode* Raytracer::addObject( SceneDagNode* parent, SceneObject* obj, Material* mat ) { SceneDagNode* node = new SceneDagNode( obj, mat ); node->parent = parent; node->next = NULL; node->child = NULL; // Add the object to the parent's child list, this means // whatever transformation applied to the parent will also // be applied to the child. if (parent->child == NULL) { parent->child = node; } else { parent = parent->child; while (parent->next != NULL) { parent = parent->next; } parent->next = node; } return node;; } LightListNode* Raytracer::addLightSource( LightSource* light ) { LightListNode* tmp = _lightSource; _lightSource = new LightListNode( light, tmp ); return _lightSource; } void Raytracer::rotate( SceneDagNode* node, char axis, double angle ) { Matrix4x4 rotation; double toRadian = 2*M_PI/360.0; int i; initMatrix(rotation); for (i = 0; i < 2; i++) { switch(axis) { case 'x': rotation[0][0] = 1; rotation[1][1] = cos(angle*toRadian); rotation[1][2] = -sin(angle*toRadian); rotation[2][1] = sin(angle*toRadian); rotation[2][2] = cos(angle*toRadian); rotation[3][3] = 1; break; case 'y': rotation[0][0] = cos(angle*toRadian); rotation[0][2] = sin(angle*toRadian); rotation[1][1] = 1; rotation[2][0] = -sin(angle*toRadian); rotation[2][2] = cos(angle*toRadian); rotation[3][3] = 1; break; case 'z': rotation[0][0] = cos(angle*toRadian); rotation[0][1] = -sin(angle*toRadian); rotation[1][0] = sin(angle*toRadian); rotation[1][1] = cos(angle*toRadian); rotation[2][2] = 1; rotation[3][3] = 1; break; } if (i == 0) { mulMatrix(node->trans, node->trans, rotation); angle = -angle; } else { mulMatrix(node->invtrans, rotation, node->invtrans); } } } void Raytracer::translate( SceneDagNode* node, Vector3D trans ) { Matrix4x4 translation; initMatrix(translation); translation[0][3] = trans.x; translation[1][3] = trans.y; translation[2][3] = trans.z; mulMatrix(node->trans, node->trans, translation); translation[0][3] = -trans.x; translation[1][3] = -trans.y; translation[2][3] = -trans.z; mulMatrix(node->invtrans, translation, node->invtrans); } void Raytracer::scale( SceneDagNode* node, Point3D origin, double factor[3] ) { Matrix4x4 scale; initMatrix(scale); scale[0][0] = factor[0]; scale[0][3] = origin.x - factor[0] * origin.x; scale[1][1] = factor[1]; scale[1][3] = origin.y - factor[1] * origin.y; scale[2][2] = factor[2]; scale[2][3] = origin.z - factor[2] * origin.z; mulMatrix(node->trans, node->trans, scale); scale[0][0] = 1/factor[0]; scale[0][3] = origin.x - 1/factor[0] * origin.x; scale[1][1] = 1/factor[1]; scale[1][3] = origin.y - 1/factor[1] * origin.y; scale[2][2] = 1/factor[2]; scale[2][3] = origin.z - 1/factor[2] * origin.z; mulMatrix(node->invtrans, scale, node->invtrans); } void Raytracer::initInvViewMatrix( Matrix4x4 mat, Point3D eye, Vector3D view, Vector3D up ) { Vector3D w; view.normalize(); up = up - up.dot(view)*view; up.normalize(); w = view.cross(up); initMatrix(mat); mat[0][0] = w.x; mat[1][0] = w.y; mat[2][0] = w.z; mat[0][1] = up.x; mat[1][1] = up.y; mat[2][1] = up.z; mat[0][2] = -view.x; mat[1][2] = -view.y; mat[2][2] = -view.z; mat[0][3] = eye.x; mat[1][3] = eye.y; mat[2][3] = eye.z; } void Raytracer::traverseScene( SceneDagNode* node, Ray3D& ray ) { SceneDagNode *childPtr; // Applies transformation of the current node to the global // transformation matrices. mulMatrix(_modelToWorld, _modelToWorld, node->trans); mulMatrix(_worldToModel, node->invtrans, _worldToModel); if (node->obj) { // Perform intersection. if (node->obj->intersect(ray, _worldToModel, _modelToWorld)) { ray.intersection.mat = node->mat; } } // Traverse the children. childPtr = node->child; while (childPtr != NULL) { traverseScene(childPtr, ray); childPtr = childPtr->next; } // Removes transformation of the current node from the global // transformation matrices. mulMatrix(_worldToModel, node->trans, _worldToModel); mulMatrix(_modelToWorld, _modelToWorld, node->invtrans); } void Raytracer::computeShading( Ray3D& ray ) { LightListNode* curLight = _lightSource; for (;;) { if (curLight == NULL) break; // Each lightSource provides its own shading function. curLight->light->shade(ray); curLight = curLight->next; } } void Raytracer::initPixelBuffer() { int numbytes = _scrWidth * _scrHeight * sizeof(unsigned char); _rbuffer = new unsigned char[numbytes]; _gbuffer = new unsigned char[numbytes]; _bbuffer = new unsigned char[numbytes]; for (int i = 0; i < _scrHeight; i++) { for (int j = 0; j < _scrWidth; j++) { _rbuffer[i*_scrWidth+j] = 0; _gbuffer[i*_scrWidth+j] = 0; _bbuffer[i*_scrWidth+j] = 0; } } } void Raytracer::flushPixelBuffer( char *file_name ) { bmp_write( file_name, _scrWidth, _scrHeight, _rbuffer, _gbuffer, _bbuffer ); delete _rbuffer; delete _gbuffer; delete _bbuffer; } void Raytracer::render( int width, int height, Point3D eye, Vector3D view, Vector3D up, double fov, char* fileName ) { Matrix4x4 viewToWorld; _scrWidth = width; _scrHeight = height; double factor = (double(height)/2)/tan(fov*M_PI/360.0); initPixelBuffer(); initInvViewMatrix(viewToWorld, eye, view, up); // Construct a ray for each pixel. for (int i = 0; i < _scrHeight; i++) { for (int j = 0; j < _scrWidth; j++) { // Sets up ray origin and direction in view space, // image plane is at z = -1. Point3D origin(0, 0, 0); Point3D imagePlane; imagePlane.x = (-double(width)/2 + 0.5 + j)/factor; imagePlane.y = (-double(height)/2 + 0.5 + i)/factor; imagePlane.z = -1; // TODO: Construct rays in and intersect them with // scene objects in world space. // Initialize ray with the proper origin and direction. Ray3D ray; // Don't bother shading if the ray didn't hit // anything. if (!ray.intersection.none) { computeShading(ray); _rbuffer[i*width+j] = int(ray.col.r*255); _gbuffer[i*width+j] = int(ray.col.g*255); _bbuffer[i*width+j] = int(ray.col.b*255); } } } flushPixelBuffer(fileName); } int main(int argc, char* argv[]) { // Build your scene and setup your camera here, by calling // functions from Raytracer. The code here sets up an example // scene and renders it from two different view points, DO NOT // change this if you're just implementing part one of the // assignment. Raytracer raytracer; // Camera parameters. Point3D eye(0, 0, 1); Vector3D view(0, 0, -1); Vector3D up(0, 1, 0); double fov = 60; // Defines a material for shading. Material mat( Colour(0.7, 0, 0), Colour(0.5, 0.7, 0.5), 25.0 ); // Defines a point light source. raytracer.addLightSource( new PointLight(Point3D(0, 0, 1), Colour(0.9, 0.9, 0.9) ) ); // Add a unit square into the scene with material mat. SceneDagNode* plane = raytracer.addObject( new UnitSquare(), &mat ); // Apply some transformations to the unit square. double factor[3] = { 1.0, 2.0, 1.0 }; raytracer.translate(plane, Vector3D(0, 0, -1)); raytracer.rotate(plane, 'z', 90); raytracer.scale(plane, Point3D(0, 0, 0), factor); // Render the scene, feel free to make the image smaller for // testing purposes. raytracer.render(640, 480, eye, view, up, fov, "view1.bmp"); // Render it from a different point of view. Point3D eye2(1, 0, 1); Vector3D view2(-1, 0, -2); raytracer.render(640, 480, eye2, view2, up, fov, "view2.bmp"); return 0; }