Merge pull request #11 from vonhyou/direct-illum

Direct illumination
This commit is contained in:
Shuo Feng 2024-03-04 02:49:05 -05:00 committed by GitHub
commit 6ccab91d73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 316 additions and 58 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
build/ build/
*.ppm *.ppm
*.zip *.zip
.vscode/
.cache/

View file

@ -21,8 +21,8 @@ add_compile_options(-DSTUDENT_SOLUTION)
# When testing large scenes the debug mode will be very slow # When testing large scenes the debug mode will be very slow
# so switch to release # so switch to release
set(CMAKE_BUILD_TYPE Debug) #set(CMAKE_BUILD_TYPE Debug)
#set(CMAKE_BUILD_TYPE Release) set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)

View file

@ -1,18 +1,42 @@
#include "Geometry.h" #include "Geometry.h"
#include <Eigen/Dense> #include <Eigen/Dense>
#include <cmath>
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) { void Geometry::setTransform(const Matrix4f &transform) {
this->transform = transform; this->transform = transform;
} }
bool Sphere::intersect(const Ray &r) const { Optional<float> Sphere::intersect(const Ray &r) const {
Vector3f originCenter = r.getOrigin() - center; Vector3f originCenter = r.getOrigin() - center;
float a = r.getDirection().dot(r.getDirection()); float a = r.getDirection().dot(r.getDirection());
float b = 2.0f * originCenter.dot(r.getDirection()); float b = 2.0f * originCenter.dot(r.getDirection());
float c = originCenter.dot(originCenter) - radius * radius; 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<float>::nullopt
: (mint < 0 ? Optional<float>(maxt) : Optional<float>(mint));
}
return Optional<float>::nullopt;
}
Vector3f Sphere::getNormal(const Vector3f &p) const {
return (p - center).normalized();
} }
bool isInRectangle(const Vector3f &p, const Vector3f &a, const Vector3f &b, 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); (s1 <= 0 && s2 <= 0 && s3 <= 0 && s4 <= 0);
} }
bool Rectangle::intersect(const Ray &r) const { Optional<float> Rectangle::intersect(const Ray &r) const {
Vector3f normal = (p2 - p1).cross(p3 - p1).normalized();
float denom = normal.dot(r.getDirection()); float denom = normal.dot(r.getDirection());
if (abs(denom) < 1e-6f) if (abs(denom) < 1e-6f)
return false; return Optional<float>::nullopt;
float t = -normal.dot(r.getOrigin() - p1) / denom; float t = -normal.dot(r.getOrigin() - p1) / denom;
if (t <= 0) if (t <= 0)
return false; return Optional<float>::nullopt;
Vector3f p = r.getOrigin() + t * r.getDirection(); Vector3f p = r.getOrigin() + t * r.getDirection();
return isInRectangle(p, p1, p2, p3, p4, normal); return isInRectangle(p, p1, p2, p3, p4, normal) ? Optional<float>(t)
: Optional<float>::nullopt;
} }
Vector3f Rectangle::getNormal(const Vector3f &p) const { return normal; }

View file

@ -1,13 +1,16 @@
#ifndef GEOMETRY_H_ #ifndef GEOMETRY_H_
#define GEOMETRY_H_ #define GEOMETRY_H_
#include "Optional.h"
#include "Ray.h" #include "Ray.h"
#include <Eigen/Core> #include <Eigen/Core>
#include <Eigen/Dense>
using Eigen::Matrix; using Eigen::Matrix;
using Eigen::Matrix4f; using Eigen::Matrix4f;
using Eigen::Vector3f; using Eigen::Vector3f;
using utils::Optional;
// Abstract class for Geometries // Abstract class for Geometries
class Geometry { class Geometry {
@ -15,7 +18,8 @@ public:
enum class Type { SPHERE, RECTANGLE }; enum class Type { SPHERE, RECTANGLE };
virtual ~Geometry() = default; virtual ~Geometry() = default;
virtual bool intersect(const Ray &) const = 0; virtual Optional<float> intersect(const Ray &) const = 0;
virtual Vector3f getNormal(const Vector3f &) const = 0;
protected: protected:
Geometry(Type type, float ka, float kd, float ks, const Vector3f &ca, Geometry(Type type, float ka, float kd, float ks, const Vector3f &ca,
@ -29,6 +33,13 @@ protected:
Matrix4f transform = Matrix4f::Identity(); Matrix4f transform = Matrix4f::Identity();
public: 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 &); void setTransform(const Matrix4f &);
}; };
@ -39,7 +50,8 @@ public:
: Geometry(Type::SPHERE, ka, kd, ks, ca, cd, cs, pc), radius(radius), : Geometry(Type::SPHERE, ka, kd, ks, ca, cd, cs, pc), radius(radius),
center(center) {} center(center) {}
bool intersect(const Ray &) const override; Optional<float> intersect(const Ray &) const override;
Vector3f getNormal(const Vector3f &) const override;
private: private:
float radius; float radius;
@ -52,12 +64,14 @@ public:
const Vector3f &cs, float pc, const Vector3f &p1, const Vector3f &cs, float pc, const Vector3f &p1,
const Vector3f &p2, const Vector3f &p3, const Vector3f &p4) const Vector3f &p2, const Vector3f &p3, const Vector3f &p4)
: Geometry(Type::RECTANGLE, ka, kd, ks, ca, cd, cs, pc), p1(p1), p2(p2), : 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<float> intersect(const Ray &) const override;
Vector3f getNormal(const Vector3f &) const override;
private: private:
Vector3f p1, p2, p3, p4; Vector3f p1, p2, p3, p4;
Vector3f normal;
}; };
#endif // !GEOMETRY_H_ #endif // !GEOMETRY_H_

19
src/HitRecord.cc Normal file
View file

@ -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()); }

29
src/HitRecord.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef HITRECORD_H_
#define HITRECORD_H_
#include "Geometry.h"
#include "Ray.h"
#include <Eigen/Core>
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_

View file

@ -1,4 +1,6 @@
#include "Light.h" #include "Light.h"
#include <algorithm>
#include <cmath>
void Light::setTransform(const Matrix4f &transform) { void Light::setTransform(const Matrix4f &transform) {
this->transform = 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 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<Geometry *> &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<Geometry *> &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;
}

View file

@ -1,11 +1,14 @@
#ifndef LIGHT_H_ #ifndef LIGHT_H_
#define LIGHT_H_ #define LIGHT_H_
#include "HitRecord.h"
#include <Eigen/Core> #include <Eigen/Core>
#include <vector>
using Eigen::Matrix; using Eigen::Matrix;
using Eigen::Matrix4f; using Eigen::Matrix4f;
using Eigen::Vector3f; using Eigen::Vector3f;
using std::vector;
// Abstract base class for Lights // Abstract base class for Lights
class Light { class Light {
@ -13,7 +16,8 @@ public:
enum class Type { Point, Area }; enum class Type { Point, Area };
virtual ~Light() = default; virtual ~Light() = default;
virtual void illumination() const = 0; virtual Vector3f illumination(const HitRecord &,
const vector<Geometry *> &) const = 0;
protected: protected:
Light(Type type, const Vector3f &id, const Vector3f &is) Light(Type type, const Vector3f &id, const Vector3f &is)
@ -25,23 +29,16 @@ protected:
Matrix4f transform = Matrix4f::Identity(); // optional member `transform` Matrix4f transform = Matrix4f::Identity(); // optional member `transform`
unsigned int gridSize = 0; // optional member `n` unsigned int gridSize = 0; // optional member `n`
bool useCenter = false; // optional member `usecenter` bool useCenter = false; // optional member `usecenter`
bool use = true; // this appears in a json file
public: public:
// setters for optional members
void setTransform(const Matrix4f &); void setTransform(const Matrix4f &);
void setGridSize(unsigned int); void setGridSize(unsigned int);
void setUseCenter(bool); void setUseCenter(bool);
}; void setIsUse(bool);
Vector3f getDiffuse() const;
class PointLight : public Light { Vector3f getSpecular() const;
public: bool isUse() const;
PointLight(const Vector3f &id, const Vector3f &is, Vector3f &center)
: Light(Type::Point, id, is), center(center) {}
virtual void illumination() const override;
private:
Vector3f center;
}; };
class AreaLight : public Light { class AreaLight : public Light {
@ -50,10 +47,26 @@ public:
const Vector3f &p2, const Vector3f &p3, const Vector3f &p4) const Vector3f &p2, const Vector3f &p3, const Vector3f &p4)
: Light(Type::Area, id, is), p1(p1), p2(p2), p3(p3), p4(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<Geometry *> &) const override;
private: private:
Vector3f p1, p2, p3, p4; Vector3f p1, p2, p3, p4;
}; };
class PointLight : public Light {
public:
PointLight(const Vector3f &id, const Vector3f &is, const Vector3f &center)
: Light(Type::Point, id, is), center(center) {}
PointLight(const AreaLight &al, const Vector3f &center)
: PointLight(al.getDiffuse(), al.getSpecular(), center) {}
virtual Vector3f illumination(const HitRecord &,
const vector<Geometry *> &) const override;
private:
Vector3f center;
};
#endif // !LIGHT_H_ #endif // !LIGHT_H_

25
src/Optional.h Normal file
View file

@ -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 <typename T> 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 <typename T> const Optional<T> Optional<T>::nullopt = Optional<T>();
} // namespace utils
#endif // !OPTIONAL_H_

22
src/Output.cc Normal file
View file

@ -0,0 +1,22 @@
#include "Output.h"
#include <fstream>
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; }

38
src/Output.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef OUTPUT_H_
#define OUTPUT_H_
#include <Eigen/Core>
#include <string>
#include <vector>
using Eigen::Vector3f;
using std::string;
using std::vector;
class Output {
public:
Output(const Vector3f &bgc, string path, int w, int h)
: red(vector<float>(w * h + 1, bgc.x())),
green(vector<float>(w * h + 1, bgc.y())),
blue(vector<float>(w * h + 1, bgc.z())), path(path), width(w),
height(h) {}
void write();
private:
int width, height;
string path;
vector<float> red;
vector<float> green;
vector<float> 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_

View file

@ -109,6 +109,7 @@ Light *Parser::getLight(const nlohmann::json &j) {
l->setGridSize(j.value("n", 0)); l->setGridSize(j.value("n", 0));
l->setUseCenter(j.value("usecenter", false)); l->setUseCenter(j.value("usecenter", false));
l->setIsUse(j.value("use", true));
return l; return l;
} }

View file

@ -1,10 +1,15 @@
#include "RayTracer.h" #include "RayTracer.h"
#include "../external/simpleppm.h" #include "../external/simpleppm.h"
#include "HitRecord.h"
#include "Output.h"
#include "Parser.h" #include "Parser.h"
#include "Ray.h" #include "Ray.h"
#include <Eigen/Core> #include <Eigen/Core>
#include <cmath> #include <cmath>
#include <queue>
using std::priority_queue;
void RayTracer::parse() { void RayTracer::parse() {
for (auto i = json["output"].begin(); i != json["output"].end(); ++i) 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); 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) { void RayTracer::render(Scene *scene) {
int width = scene->getWidth(); int width = scene->getWidth();
int height = scene->getHeight(); int height = scene->getHeight();
float fov = scene->getFov();
Vector3f cameraPos = scene->getCenter(); Vector3f cameraPos = scene->getCenter();
Vector3f lookAt = scene->getLookAt(); Vector3f lookAt = scene->getLookAt();
Vector3f up = scene->getUpVector(); float vpHeight = 2 * tan(scene->getFov() / 180 * M_PI / 2) * lookAt.norm();
float vpHeight = 2 * tan(fov / 180 * M_PI / 2) * lookAt.norm();
float vpWidth = vpHeight * width / height; float vpWidth = vpHeight * width / height;
Vector3f vpU = Vector3f(vpWidth, 0, 0); Vector3f vpU = Vector3f(vpWidth, 0, 0);
Vector3f vpV = Vector3f(0, -vpHeight, 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 vpUpperLeft = cameraPos + lookAt - vpU / 2.0 - vpV / 2.0;
Vector3f pxUpperLeft = vpUpperLeft + (du + dv) / 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 y = 0; y < height; ++y)
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
Ray ray = getRay(x, y, cameraPos, pxUpperLeft, du, dv); Ray ray = getRay(x, y, cameraPos, pxUpperLeft, du, dv);
priority_queue<HitRecord> records;
for (auto g : geometries) {
Optional<float> t = g->intersect(ray);
if (t.hasValue())
records.push(HitRecord(t.value(), ray, g));
}
for (auto geometry : geometries) if (!records.empty()) {
if (geometry->intersect(ray)) { HitRecord hit = records.top();
buffer[3 * y * width + 3 * x + 0] = 1; hit.calcNormal();
buffer[3 * y * width + 3 * x + 1] = 1; calculateColor(hit, buffer, y * width + x);
buffer[3 * y * width + 3 * x + 2] = 1; }
break;
}
} }
Task *task = new Task(scene, buffer); outputs.push_back(buffer);
tasks.push_back(task);
} }
void RayTracer::output(Task *task) { void RayTracer::output() {
string path = task->first->getName(); for (auto output : outputs)
int width = task->first->getWidth(); output->write();
int height = task->first->getHeight();
save_ppm(path, task->second, width, height);
} }
void RayTracer::run() { void RayTracer::run() {
@ -71,6 +91,5 @@ void RayTracer::run() {
for (auto scene : scenes) for (auto scene : scenes)
render(scene); render(scene);
for (auto task : tasks) output();
output(task);
} }

View file

@ -4,14 +4,11 @@
#include "../external/json.hpp" #include "../external/json.hpp"
#include "Geometry.h" #include "Geometry.h"
#include "Light.h" #include "Light.h"
#include "Output.h"
#include "Scene.h" #include "Scene.h"
#include <utility>
#include <vector> #include <vector>
using Buffer = std::vector<double>;
using Task = std::pair<Scene *, Buffer>;
class RayTracer { class RayTracer {
public: public:
RayTracer(const nlohmann::json &j) : json(j) {} RayTracer(const nlohmann::json &j) : json(j) {}
@ -22,12 +19,12 @@ private:
std::vector<Scene *> scenes; std::vector<Scene *> scenes;
std::vector<Light *> lights; std::vector<Light *> lights;
std::vector<Geometry *> geometries; std::vector<Geometry *> geometries;
std::vector<Output *> outputs;
std::vector<Task *> tasks;
void parse(); void parse();
void calculateColor(const HitRecord &, Output *, int);
void render(Scene *); void render(Scene *);
void output(Task *); void output();
}; };
#endif // !RAY_TRACER_H_ #endif // !RAY_TRACER_H_

View file

@ -14,6 +14,8 @@ Vector3f Scene::getUpVector() const { return up; }
Vector3f Scene::getLookAt() const { return lookAt; } Vector3f Scene::getLookAt() const { return lookAt; }
Vector3f Scene::getBackgroundColor() const { return backgroundColor; }
void Scene::setRaysPerPixel(const Eigen::VectorXi &raysPerPixel) { void Scene::setRaysPerPixel(const Eigen::VectorXi &raysPerPixel) {
this->raysPerPixel = raysPerPixel; this->raysPerPixel = raysPerPixel;
} }

View file

@ -39,6 +39,7 @@ public:
Vector3f getCenter() const; Vector3f getCenter() const;
Vector3f getUpVector() const; Vector3f getUpVector() const;
Vector3f getLookAt() const; Vector3f getLookAt() const;
Vector3f getBackgroundColor() const;
void setRaysPerPixel(const Eigen::VectorXi &); void setRaysPerPixel(const Eigen::VectorXi &);
void setAntialiasing(bool); void setAntialiasing(bool);
void setTwoSideRender(bool); void setTwoSideRender(bool);