Commit ae2b9dc5 authored by TheNumbat's avatar TheNumbat
Browse files

Release new version

Features:
    - Particle systems can now specify a maximum dt per step
    - Animation key-framing & timing system now supports objects with simulation
    - Mixture/multiple importance sampling for correct low-variance direct lighting
        - New BSDF, point light, and environment light APIs that separate sampling, evaluation, and pdf
        - Area light sampling infrastructure
        - Removed rectangle area lights; all area lights are now emissive meshes
        - Reworked PathTracer tasks 4-6, adjusted/improved instructions for the other tasks

Bug fixes:
    - Use full rgb/srgb conversion equation instead of approximation
    - Material albedo now specified in srgb (matching the displayed color)
    - ImGui input fields becoming inactive no longer apply to a newly selected object
    - Rendering animations with path tracing correctly steps simulations each frame
    - Rasterization based renderer no longer inherits projection matrix from window
    - Scene file format no longer corrupts particle emitter enable states
    - Documentation videos no longer autoplay
    - Misc. refactoring
    - Misc. documentation website improvements
parent afa3f68f
......@@ -24,11 +24,7 @@ bool Simulate::keydown(Widgets& widgets, Undo& undo, SDL_Keysym key) {
}
void Simulate::step(Scene& scene, float dt) {
scene.for_items([this, dt](Scene_Item& item) {
if(item.is<Scene_Particles>()) {
item.get<Scene_Particles>().step(scene_bvh, dt);
}
});
scene.for_items([this, dt](Scene_Item& item) { item.step(scene_obj, dt); });
}
void Simulate::update_time() {
......@@ -48,14 +44,7 @@ void Simulate::update(Scene& scene, Undo& undo) {
float dt = clamp((float)(udt / freq), 0.0f, 0.05f);
last_update = time;
scene.for_items([this, dt](Scene_Item& item) {
if(item.is<Scene_Particles>()) {
Scene_Particles& particles = item.get<Scene_Particles>();
if(particles.opt.enabled) {
particles.step(scene_bvh, dt);
}
}
});
step(scene, dt);
}
void Simulate::render(Scene_Maybe obj_opt, Widgets& widgets, Camera& cam) {
......@@ -68,52 +57,46 @@ void Simulate::render(Scene_Maybe obj_opt, Widgets& widgets, Camera& cam) {
if(light.is_env()) return;
}
Pose& pose = item.pose();
float scale = std::min((cam.pos() - pose.pos).norm() / 5.5f, 10.0f);
Mat4 view = cam.get_view();
item.render(view);
Renderer::get().outline(view, item);
Pose& pose = item.pose();
float scale = std::min((cam.pos() - pose.pos).norm() / 5.5f, 10.0f);
widgets.render(view, pose.pos, scale);
}
void Simulate::build_scene(Scene& scene) {
if(!scene.has_particles()) return;
if(!scene.has_sim()) return;
std::mutex obj_mut;
std::vector<PT::Object> obj_list;
std::vector<std::future<PT::Object>> futures;
scene.for_items([&, this](Scene_Item& item) {
if(item.is<Scene_Object>()) {
Scene_Object& obj = item.get<Scene_Object>();
thread_pool.enqueue([&]() {
futures.push_back(thread_pool.enqueue([&]() {
if(obj.is_shape()) {
PT::Shape shape(obj.opt.shape);
std::lock_guard<std::mutex> lock(obj_mut);
obj_list.push_back(
PT::Object(std::move(shape), obj.id(), 0, obj.pose.transform()));
return PT::Object(std::move(shape), obj.id(), 0, obj.pose.transform());
} else {
PT::Tri_Mesh mesh(obj.posed_mesh());
std::lock_guard<std::mutex> lock(obj_mut);
obj_list.push_back(
PT::Object(std::move(mesh), obj.id(), 0, obj.pose.transform()));
PT::Tri_Mesh mesh(obj.posed_mesh(), use_bvh);
return PT::Object(std::move(mesh), obj.id(), 0, obj.pose.transform());
}
});
} else if(item.is<Scene_Light>()) {
Scene_Light& light = item.get<Scene_Light>();
if(light.opt.type != Light_Type::rectangle) return;
PT::Tri_Mesh mesh(Util::quad_mesh(light.opt.size.x, light.opt.size.y));
std::lock_guard<std::mutex> lock(obj_mut);
obj_list.push_back(PT::Object(std::move(mesh), light.id(), 0, light.pose.transform()));
}));
}
});
thread_pool.wait();
scene_bvh.build(std::move(obj_list));
for(auto& f : futures) {
obj_list.push_back(f.get());
}
if(use_bvh) {
scene_obj = PT::Object(PT::BVH<PT::Object>(std::move(obj_list)));
} else {
scene_obj = PT::Object(PT::List<PT::Object>(std::move(obj_list)));
}
}
void Simulate::clear_particles(Scene& scene) {
......@@ -143,6 +126,13 @@ Mode Simulate::UIsidebar(Manager& manager, Scene& scene, Undo& undo, Widgets& wi
update_bvh(scene, undo);
ImGui::Text("Simulation");
if(ImGui::Checkbox("Use BVH", &use_bvh)) {
clear_particles(scene);
build_scene(scene);
}
if(ImGui::CollapsingHeader("Add New Emitter")) {
ImGui::PushID(0);
......@@ -196,7 +186,7 @@ Mode Simulate::UIsidebar(Manager& manager, Scene& scene, Undo& undo, Widgets& wi
mesh = Util::sphere_mesh(1.0f, 1);
} break;
case Solid_Type::custom: {
mesh = scene.get_obj(ids[name_idx]).mesh().copy();
mesh = scene.get<Scene_Object>(ids[name_idx]).mesh().copy();
} break;
default: break;
}
......@@ -208,17 +198,12 @@ Mode Simulate::UIsidebar(Manager& manager, Scene& scene, Undo& undo, Widgets& wi
particles.opt.lifetime = gui_opt.lifetime;
particles.opt.pps = gui_opt.pps;
particles.opt.enabled = gui_opt.enabled;
undo.add_particles(std::move(particles));
undo.add(std::move(particles));
}
ImGui::PopID();
}
if(ImGui::Button("Generate BVH")) {
clear_particles(scene);
build_scene(scene);
}
return mode;
}
......
......@@ -35,7 +35,9 @@ public:
Mode UIsidebar(Manager& manager, Scene& scene, Undo& undo, Widgets& widgets, Scene_Maybe obj);
private:
PT::BVH<PT::Object> scene_bvh;
PT::Object scene_obj;
bool use_bvh = true;
Thread_Pool thread_pool;
Pose old_pose;
size_t cur_actions = 0;
......
......@@ -10,6 +10,7 @@
#include "manager.h"
#include "widgets.h"
#include "../app.h"
#include "../geometry/util.h"
#include "../platform/platform.h"
#include "../scene/renderer.h"
......@@ -157,7 +158,7 @@ void Widgets::render(const Mat4& view, Vec3 pos, float scl) {
z_scl.pose.scale = scale;
z_scl.pose.pos = pos + Vec3(0.0f, 0.0f, 0.15f * scl);
z_scl.render(view, true);
xyz_scl.pose.scale = scale;
xyz_scl.pose.pos = pos;
xyz_scl.render(view, true);
......@@ -186,11 +187,11 @@ Pose Widgets::apply_action(const Pose& pose) {
result.scale = Vec3{1.0f};
result.scale[(int)axis] = drag_end[(int)axis];
Mat4 rot = pose.rotation_mat();
Mat4 trans =
Mat4 trans =
Mat4::transpose(rot) * Mat4::scale(result.scale) * rot * Mat4::scale(pose.scale);
result.scale = Vec3(trans[0][0], trans[1][1], trans[2][2]);
}
} break;
case Widget_Type::bevel: {
Vec2 off = bevel_start - bevel_end;
......@@ -240,7 +241,7 @@ bool Widgets::to_axis3(Vec3 obj_pos, Vec3 cam_pos, Vec3 dir, Vec3& hit) {
Line select(cam_pos, dir);
Line target(obj_pos, axis1);
Plane k(obj_pos, axis1);
Plane l(obj_pos, axis2);
Plane r(obj_pos, axis3);
......@@ -300,7 +301,7 @@ void Widgets::start_drag(Vec3 pos, Vec3 cam, Vec2 spos, Vec3 dir) {
good = to_plane(pos, cam, dir, norm, hit);
else if(univ_scl)
good = to_axis3(pos, cam, dir, hit);
else
else
good = to_axis(pos, cam, dir, hit);
if(!good) return;
......@@ -639,7 +640,6 @@ void Widget_Render::begin(Scene& scene, Widget_Camera& cam, Camera& user_cam) {
if(method == 1) {
ImGui::InputInt("Samples", &out_samples, 1, 100);
ImGui::InputInt("Area Light Samples", &out_area_samples, 1, 100);
ImGui::InputInt("Max Ray Depth", &out_depth, 1, 32);
ImGui::SliderFloat("Exposure", &exposure, 0.01f, 10.0f, "%.2f", 2.5f);
} else {
......@@ -650,7 +650,6 @@ void Widget_Render::begin(Scene& scene, Widget_Camera& cam, Camera& user_cam) {
out_w = std::max(1, out_w);
out_h = std::max(1, out_h);
out_samples = std::max(1, out_samples);
out_area_samples = std::max(1, out_area_samples);
out_depth = std::max(1, out_depth);
if(ImGui::Button("Set Width via AR")) {
......@@ -660,6 +659,8 @@ void Widget_Render::begin(Scene& scene, Widget_Camera& cam, Camera& user_cam) {
if(ImGui::Button("Set AR via W/H")) {
cam.ar(user_cam, (float)out_w / (float)out_h);
}
ImGui::SameLine();
ImGui::Checkbox("Use BVH", &use_bvh);
}
std::string Widget_Render::step(Animate& animate, Scene& scene) {
......@@ -676,9 +677,10 @@ std::string Widget_Render::step(Animate& animate, Scene& scene) {
}
Camera cam = animate.set_time(scene, (float)next_frame);
animate.step_sim(scene);
if(method == 0) {
animate.step_sim(scene);
std::vector<unsigned char> data;
Renderer::get().save(scene, cam, out_w, out_h, out_samples);
......@@ -726,6 +728,7 @@ std::string Widget_Render::step(Animate& animate, Scene& scene) {
return "Failed to write output!";
}
animate.step_sim(scene);
pathtracer.begin_render(scene, cam);
next_frame++;
}
......@@ -778,7 +781,7 @@ void Widget_Render::animate(Scene& scene, Widget_Camera& cam, Camera& user_cam,
if(method == 1) {
init = true;
ray_log.clear();
pathtracer.set_sizes(out_w, out_h, out_samples, out_area_samples, out_depth);
pathtracer.set_params(out_w, out_h, out_samples, out_depth, use_bvh);
}
}
}
......@@ -831,7 +834,7 @@ bool Widget_Render::UI(Scene& scene, Widget_Camera& cam, Camera& user_cam, std::
has_rendered = true;
ret = true;
ray_log.clear();
pathtracer.set_sizes(out_w, out_h, out_samples, out_area_samples, out_depth);
pathtracer.set_params(out_w, out_h, out_samples, out_depth, use_bvh);
pathtracer.begin_render(scene, cam.get());
} else {
Renderer::get().save(scene, cam.get(), out_w, out_h, out_samples);
......@@ -871,6 +874,7 @@ bool Widget_Render::UI(Scene& scene, Widget_Camera& cam, Camera& user_cam, std::
if(method == 1 && has_rendered) {
ImGui::SameLine();
if(ImGui::Button("Add Samples")) {
pathtracer.set_samples((int)out_samples);
pathtracer.begin_render(scene, cam.get(), true);
}
}
......@@ -897,21 +901,20 @@ bool Widget_Render::UI(Scene& scene, Widget_Camera& cam, Camera& user_cam, std::
}
std::string Widget_Render::headless(Animate& animate, Scene& scene, const Camera& cam,
std::string output, bool a, int w, int h, int s, int ls, int d,
float exp) {
const Launch_Settings& set) {
info("Render settings:");
info("\twidth: %d", w);
info("\theight: %d", h);
info("\tsamples: %d", s);
info("\tlight samples: %d", ls);
info("\tmax depth: %d", d);
info("\texposure: %f", exp);
info("\twidth: %d", set.w);
info("\theight: %d", set.h);
info("\tsamples: %d", set.s);
info("\tmax depth: %d", set.d);
info("\texposure: %f", set.exp);
info("\trender threads: %u", std::thread::hardware_concurrency());
if(set.no_bvh) info("\tusing object list instead of BVH");
out_w = w;
out_h = h;
pathtracer.set_sizes(w, h, s, ls, d);
out_w = set.w;
out_h = set.h;
pathtracer.set_params(set.w, set.h, set.s, set.d, !set.no_bvh);
auto print_progress = [](float f) {
std::cout << "Progress: [";
......@@ -931,14 +934,14 @@ std::string Widget_Render::headless(Animate& animate, Scene& scene, const Camera
};
std::cout << std::fixed << std::setw(2) << std::setprecision(2) << std::setfill('0');
if(a) {
if(set.animate) {
method = 1;
init = true;
animating = true;
max_frame = animate.n_frames();
next_frame = 0;
folder = output;
folder = set.output_file;
while(next_frame < max_frame) {
std::string err = step(animate, scene);
if(!err.empty()) return err;
......@@ -957,8 +960,8 @@ std::string Widget_Render::headless(Animate& animate, Scene& scene, const Camera
std::cout << std::endl;
std::vector<unsigned char> data;
pathtracer.get_output().tonemap_to(data, exp);
if(!stbi_write_png(output.c_str(), w, h, 4, data.data(), w * 4)) {
pathtracer.get_output().tonemap_to(data, set.exp);
if(!stbi_write_png(set.output_file.c_str(), set.w, set.h, 4, data.data(), set.w * 4)) {
return "Failed to write output!";
}
}
......
......@@ -6,6 +6,7 @@
#include "../scene/scene.h"
class Undo;
struct Launch_Settings;
namespace Gui {
......@@ -84,8 +85,8 @@ public:
void animate(Scene& scene, Widget_Camera& cam, Camera& user_cam, int max_frame);
std::string step(Animate& animate, Scene& scene);
std::string headless(Animate& animate, Scene& scene, const Camera& cam, std::string output,
bool a, int w, int h, int s, int ls, int d, float exp);
std::string headless(Animate& animate, Scene& scene, const Camera& cam,
const Launch_Settings& set);
void log_ray(const Ray& ray, float t, Spectrum color = Spectrum{1.0f});
void render_log(const Mat4& view) const;
......@@ -100,7 +101,7 @@ public:
return pathtracer.completion_time();
}
bool in_progress() const {
return pathtracer.in_progress();
return pathtracer.in_progress() || animating;
}
float wh_ar() const {
return (float)out_w / (float)out_h;
......@@ -112,8 +113,9 @@ private:
mutable std::mutex log_mut;
GL::Lines ray_log;
int out_w, out_h, out_samples = 32, out_area_samples = 8, out_depth = 4;
int out_w, out_h, out_samples = 32, out_depth = 8;
float exposure = 1.0f;
bool use_bvh = true;
bool has_rendered = false;
bool render_window = false, render_window_focus = false;
......
......@@ -22,6 +22,16 @@ inline std::string last_file(std::string path) {
return path.substr(p, path.size() - p);
}
#ifdef _MSC_VER
#define DEBUG_BREAK __debugbreak()
#elif defined(__GNUC__)
#define DEBUG_BREAK __builtin_trap()
#elif defined(__clang__)
#define DEBUG_BREAK __builtin_debugtrap()
#else
#error Unsupported compiler.
#endif
/// Log informational message
#define info(fmt, ...) \
(void)(log("%s:%u [info] " fmt "\n", last_file(__FILE__).c_str(), __LINE__, ##__VA_ARGS__))
......@@ -35,17 +45,7 @@ inline std::string last_file(std::string path) {
#define die(fmt, ...) \
(void)(log("\033[0;31m%s:%u [fatal] " fmt "\033[0m\n", last_file(__FILE__).c_str(), __LINE__, \
##__VA_ARGS__), \
std::exit(__LINE__));
#ifdef _MSC_VER
#define DEBUG_BREAK __debugbreak()
#elif defined(__GNUC__)
#define DEBUG_BREAK __builtin_trap()
#elif defined(__clang__)
#define DEBUG_BREAK __builtin_debugtrap()
#else
#error Unsupported compiler.
#endif
DEBUG_BREAK, std::exit(__LINE__));
#define fail_assert(msg, file, line) \
(void)(log("\033[1;31m%s:%u [ASSERT] " msg "\033[0m\n", file, line), DEBUG_BREAK, \
......
......@@ -165,15 +165,18 @@ struct Mat4 {
bool single = true;
static const float singularity[] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0};
for(int i = 0; i < 12 && single; i++) {
single = single && std::abs(data[i] - singularity[i]) < 16.0f * FLT_EPSILON;
for(int i = 0; i < 3 && single; i++) {
for(int j = 0; j < 4 && single; j++) {
single = single && std::abs(cols[i][j] - singularity[i * 4 + j]) < EPS_F;
}
}
if(single) return Vec3{0.0f, 0.0f, 180.0f};
Vec3 eul1, eul2;
float cy = std::hypotf(cols[0][0], cols[0][1]);
if(cy > 16.0f * FLT_EPSILON) {
if(cy > EPS_F) {
eul1[0] = std::atan2(cols[1][2], cols[2][2]);
eul1[1] = std::atan2(-cols[0][2], cy);
eul1[2] = std::atan2(cols[0][1], cols[0][0]);
......
......@@ -13,8 +13,9 @@ struct Ray {
Ray() = default;
/// Create Ray from point and direction
explicit Ray(Vec3 point, Vec3 dir)
: point(point), dir(dir.unit()), dist_bounds(0.0f, std::numeric_limits<float>::max()) {
explicit Ray(Vec3 point, Vec3 dir,
Vec2 dist_bounds = Vec2{0.0f, std::numeric_limits<float>::max()}, size_t depth = 0)
: point(point), dir(dir.unit()), dist_bounds(dist_bounds), depth(depth) {
}
Ray(const Ray&) = default;
......
......@@ -5,8 +5,6 @@
#include <cmath>
#include <ostream>
#define GAMMA 2.1f
struct Spectrum {
Spectrum() {
......@@ -49,19 +47,38 @@ struct Spectrum {
static Spectrum direction(Vec3 v) {
v.normalize();
Spectrum s(std::abs(v.x), std::abs(v.y), std::abs(v.z));
s.make_linear();
s.to_linear();
return s;
}
void make_srgb() {
r = std::pow(r, 1.0f / GAMMA);
g = std::pow(g, 1.0f / GAMMA);
b = std::pow(b, 1.0f / GAMMA);
static float to_linear(float f) {
if(f > 0.04045f) {
return std::pow((f + 0.055f) / 1.055f, 2.4f);
} else {
return f / 12.92f;
}
}
static float to_srgb(float f) {
if(f > 0.0031308f) {
return 1.055f * (std::pow(f, (1.0f / 2.4f))) - 0.055f;
} else {
return f * 12.92f;
}
}
Spectrum to_srgb() const {
Spectrum ret;
ret.r = to_srgb(r);
ret.g = to_srgb(g);
ret.b = to_srgb(b);
return ret;
}
void make_linear() {
r = std::pow(r, GAMMA);
g = std::pow(g, GAMMA);
b = std::pow(b, GAMMA);
Spectrum to_linear() const {
Spectrum ret;
ret.r = to_linear(r);
ret.g = to_linear(g);
ret.b = to_linear(b);
return ret;
}
Spectrum operator+(Spectrum v) const {
......@@ -96,8 +113,7 @@ struct Spectrum {
}
bool valid() const {
return !(std::isinf(r) || std::isinf(g) || std::isinf(b) || std::isnan(r) ||
std::isnan(g) || std::isnan(b));
return std::isfinite(r) && std::isfinite(g) && std::isfinite(b);
}
Vec3 to_vec() const {
......
......@@ -123,7 +123,7 @@ struct Vec2 {
}
/// Are all members real numbers?
bool valid() const {
return !(std::isinf(x) || std::isinf(y) || std::isnan(x) || std::isnan(y));
return std::isfinite(x) && std::isfinite(y);
}
/// Modify vec to have unit length
......
......@@ -134,8 +134,7 @@ struct Vec3 {
}
/// Are all members real numbers?
bool valid() const {
return !(std::isinf(x) || std::isinf(y) || std::isinf(z) || std::isnan(x) ||
std::isnan(y) || std::isnan(z));
return std::isfinite(x) && std::isfinite(y) && std::isfinite(z);
}
/// Modify vec to have unit length
......
......@@ -152,8 +152,7 @@ struct Vec4 {
}
/// Are all members real numbers?
bool valid() const {
return !(std::isinf(x) || std::isinf(y) || std::isinf(z) || std::isinf(w) ||
std::isnan(x) || std::isnan(y) || std::isnan(z) || std::isnan(w));
return std::isfinite(x) && std::isfinite(y) && std::isfinite(z) && std::isfinite(w);
}
/// Modify vec to have unit length
......
......@@ -7,31 +7,31 @@ int main(int argc, char** argv) {
RNG::seed();
App::Settings settings;
Launch_Settings set;
CLI::App args{"Scotty3D - 15-462"};
args.add_option("-s,--scene", settings.scene_file, "Scene file to load");
args.add_option("--env_map", settings.env_map_file, "Override scene environment map");
args.add_flag("--headless", settings.headless, "Path-trace scene without opening the GUI");
args.add_option("-o,--output", settings.output_file, "Image file to write (if headless)");
args.add_flag("--animate", settings.animate, "Output animation frames (if headless)");
args.add_option("--width", settings.w, "Output image width (if headless)");
args.add_option("--height", settings.h, "Output image height (if headless)");
args.add_flag("--use_ar", settings.w_from_ar,
args.add_option("-s,--scene", set.scene_file, "Scene file to load");
args.add_option("--env_map", set.env_map_file, "Override scene environment map");
args.add_flag("--headless", set.headless, "Path-trace scene without opening the GUI");
args.add_option("-o,--output", set.output_file, "Image file to write (if headless)");
args.add_flag("--animate", set.animate, "Output animation frames (if headless)");
args.add_flag("--no_bvh", set.no_bvh, "Don't use BVH (if headless)");
args.add_option("--width", set.w, "Output image width (if headless)");
args.add_option("--height", set.h, "Output image height (if headless)");
args.add_flag("--use_ar", set.w_from_ar,
"Compute output image width based on camera AR (if headless)");
args.add_option("--depth", settings.d, "Maximum ray depth (if headless)");
args.add_option("--samples", settings.s, "Pixel samples (if headless)");
args.add_option("--exposure", settings.exp, "Output exposure (if headless)");
args.add_option("--area_samples", settings.ls, "Area light samples (if headless)");
args.add_option("--depth", set.d, "Maximum ray depth (if headless)");
args.add_option("--samples", set.s, "Pixel samples (if headless)");
args.add_option("--exposure", set.exp, "Output exposure (if headless)");
CLI11_PARSE(args, argc, argv);
if(!settings.headless) {
if(!set.headless) {
Platform plt;
App app(settings, &plt);
App app(set, &plt);
plt.loop(app);
} else {
App app(settings);
App app(set);
}
return 0;
}
......@@ -152,8 +152,8 @@ void Tex2D::image(int w, int h, unsigned char* img) {
if(!id) glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
......@@ -1305,7 +1305,7 @@ void main() {
vec3 pos = normalize(f_pos);
if(pos.y > cosine) {
if(use_texture) {
float theta = atan(pos.z,pos.x) / TAU + 0.5f;
float theta = atan(pos.z,pos.x) / TAU;
float phi = acos(pos.y) / PI;
out_col = texture(tex, vec2(theta,phi));
} else {
......
......@@ -11,12 +11,10 @@
namespace PT {
struct BSDF_Sample {
struct Scatter {
Spectrum emissive;
Spectrum attenuation;
Vec3 direction;
float pdf;
void transform(const Mat4& T) {
direction = T.rotate(direction);
......@@ -25,14 +23,15 @@ struct BSDF_Sample {
struct BSDF_Lambertian {
BSDF_Lambertian(Spectrum albedo) : albedo(albedo) {
BSDF_Lambertian(Spectrum albedo) : albedo(albedo / PI_F) {
}
BSDF_Sample sample(Vec3 out_dir) const;
Scatter scatter(Vec3 out_dir) const;
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const;
float pdf(Vec3 out_Dir, Vec3 in_dir) const;
Spectrum albedo;
Samplers::Hemisphere::Uniform sampler;
Samplers::Hemisphere::Cosine sampler;
};
struct BSDF_Mirror {
......@@ -40,8 +39,7 @@ struct BSDF_Mirror {
BSDF_Mirror(Spectrum reflectance) : reflectance(reflectance) {
}
BSDF_Sample sample(Vec3 out_dir) const;
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const;
Scatter scatter(Vec3 out_dir) const;
Spectrum reflectance;
};
......@@ -52,8 +50,7 @@ struct BSDF_Refract {
: transmittance(transmittance), index_of_refraction(ior) {
}
BSDF_Sample sample(Vec3 out_dir) const;
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const;
Scatter scatter(Vec3 out_dir) const;
Spectrum transmittance;
float index_of_refraction;
......@@ -65,8 +62,7 @@ struct BSDF_Glass {
: transmittance(transmittance), reflectance(reflectance), index_of_refraction(ior) {
}
BSDF_Sample sample(Vec3 out_dir) const;
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const;
Scatter scatter(Vec3 out_dir) const;
Spectrum transmittance;
Spectrum reflectance;
......@@ -78,11 +74,9 @@ struct BSDF_Diffuse {
BSDF_Diffuse(Spectrum radiance) : radiance(radiance) {
}
BSDF_Sample sample(Vec3 out_dir) const;
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const;
Spectrum emissive() const;
Spectrum radiance;
Samplers::Hemisphere::Uniform sampler;
};
class BSDF {
......@@ -103,22 +97,41 @@ public:
BSDF& operator=(BSDF&& src) = default;
BSDF(BSDF&& src) = default;
BSDF_Sample sample(Vec3 out_dir) const {
return std::visit(overloaded{[&out_dir](const auto& b) { return b.sample(out_dir); }},
Scatter scatter(Vec3 out_dir) const {
return std::visit(overloaded{[](const BSDF_Diffuse& d) -> Scatter {
die("You scattered an emissive BSDF!");
},
[out_dir](const auto& b) { return b.scatter(out_dir); }},
underlying);
}
Spectrum evaluate(Vec3 out_dir, Vec3 in_dir) const {
return std::visit(
overloaded{[&out_dir, &in_dir](const auto& b) { return b.evaluate(out_dir, in_dir); }},
overloaded{
[out_dir, in_dir](const BSDF_Lambertian& l) { return l.evaluate(out_dir, in_dir); },
[](const auto&) -> Spectrum { die("You evaluated a delta BSDF!"); }},
underlying);
}
float pdf(Vec3 out_dir, Vec3 in_dir) const {
return std::visit(
overloaded{
[out_dir, in_dir](const BSDF_Lambertian& l) { return l.pdf(out_dir, in_dir); },
[](const auto&) -> float { die("You evaluated the pdf of a delta BSDF!"); }},
underlying);
}
Spectrum emissive() const {
return std::visit(overloaded{[](const BSDF_Diffuse& d) { return d.emissive(); },
[](const auto& b) { return Spectrum{}; }},
underlying);
}
bool is_discrete() const {
return std::visit(overloaded{[](const BSDF_Lambertian&) { return false; },
[](const BSDF_Diffuse&) { return false; },
[](const BSDF_Mirror&) { return true; },
[](const BSDF_Glass&) { return true; },
[](const BSDF_Diffuse&) { return false; },
[](const BSDF_Refract&) { return true; }},
underlying);
}
......@@ -136,7 +149,4 @@ private:
std::variant<BSDF_Lambertian, BSDF_Mirror, BSDF_Glass, BSDF_Diffuse, BSDF_Refract> underlying;
};
Vec3 reflect(Vec3 dir);
Vec3 refract(Vec3 out_dir, float index_of_refraction, bool& was_internal);
} // namespace PT
......@@ -31,7 +31,7 @@ public:
private:
class Node {
BBox bbox;
size_t start, size, l, r;
......
......@@ -17,8 +17,9 @@ struct Env_Hemisphere {
Env_Hemisphere(Spectrum r) : radiance(r) {
}
Light_Sample sample() const;
Spectrum sample_direction(Vec3 dir) const;
Vec3 sample() const;
Spectrum evaluate(Vec3 dir) const;
float pdf(Vec3 dir) const;
Spectrum radiance;
Samplers::Hemisphere::Uniform sampler;
......@@ -29,8 +30,9 @@ struct Env_Sphere {
Env_Sphere(Spectrum r) : radiance(r) {
}
Light_Sample sample() const;
Spectrum sample_direction(Vec3 dir) const;
Vec3 sample() const;
Spectrum evaluate(Vec3 dir) const;
float pdf(Vec3 dir) const;
Spectrum radiance;
Samplers::Sphere::Uniform sampler;
......@@ -38,14 +40,16 @@ struct Env_Sphere {
struct Env_Map {
Env_Map(HDR_Image&& img) : image(std::move(img)), sampler(image) {
Env_Map(HDR_Image&& img) : image(std::move(img)), image_sampler(image) {
}
Light_Sample sample() const;
Spectrum sample_direction(Vec3 dir) const;
Vec3 sample() const;
Spectrum evaluate(Vec3 dir) const;
float pdf(Vec3 dir) const;
HDR_Image image;
Samplers::Sphere::Image sampler;
Samplers::Sphere::Uniform uniform_sampler;
Samplers::Sphere::Image image_sampler;
};
class Env_Light {
......@@ -62,19 +66,16 @@ public:
Env_Light& operator=(Env_Light&& src) = default;
Env_Light(Env_Light&& src) = default;
Light_Sample sample(Vec3) const {
return std::visit(overloaded{[](const Env_Hemisphere& h) { return h.sample(); },
[](const Env_Sphere& h) { return h.sample(); },
[](const Env_Map& h) { return h.sample(); }},
underlying);
Vec3 sample() const {
return std::visit([](const auto& h) { return h.sample(); }, underlying);
}
Spectrum sample_direction(Vec3 dir) const {
return std::visit(
overloaded{[&dir](const Env_Hemisphere& h) { return h.sample_direction(dir); },
[&dir](const Env_Sphere& h) { return h.sample_direction(dir); },
[&dir](const Env_Map& h) { return h.sample_direction(dir); }},
underlying);
float pdf(Vec3 dir) const {
return std::visit([dir](const auto& h) { return h.pdf(dir); }, underlying);
}
Spectrum evaluate(Vec3 dir) const {
return std::visit([&dir](const auto& h) { return h.evaluate(dir); }, underlying);
}
bool is_discrete() const {
......
......@@ -7,7 +7,6 @@ Light_Sample Directional_Light::sample(Vec3) const {
Light_Sample ret;
ret.direction = Vec3(0.0f, -1.0f, 0.0f);
ret.distance = std::numeric_limits<float>::infinity();
ret.pdf = 1.0f;
ret.radiance = radiance;
return ret;
}
......@@ -16,7 +15,6 @@ Light_Sample Point_Light::sample(Vec3 from) const {
Light_Sample ret;
ret.direction = -from.unit();
ret.distance = from.norm();
ret.pdf = 1.0f;
ret.radiance = radiance;
return ret;
}
......@@ -27,28 +25,9 @@ Light_Sample Spot_Light::sample(Vec3 from) const {
angle = std::abs(Degrees(angle));
ret.direction = -from.unit();
ret.distance = from.norm();
ret.pdf = 1.0f;
ret.radiance =
(1.0f - smoothstep(angle_bounds.x / 2.0f, angle_bounds.y / 2.0f, angle)) * radiance;
return ret;
}
Light_Sample Rect_Light::sample(Vec3 from) const {
Light_Sample ret;
Vec2 sample = sampler.sample(ret.pdf);
Vec3 point(sample.x - size.x / 2.0f, 0.0f, sample.y - size.y / 2.0f);
Vec3 dir = point - from;
float cos_theta = dir.y;
float squared_dist = dir.norm_squared();
float dist = std::sqrt(squared_dist);
ret.direction = dir / dist;
ret.distance = dist;
ret.pdf *= squared_dist / std::abs(cos_theta);
ret.radiance = cos_theta > 0.0f ? radiance : Spectrum{};
return ret;
}
} // namespace PT
......@@ -16,8 +16,7 @@ struct Light_Sample {
Spectrum radiance;
Vec3 direction;
float distance;
float pdf;
float distance = 0.0f;
void transform(const Mat4& T) {
direction = T.rotate(direction);
......@@ -26,90 +25,59 @@ struct Light_Sample {
struct Directional_Light {
Directional_Light(Spectrum r) : radiance(r), sampler(Vec3(0.0f, 1.0f, 0.0f)) {
Directional_Light(Spectrum r) : radiance(r) {
}
Light_Sample sample(Vec3 from) const;
Spectrum radiance;
Samplers::Direction sampler;
};
struct Point_Light {
Point_Light(Spectrum r) : radiance(r), sampler(Vec3(0.0f)) {
Point_Light(Spectrum r) : radiance(r) {
}
Light_Sample sample(Vec3 from) const;
Spectrum radiance;
Samplers::Point sampler;
};
struct Spot_Light {
Spot_Light(Spectrum r, Vec2 a) : radiance(r), angle_bounds(a), sampler(Vec3(0.0f)) {
Spot_Light(Spectrum r, Vec2 a) : radiance(r), angle_bounds(a) {
}
Light_Sample sample(Vec3 from) const;
Spectrum radiance;
Vec2 angle_bounds;
Samplers::Point sampler;
};
struct Rect_Light {
Rect_Light(Spectrum r, Vec2 s) : radiance(r), size(s), sampler(size) {
}
Light_Sample sample(Vec3 from) const;
Spectrum radiance;
Vec2 size;
Samplers::Rect::Uniform sampler;
};
class Light {
class Delta_Light {
public:
Light(Directional_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), _id(id), underlying(std::move(l)) {
has_trans = trans != Mat4::I;
}
Light(Point_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
Delta_Light(Directional_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), _id(id), underlying(std::move(l)) {
has_trans = trans != Mat4::I;
}
Light(Spot_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
Delta_Light(Point_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), _id(id), underlying(std::move(l)) {
has_trans = trans != Mat4::I;
}
Light(Rect_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
Delta_Light(Spot_Light&& l, Scene_ID id, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), _id(id), underlying(std::move(l)) {
has_trans = trans != Mat4::I;
}
Light(const Light& src) = delete;
Light& operator=(const Light& src) = delete;
Light& operator=(Light&& src) = default;
Light(Light&& src) = default;
Delta_Light(const Delta_Light& src) = delete;
Delta_Light& operator=(const Delta_Light& src) = delete;
Delta_Light& operator=(Delta_Light&& src) = default;
Delta_Light(Delta_Light&& src) = default;
Light_Sample sample(Vec3 from) const {
if(has_trans) from = itrans * from;
Light_Sample ret =
std::visit(overloaded{[&from](const auto& l) { return l.sample(from); }}, underlying);
Light_Sample ret = std::visit([from](const auto& l) { return l.sample(from); }, underlying);
if(has_trans) ret.transform(trans);
return ret;
}
bool is_discrete() const {
return std::visit(overloaded{[](const Directional_Light&) { return true; },
[](const Point_Light&) { return true; },
[](const Spot_Light&) { return true; },
[](const Rect_Light&) { return false; }},
underlying);
}
Scene_ID id() const {
return _id;
}
......@@ -123,7 +91,7 @@ private:
bool has_trans;
Mat4 trans, itrans;
Scene_ID _id;
std::variant<Directional_Light, Point_Light, Spot_Light, Rect_Light> underlying;
std::variant<Directional_Light, Point_Light, Spot_Light> underlying;
};
} // namespace PT
......@@ -2,6 +2,7 @@
#pragma once
#include "../lib/mathlib.h"
#include "../util/rand.h"
#include "trace.h"
namespace PT {
......@@ -10,7 +11,7 @@ template<typename Primitive> class List {
public:
List() {
}
List(std::vector<Primitive>&& primitives) : prims(primitives) {
List(std::vector<Primitive>&& primitives) : prims(std::move(primitives)) {
}
BBox bbox() const {
......@@ -34,6 +35,32 @@ public:
prims.push_back(std::move(prim));
}
List<Primitive> copy() const {
std::vector<Primitive> prim_copy = prims;
return List<Primitive>(std::move(prim_copy));
}
Vec3 sample(Vec3 from) const {
if(prims.empty()) return {};
int n = RNG::integer(0, (int)prims.size());
return prims[n].sample(from);
}
float pdf(Ray ray, const Mat4& T = Mat4::I, const Mat4& iT = Mat4::I) const {
if(prims.empty()) return 0.0f;
float ret = 0.0f;
for(auto& prim : prims) ret += prim.pdf(ray, T, iT);
return ret / prims.size();
}
void clear() {
prims.clear();
}
bool empty() const {
return prims.empty();
}
private:
std::vector<Primitive> prims;
};
......
......@@ -32,38 +32,73 @@ public:
has_trans = trans != Mat4::I;
}
Object() {
}
Object(List<Object>&& list, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), underlying(std::move(list)) {
}
Object(BVH<Object>&& bvh, const Mat4& T = Mat4::I)
: trans(T), itrans(T.inverse()), underlying(std::move(bvh)) {
}
Object(const Object& src) = delete;
Object& operator=(const Object& src) = delete;
Object& operator=(Object&& src) = default;
Object(Object&& src) = default;
BBox bbox() const {
BBox box = std::visit(overloaded{[](const auto& o) { return o.bbox(); }}, underlying);
BBox box = std::visit([](const auto& o) { return o.bbox(); }, underlying);
if(has_trans) box.transform(trans);
return box;
}
Trace hit(Ray ray) const {
if(has_trans) ray.transform(itrans);
Trace ret =
std::visit(overloaded{[&ray](const auto& o) { return o.hit(ray); }}, underlying);
Trace ret = std::visit([&ray](const auto& o) { return o.hit(ray); }, underlying);
if(ret.hit) {
ret.material = material;
if(material != -1) ret.material = material;
if(has_trans) ret.transform(trans, itrans.T());
}
return ret;
}
size_t visualize(GL::Lines& lines, GL::Lines& active, size_t level, const Mat4& vtrans) const {
Mat4 next = has_trans ? vtrans * trans : vtrans;
size_t visualize(GL::Lines& lines, GL::Lines& active, size_t level, Mat4 vtrans) const {
if(has_trans) vtrans = vtrans * trans;
return std::visit(
overloaded{
[&](const BVH<Object>& bvh) { return bvh.visualize(lines, active, level, next); },
[&](const Tri_Mesh& mesh) { return mesh.visualize(lines, active, level, next); },
[&](const BVH<Object>& bvh) { return bvh.visualize(lines, active, level, vtrans); },
[&](const Tri_Mesh& mesh) { return mesh.visualize(lines, active, level, vtrans); },
[](const auto&) { return size_t(0); }},
underlying);
}
Vec3 sample(Vec3 from) const {
if(has_trans) from = itrans * from;
Vec3 dir =
std::visit(overloaded{[from](const List<Object>& list) { return list.sample(from); },
[from](const Tri_Mesh& mesh) { return mesh.sample(from); },
[](const auto&) -> Vec3 {
die("Sampling implicit objects/BVHs is not yet supported.");
}},
underlying);
if(has_trans) dir = trans.rotate(dir).unit();
return dir;
}
float pdf(Ray ray, Mat4 T = Mat4::I, Mat4 iT = Mat4::I) const {
if(has_trans) {
T = T * trans;
iT = itrans * iT;
}
return std::visit(
overloaded{[ray, T, iT](const List<Object>& list) { return list.pdf(ray, T, iT); },
[ray, T, iT](const Tri_Mesh& mesh) { return mesh.pdf(ray, T, iT); },
[](const auto&) -> float {
die("Sampling implicit objects/BVHs is not yet supported.");
}},
underlying);
}
Scene_ID id() const {
return _id;
}
......@@ -76,7 +111,7 @@ public:
private:
bool has_trans;
Mat4 trans, itrans;
unsigned int material;
int material = -1;
Scene_ID _id;
std::variant<Tri_Mesh, Shape, BVH<Object>, List<Object>> underlying;
};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment