diff --git a/.gitignore b/.gitignore index c65ce39..bf7cd72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build/ *.ppm *.zip +.vscode/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 801846f..dfefc30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,8 @@ add_compile_options(-DSTUDENT_SOLUTION) # When testing large scenes the debug mode will be very slow # so switch to release -set(CMAKE_BUILD_TYPE Debug) -#set(CMAKE_BUILD_TYPE Release) +#set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_STANDARD 14) diff --git a/src/Geometry.cc b/src/Geometry.cc index 9a98549..0a29ab8 100644 --- a/src/Geometry.cc +++ b/src/Geometry.cc @@ -1,18 +1,42 @@ #include "Geometry.h" #include +#include + +Vector3f Geometry::diffuse() const { return cd; } +Vector3f Geometry::specular() const { return cs; } +Vector3f Geometry::ambient() const { return ca; } +float Geometry::coefDiffuse() const { return kd; } +float Geometry::coefSpecular() const { return ks; } +float Geometry::coefAmbient() const { return ka; } +float Geometry::getPhong() const { return phong; } void Geometry::setTransform(const Matrix4f &transform) { this->transform = transform; } -bool Sphere::intersect(const Ray &r) const { +Optional Sphere::intersect(const Ray &r) const { Vector3f originCenter = r.getOrigin() - center; float a = r.getDirection().dot(r.getDirection()); float b = 2.0f * originCenter.dot(r.getDirection()); float c = originCenter.dot(originCenter) - radius * radius; - return b * b - 4 * a * c >= 0; + float delta = b * b - 4 * a * c; + if (delta >= 0) { + float t1 = (-b + sqrt(delta)) / 2.0f / a; + float t2 = (-b - sqrt(delta)) / 2.0f / a; + float mint = std::min(t1, t2); + float maxt = std::max(t1, t2); + return maxt <= 0 + ? Optional::nullopt + : (mint < 0 ? Optional(maxt) : Optional(mint)); + } + + return Optional::nullopt; +} + +Vector3f Sphere::getNormal(const Vector3f &p) const { + return (p - center).normalized(); } bool isInRectangle(const Vector3f &p, const Vector3f &a, const Vector3f &b, @@ -26,18 +50,19 @@ bool isInRectangle(const Vector3f &p, const Vector3f &a, const Vector3f &b, (s1 <= 0 && s2 <= 0 && s3 <= 0 && s4 <= 0); } -bool Rectangle::intersect(const Ray &r) const { - Vector3f normal = (p2 - p1).cross(p3 - p1).normalized(); - +Optional Rectangle::intersect(const Ray &r) const { float denom = normal.dot(r.getDirection()); if (abs(denom) < 1e-6f) - return false; + return Optional::nullopt; float t = -normal.dot(r.getOrigin() - p1) / denom; if (t <= 0) - return false; + return Optional::nullopt; Vector3f p = r.getOrigin() + t * r.getDirection(); - return isInRectangle(p, p1, p2, p3, p4, normal); + return isInRectangle(p, p1, p2, p3, p4, normal) ? Optional(t) + : Optional::nullopt; } + +Vector3f Rectangle::getNormal(const Vector3f &p) const { return normal; } diff --git a/src/Geometry.h b/src/Geometry.h index d92709f..b3d1eee 100644 --- a/src/Geometry.h +++ b/src/Geometry.h @@ -1,13 +1,16 @@ #ifndef GEOMETRY_H_ #define GEOMETRY_H_ +#include "Optional.h" #include "Ray.h" #include +#include using Eigen::Matrix; using Eigen::Matrix4f; using Eigen::Vector3f; +using utils::Optional; // Abstract class for Geometries class Geometry { @@ -15,7 +18,8 @@ public: enum class Type { SPHERE, RECTANGLE }; virtual ~Geometry() = default; - virtual bool intersect(const Ray &) const = 0; + virtual Optional intersect(const Ray &) const = 0; + virtual Vector3f getNormal(const Vector3f &) const = 0; protected: Geometry(Type type, float ka, float kd, float ks, const Vector3f &ca, @@ -29,6 +33,13 @@ protected: Matrix4f transform = Matrix4f::Identity(); public: + Vector3f diffuse() const; + Vector3f specular() const; + Vector3f ambient() const; + float coefDiffuse() const; + float coefSpecular() const; + float coefAmbient() const; + float getPhong() const; void setTransform(const Matrix4f &); }; @@ -39,7 +50,8 @@ public: : Geometry(Type::SPHERE, ka, kd, ks, ca, cd, cs, pc), radius(radius), center(center) {} - bool intersect(const Ray &) const override; + Optional intersect(const Ray &) const override; + Vector3f getNormal(const Vector3f &) const override; private: float radius; @@ -52,12 +64,14 @@ public: const Vector3f &cs, float pc, const Vector3f &p1, const Vector3f &p2, const Vector3f &p3, const Vector3f &p4) : Geometry(Type::RECTANGLE, ka, kd, ks, ca, cd, cs, pc), p1(p1), p2(p2), - p3(p3), p4(p4) {} + p3(p3), p4(p4), normal((p2 - p1).cross(p3 - p1).normalized()) {} - bool intersect(const Ray &) const override; + Optional intersect(const Ray &) const override; + Vector3f getNormal(const Vector3f &) const override; private: Vector3f p1, p2, p3, p4; + Vector3f normal; }; #endif // !GEOMETRY_H_ diff --git a/src/HitRecord.cc b/src/HitRecord.cc new file mode 100644 index 0000000..6a07ee7 --- /dev/null +++ b/src/HitRecord.cc @@ -0,0 +1,19 @@ +#include "HitRecord.h" + +bool HitRecord::operator<(const HitRecord &other) const { + return this->t > other.t; // to get the nearest t +} + +Geometry *HitRecord::geometry() const { return g; } + +Vector3f HitRecord::getPoint() const { + return r.getOrigin() + t * r.getDirection(); +} + +Vector3f HitRecord::viewDirection() const { + return -r.getDirection().normalized(); +} + +Vector3f HitRecord::normal() const { return n; } + +void HitRecord::calcNormal() { n = g->getNormal(getPoint()); } diff --git a/src/HitRecord.h b/src/HitRecord.h new file mode 100644 index 0000000..c7fad17 --- /dev/null +++ b/src/HitRecord.h @@ -0,0 +1,29 @@ +#ifndef HITRECORD_H_ +#define HITRECORD_H_ + +#include "Geometry.h" +#include "Ray.h" +#include + +using Eigen::Vector3f; + +class HitRecord { +public: + HitRecord(float t, const Ray &r, Geometry *g) : t(t), r(r), g(g) {} + bool operator<(const HitRecord &) const; + +private: + float t; + Ray r; + Vector3f n; + Geometry *g; + +public: + Geometry *geometry() const; + Vector3f getPoint() const; + Vector3f viewDirection() const; + Vector3f normal() const; + void calcNormal(); +}; + +#endif // !HITRECORD_H_ diff --git a/src/Light.cc b/src/Light.cc index 07d7c27..2b0030a 100644 --- a/src/Light.cc +++ b/src/Light.cc @@ -1,4 +1,6 @@ #include "Light.h" +#include +#include void Light::setTransform(const Matrix4f &transform) { this->transform = transform; @@ -8,6 +10,55 @@ void Light::setGridSize(unsigned int gridSize) { this->gridSize = gridSize; } void Light::setUseCenter(bool useCenter) { this->useCenter = useCenter; } -void PointLight::illumination() const {} +void Light::setIsUse(bool isUse) { this->use = isUse; } -void AreaLight::illumination() const {} +Vector3f Light::getDiffuse() const { return diffuse; } + +Vector3f Light::getSpecular() const { return specular; } + +bool Light::isUse() const { return use; } + +Vector3f PointLight::illumination(const HitRecord &hit, + const vector &geometries) const { + Vector3f shadingPoint = hit.getPoint(); + Vector3f rayDirection = (center - shadingPoint).normalized(); + Geometry *geometry = hit.geometry(); + Ray shadowRay(shadingPoint, rayDirection); + + for (auto g : geometries) + if (g != geometry && g->intersect(shadowRay).hasValue()) + return Vector3f::Zero(); + + Vector3f ambient_ = geometry->coefAmbient() * geometry->ambient(); + + Vector3f diffuse_ = geometry->coefDiffuse() * geometry->diffuse().array() * + diffuse.array() * + std::max(0.0f, hit.normal().dot(rayDirection)); + + Vector3f halfWay = (hit.viewDirection() + rayDirection).normalized(); + Vector3f specular_ = + geometry->coefSpecular() * geometry->specular().array() * + specular.array() * + pow(std::max(0.0f, hit.normal().dot(halfWay)), geometry->getPhong()); + + return specular_ + ambient_ + diffuse_; +} + +Vector3f AreaLight::illumination(const HitRecord &hit, + const vector &geometries) const { + Vector3f u = p4 - p1; + Vector3f v = p2 - p1; + + Vector3f color = Vector3f::Zero(); + + if (useCenter) { + color += PointLight(*this, p1 + (u + v) / 2).illumination(hit, geometries); + } else { + for (int y = 0; y < gridSize; ++y) + for (int x = 0; x < gridSize; ++x) + color += PointLight(*this, p1 + (u * x + v * y) / gridSize) + .illumination(hit, geometries); + } + + return color / gridSize / gridSize; +} diff --git a/src/Light.h b/src/Light.h index c708467..60afbce 100644 --- a/src/Light.h +++ b/src/Light.h @@ -1,11 +1,14 @@ #ifndef LIGHT_H_ #define LIGHT_H_ +#include "HitRecord.h" #include +#include using Eigen::Matrix; using Eigen::Matrix4f; using Eigen::Vector3f; +using std::vector; // Abstract base class for Lights class Light { @@ -13,7 +16,8 @@ public: enum class Type { Point, Area }; virtual ~Light() = default; - virtual void illumination() const = 0; + virtual Vector3f illumination(const HitRecord &, + const vector &) const = 0; protected: Light(Type type, const Vector3f &id, const Vector3f &is) @@ -25,23 +29,16 @@ protected: Matrix4f transform = Matrix4f::Identity(); // optional member `transform` unsigned int gridSize = 0; // optional member `n` bool useCenter = false; // optional member `usecenter` + bool use = true; // this appears in a json file public: - // setters for optional members void setTransform(const Matrix4f &); void setGridSize(unsigned int); void setUseCenter(bool); -}; - -class PointLight : public Light { -public: - PointLight(const Vector3f &id, const Vector3f &is, Vector3f ¢er) - : Light(Type::Point, id, is), center(center) {} - - virtual void illumination() const override; - -private: - Vector3f center; + void setIsUse(bool); + Vector3f getDiffuse() const; + Vector3f getSpecular() const; + bool isUse() const; }; class AreaLight : public Light { @@ -50,10 +47,26 @@ public: const Vector3f &p2, const Vector3f &p3, const Vector3f &p4) : Light(Type::Area, id, is), p1(p1), p2(p2), p3(p3), p4(p4) {} - virtual void illumination() const override; + virtual Vector3f illumination(const HitRecord &, + const vector &) const override; private: Vector3f p1, p2, p3, p4; }; +class PointLight : public Light { +public: + PointLight(const Vector3f &id, const Vector3f &is, const Vector3f ¢er) + : Light(Type::Point, id, is), center(center) {} + + PointLight(const AreaLight &al, const Vector3f ¢er) + : PointLight(al.getDiffuse(), al.getSpecular(), center) {} + + virtual Vector3f illumination(const HitRecord &, + const vector &) const override; + +private: + Vector3f center; +}; + #endif // !LIGHT_H_ diff --git a/src/Optional.h b/src/Optional.h new file mode 100644 index 0000000..3b3a7d4 --- /dev/null +++ b/src/Optional.h @@ -0,0 +1,25 @@ +#ifndef OPTIONAL_H_ + +namespace utils { + +// I write this class because we are using C++14 +// so std::optional is not available +template class Optional { +public: + Optional() : hasValue_(false) {} + Optional(T value) : value_(value), hasValue_(true) {} + + bool hasValue() const { return hasValue_; } + T value() const { return value_; } + + static const Optional nullopt; + +private: + T value_; + bool hasValue_; +}; + +template const Optional Optional::nullopt = Optional(); +} // namespace utils + +#endif // !OPTIONAL_H_ diff --git a/src/Output.cc b/src/Output.cc new file mode 100644 index 0000000..840212b --- /dev/null +++ b/src/Output.cc @@ -0,0 +1,22 @@ +#include "Output.h" + +#include + +void Output::write() { + std::ofstream fout(path, std::ios_base::out | std::ios_base::binary); + fout << "P6\n" << width << ' ' << height << '\n' << "255" << std::endl; + + for (unsigned int y = 0; y < height; ++y) + for (unsigned int x = 0; x < width; ++x) + fout << (char)(255.0f * red[y * width + x]) + << (char)(255.0f * green[y * width + x]) + << (char)(255.0f * blue[y * width + x]); + fout.close(); +} + +float Output::r(int index) const { return red.at(index); } +float Output::g(int index) const { return green.at(index); } +float Output::b(int index) const { return blue.at(index); } +void Output::r(int index, float value) { red.at(index) = value; } +void Output::g(int index, float value) { green.at(index) = value; } +void Output::b(int index, float value) { blue.at(index) = value; } diff --git a/src/Output.h b/src/Output.h new file mode 100644 index 0000000..d7a065d --- /dev/null +++ b/src/Output.h @@ -0,0 +1,38 @@ +#ifndef OUTPUT_H_ +#define OUTPUT_H_ + +#include +#include +#include + +using Eigen::Vector3f; +using std::string; +using std::vector; + +class Output { +public: + Output(const Vector3f &bgc, string path, int w, int h) + : red(vector(w * h + 1, bgc.x())), + green(vector(w * h + 1, bgc.y())), + blue(vector(w * h + 1, bgc.z())), path(path), width(w), + height(h) {} + + void write(); + +private: + int width, height; + string path; + vector red; + vector green; + vector blue; + +public: + void r(int, float); + float r(int) const; + void g(int, float); + float g(int) const; + void b(int, float); + float b(int) const; +}; + +#endif // !OUTPUT_H_ diff --git a/src/Parser.cc b/src/Parser.cc index 4d15d76..126c6b3 100644 --- a/src/Parser.cc +++ b/src/Parser.cc @@ -109,6 +109,7 @@ Light *Parser::getLight(const nlohmann::json &j) { l->setGridSize(j.value("n", 0)); l->setUseCenter(j.value("usecenter", false)); + l->setIsUse(j.value("use", true)); return l; } diff --git a/src/RayTracer.cc b/src/RayTracer.cc index e9c93a1..c79add8 100644 --- a/src/RayTracer.cc +++ b/src/RayTracer.cc @@ -1,10 +1,15 @@ #include "RayTracer.h" #include "../external/simpleppm.h" +#include "HitRecord.h" +#include "Output.h" #include "Parser.h" #include "Ray.h" #include #include +#include + +using std::priority_queue; void RayTracer::parse() { for (auto i = json["output"].begin(); i != json["output"].end(); ++i) @@ -22,14 +27,27 @@ Ray getRay(int x, int y, const Vector3f &camPos, const Vector3f &pxUpperLeft, return Ray(camPos, pxUpperLeft + x * du + y * dv - camPos); } +void RayTracer::calculateColor(const HitRecord &hit, Output *buffer, int i) { + buffer->r(i, 0); + buffer->g(i, 0); + buffer->b(i, 0); + for (auto light : lights) + if (light->isUse()) { + Vector3f contribution = + light->illumination(hit, geometries).cwiseMax(0.0f).cwiseMin(1.0f) / + lights.size(); + buffer->r(i, buffer->r(i) + contribution.x()); + buffer->g(i, buffer->g(i) + contribution.y()); + buffer->b(i, buffer->b(i) + contribution.z()); + } +} + void RayTracer::render(Scene *scene) { int width = scene->getWidth(); int height = scene->getHeight(); - float fov = scene->getFov(); Vector3f cameraPos = scene->getCenter(); Vector3f lookAt = scene->getLookAt(); - Vector3f up = scene->getUpVector(); - float vpHeight = 2 * tan(fov / 180 * M_PI / 2) * lookAt.norm(); + float vpHeight = 2 * tan(scene->getFov() / 180 * M_PI / 2) * lookAt.norm(); float vpWidth = vpHeight * width / height; Vector3f vpU = Vector3f(vpWidth, 0, 0); Vector3f vpV = Vector3f(0, -vpHeight, 0); @@ -39,30 +57,32 @@ void RayTracer::render(Scene *scene) { Vector3f vpUpperLeft = cameraPos + lookAt - vpU / 2.0 - vpV / 2.0; Vector3f pxUpperLeft = vpUpperLeft + (du + dv) / 2.0; - Buffer buffer(width * height * 3); + Output *buffer = + new Output(scene->getBackgroundColor(), scene->getName(), width, height); + for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { Ray ray = getRay(x, y, cameraPos, pxUpperLeft, du, dv); + priority_queue records; + for (auto g : geometries) { + Optional t = g->intersect(ray); + if (t.hasValue()) + records.push(HitRecord(t.value(), ray, g)); + } - for (auto geometry : geometries) - if (geometry->intersect(ray)) { - buffer[3 * y * width + 3 * x + 0] = 1; - buffer[3 * y * width + 3 * x + 1] = 1; - buffer[3 * y * width + 3 * x + 2] = 1; - break; - } + if (!records.empty()) { + HitRecord hit = records.top(); + hit.calcNormal(); + calculateColor(hit, buffer, y * width + x); + } } - Task *task = new Task(scene, buffer); - tasks.push_back(task); + outputs.push_back(buffer); } -void RayTracer::output(Task *task) { - string path = task->first->getName(); - int width = task->first->getWidth(); - int height = task->first->getHeight(); - - save_ppm(path, task->second, width, height); +void RayTracer::output() { + for (auto output : outputs) + output->write(); } void RayTracer::run() { @@ -71,6 +91,5 @@ void RayTracer::run() { for (auto scene : scenes) render(scene); - for (auto task : tasks) - output(task); + output(); } diff --git a/src/RayTracer.h b/src/RayTracer.h index 95c917c..43a9075 100644 --- a/src/RayTracer.h +++ b/src/RayTracer.h @@ -4,14 +4,11 @@ #include "../external/json.hpp" #include "Geometry.h" #include "Light.h" +#include "Output.h" #include "Scene.h" -#include #include -using Buffer = std::vector; -using Task = std::pair; - class RayTracer { public: RayTracer(const nlohmann::json &j) : json(j) {} @@ -22,12 +19,12 @@ private: std::vector scenes; std::vector lights; std::vector geometries; - - std::vector tasks; + std::vector outputs; void parse(); + void calculateColor(const HitRecord &, Output *, int); void render(Scene *); - void output(Task *); + void output(); }; #endif // !RAY_TRACER_H_ diff --git a/src/Scene.cc b/src/Scene.cc index 8533ff3..9c48128 100644 --- a/src/Scene.cc +++ b/src/Scene.cc @@ -14,6 +14,8 @@ Vector3f Scene::getUpVector() const { return up; } Vector3f Scene::getLookAt() const { return lookAt; } +Vector3f Scene::getBackgroundColor() const { return backgroundColor; } + void Scene::setRaysPerPixel(const Eigen::VectorXi &raysPerPixel) { this->raysPerPixel = raysPerPixel; } diff --git a/src/Scene.h b/src/Scene.h index 24a697e..8ac10e0 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -39,6 +39,7 @@ public: Vector3f getCenter() const; Vector3f getUpVector() const; Vector3f getLookAt() const; + Vector3f getBackgroundColor() const; void setRaysPerPixel(const Eigen::VectorXi &); void setAntialiasing(bool); void setTwoSideRender(bool);