diff --git a/Makefile b/Makefile index 604b2c7..9707de2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX = g++ RM = rm -f -CXXFLAGS = -O3 -Wall -Wpedantic -std=c++17 -MMD -MP -fopenmp +CXXFLAGS = -O3 -Wall -Wpedantic -std=c++20 -MMD -MP -fopenmp EXEC = render.exe OUTPUT_DIR = output @@ -37,7 +37,7 @@ $(EXEC): $(OBJS_LOCATION) $(BUILD_OBJS_DIR)/%.o: %.cpp Makefile @$(eval CURRENT_TARGET=$(shell expr $(CURRENT_TARGET) + 1)) - @printf "\rCompiling file $(CURRENT_TARGET) / $(NB_TARGETS)" + @printf "\rCompiling file $(CURRENT_TARGET) / $(NB_TARGETS) " @if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi @$(CXX) $(CXXFLAGS) -I $(SRC_DIR) -c $< -o $@ diff --git a/TODO.md b/TODO.md index c39fad5..1b021bb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,8 @@ -- Book 2 section 4 : texture mapping +- Book 2 section 8 : instances - make buildchain crossplatform with CMake - test to replace RNG for grid sampling in antialiasing - centralize asset loading in a resource manager - find a good name for the project - have a format describing scenes instead of code description -- add log system \ No newline at end of file +- add render queue \ No newline at end of file diff --git a/run.bash b/run.bash index 44b833e..7e46723 100755 --- a/run.bash +++ b/run.bash @@ -7,7 +7,7 @@ if [ "$result" -ne "0" ]; then fi source ./config.bash -./build/executable/render.exe $1 +./build/executable/render.exe $1 $2 $3 $4 result=$? if [ "$result" -eq "0" ]; then diff --git a/src/assets/image_asset.cpp b/src/assets/image_asset.cpp index a0113b5..4873bc7 100644 --- a/src/assets/image_asset.cpp +++ b/src/assets/image_asset.cpp @@ -2,6 +2,7 @@ #include "image_asset.hpp" #include "utils.hpp" +#include "logger.hpp" #define STB_IMAGE_IMPLEMENTATION #include "third_party/stb_image.h" @@ -17,11 +18,11 @@ namespace fs = std::filesystem; int ImageAsset::Load() { - clog << "Loading " << m_filepath << " ..." << endl; - fs::path path(m_filepath); + Logger::LogInfo("Loading " + m_filepath + " ..."); + fs::path path(m_filepath); if(!fs::exists(m_filepath)){ - cerr << "\t" << m_filepath << ": file not found" << endl; + Logger::LogError("\t" + m_filepath + ": file not found"); return false; } @@ -30,8 +31,8 @@ int ImageAsset::Load() float* fdata; fdata = stbi_loadf(path.c_str(), &m_width, &m_height, &n, m_bytes_per_pixel); if(fdata == NULL){ - cerr << "\t" << m_filepath << ": error while loading file" << endl; - cerr << "\t" << stbi_failure_reason() << endl; + Logger::LogError("\t" + m_filepath + ": error while loading file"); + Logger::LogError("\t" + string(stbi_failure_reason())); return false; } @@ -45,7 +46,7 @@ int ImageAsset::Load() m_bdata[i] = rgb_normalized_to_8bits(m_fdata[i]); } - clog << "Loaded " << m_filepath << " successfully" << endl; + Logger::LogInfo("Loaded " + m_filepath + " successfully"); return true; } diff --git a/src/export/png_exporter.cpp b/src/export/png_exporter.cpp index 471ac58..bbf9c54 100644 --- a/src/export/png_exporter.cpp +++ b/src/export/png_exporter.cpp @@ -2,11 +2,11 @@ #include "png_exporter.hpp" #include "utils.hpp" +#include "logger.hpp" #include #include #include -#include using namespace std; @@ -98,7 +98,7 @@ void PngExporter::ImageToScanline(uint8_t filtering, uint8_t *dest) const for(uint32_t j=0; j buffer) m_output_fs = ofstream(m_filepath, std::ios::out | std::ios::binary); if ( !m_output_fs.is_open()) { - cerr << __FUNCTION__ << " : failed to open " << m_filepath << endl; + Logger::LogError(string(__FUNCTION__) + " : failed to open " + m_filepath); return 1; } diff --git a/src/export/ppm_exporter.cpp b/src/export/ppm_exporter.cpp index 19cc959..de09b1b 100644 --- a/src/export/ppm_exporter.cpp +++ b/src/export/ppm_exporter.cpp @@ -3,9 +3,9 @@ #include "renderer/camera.hpp" #include "math/interval.hpp" #include "utils.hpp" +#include "logger.hpp" #include -#include #include @@ -23,7 +23,7 @@ int PpmExporter::Export(int width, int height, std::shared_ptr buffe ofstream file(m_filepath); if ( !file.is_open()) { - cerr << __FUNCTION__ << " : failed to open " << m_filepath << endl; + Logger::LogError(string(__FUNCTION__) + " : failed to open " + m_filepath); return 1; } @@ -32,7 +32,7 @@ int PpmExporter::Export(int width, int height, std::shared_ptr buffe for(int j=0; j material) + : m_q{q}, m_u{u}, m_v{v}, m_material{material} +{ + Vec3 n = m_u.Cross(m_v); + m_normal = n.Normalized(); + m_d = m_normal.Dot(m_q); + m_w = n / n.Dot(n); + + AABB diag1_aabb = AABB(m_q, m_q + m_u + m_v); + AABB diag2_aabb = AABB(m_q + m_u, m_q + m_v); + m_aabb = AABB(diag1_aabb, diag2_aabb); +} + + +bool Quad::Hit(const Ray &ray, const Interval &interval, HitRecord &outRecord) const +{ + double denominator = m_normal.Dot(ray.Direction()); + if(std::fabs(denominator) < 1e-8){ + return false; + } + + double t = (m_d - m_normal.Dot(ray.Origin())) / denominator; + + if(! interval.Contains(t)){ + return false; + } + + Vec3 intersection = ray.At(t); + + Vec3 plan_centered_hitpoint = intersection - m_q; + double alpha = m_w.Dot(plan_centered_hitpoint.Cross(m_v)); + double beta = m_w.Dot(m_u.Cross(plan_centered_hitpoint)); + + if(! IsInQuad(alpha, beta, outRecord)){ + return false; + } + + outRecord.t = t; + outRecord.hit_point = intersection; + outRecord.material = m_material; + outRecord.SetFaceNormal(ray, m_normal); + + return true; +} + + +bool Quad::IsInQuad(double alpha, double beta, HitRecord& hit_record) const { + static const Interval unit_interval = Interval(0.0, 1.0); + + // set it anyway to make code branchless + hit_record.u = alpha; + hit_record.v = beta; + + return unit_interval.Contains(alpha) && unit_interval.Contains(beta); +} \ No newline at end of file diff --git a/src/geometry/quad.hpp b/src/geometry/quad.hpp new file mode 100644 index 0000000..0f6c981 --- /dev/null +++ b/src/geometry/quad.hpp @@ -0,0 +1,38 @@ + +#pragma once + + +#include "geometry/IHittable.hpp" + +#include "math/vec.hpp" + + +class Quad : public IHittable { + +private: + + Point3 m_q; + Vec3 m_u; + Vec3 m_v; + + Vec3 m_normal; + // d is the intersection equation result for the plan + // for a point Q on the plane, n.Q = d + double m_d; + // w is a constant for a given quadrilateral + // It simplifies the check to know if we are inside the quad + Vec3 m_w; + + AABB m_aabb; + std::shared_ptr m_material; + + bool IsInQuad(double alpha, double beta, HitRecord& hit_record) const; + + +public: + + Quad(const Point3& q, const Vec3& u, const Vec3& v, std::shared_ptr material); + + bool Hit(const Ray& ray, const Interval& interval, HitRecord& outRecord) const override; + AABB GetAABB() const override { return m_aabb; } +}; \ No newline at end of file diff --git a/src/geometry/sphere.hpp b/src/geometry/sphere.hpp index 1263d6f..cdb5f13 100644 --- a/src/geometry/sphere.hpp +++ b/src/geometry/sphere.hpp @@ -13,8 +13,9 @@ class Sphere : public IHittable { Point3 m_center; double m_radius; - std::shared_ptr m_material; + AABB m_aabb; + std::shared_ptr m_material; public: diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000..0648f95 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,36 @@ + +#include "logger.hpp" + +#include +#include +#include + + +using namespace std; +using namespace std::chrono; + + +string Logger::GetCurrentTimeFormatted() +{ + auto now = std::chrono::system_clock::now(); + return format("[{:%T}]", zoned_time{ current_zone(), floor(now)}); +} + + +void Logger::Log(const std::string &log, const LogLevel& level) +{ + if(level < s_level){ + return; + } + + ostringstream string_builder; + string_builder << GetCurrentTimeFormatted() << " "; + string_builder << s_colored_name_lookup.at(level) << " "; + string_builder << log; + string_builder << (s_overwrite ? '\r' : '\n'); + + const string& formatted_log = string_builder.str(); + for(ostream* stream : s_outputs){ + *stream << formatted_log << flush; + } +} diff --git a/src/logger.hpp b/src/logger.hpp new file mode 100644 index 0000000..41070f1 --- /dev/null +++ b/src/logger.hpp @@ -0,0 +1,51 @@ + +#pragma once + +#include +#include +#include + + +enum class LogLevel { + DEBUG = 1, + INFO = 2, + WARNING = 3, + ERROR = 4, + FATAL = 5 +}; + + +class Logger { + +private: + + static inline std::vector s_outputs = { &std::cout }; + static inline LogLevel s_level = LogLevel::DEBUG; + static inline bool s_overwrite = false; + + static inline const std::unordered_map s_colored_name_lookup + { + { LogLevel::DEBUG, "[\x1b[94mDEBUG\x1b[0m] " }, + { LogLevel::INFO, "[INFO] " }, + { LogLevel::WARNING, "[\x1b[93mWARNING\x1b[0m]" }, + { LogLevel::ERROR, "[\x1b[91mERROR\x1b[0m] " }, + { LogLevel::FATAL, "[\x1b[31mFATAL\x1b[0m] " } + }; + + static void Log(const std::string& log, const LogLevel& level); + static std::string GetCurrentTimeFormatted(); + + +public: + + static void SetLogLevel(const LogLevel& level) { s_level = level; } + static void AddOutput(std::ostream& stream) { s_outputs.push_back(&stream); } + static void SetOverwrite(bool value) { s_overwrite = value; } + + static void LogDebug(const std::string& log) { Log(log, LogLevel::DEBUG); } + static void LogInfo(const std::string& log) { Log(log, LogLevel::INFO); } + static void LogWarning(const std::string& log) { Log(log, LogLevel::WARNING); } + static void LogError(const std::string& log) { Log(log, LogLevel::ERROR); } + static void LogFatal(const std::string& log) { Log(log, LogLevel::FATAL); } + +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6b28ceb..6ff11d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,10 +3,14 @@ #include "scenes/sparsed_spheres_scene.hpp" #include "scenes/checkered_spheres_scene.hpp" #include "scenes/solar_system_scene.hpp" +#include "scenes/quads_scene.hpp" +#include "scenes/cornell_box_scene.hpp" #include "renderer/camera.hpp" +#include "renderer/pathtracing_renderer.hpp" #include "export/png_exporter.hpp" +#include "logger.hpp" -#include +#include #include @@ -15,20 +19,37 @@ using namespace std; int main(int argc, char *argv[]){ - if (argc != 2){ - cout << "Usage : " << argv[0] << " width" << endl; + if (argc != 5){ + Logger::LogError(format("Usage : {} width aa_sample_per_pixel ray_max_depth scene_id", argv[0])); + Logger::LogInfo("Advised values are width=1280 aa_sample_per_pixel=100 ray_max_depth=10 scene_id=5"); return 1; } - cout << "Initializing scene" << endl; - + Logger::LogInfo("Initializing scene"); + SceneParams scene_params; - scene_params.render_width = atoi(argv[1]); - scene_params.enable_bvh = true; + int aa_sample_per_pixel; + int max_depth; + int scene_id; + + try{ + scene_params.render_width = stoi(argv[1]); + scene_params.enable_bvh = true; + + aa_sample_per_pixel = stoi(argv[2]); + max_depth = stoi(argv[3]); + scene_id = stoi(argv[4]); + } + catch(std::exception const& e) { + Logger::LogFatal("Could not parse command-line arguments"); + Logger::LogFatal(e.what()); + return 1; + } + unique_ptr scene; - switch(3){ + switch(scene_id){ case 1: scene = make_unique(); break; @@ -38,20 +59,36 @@ int main(int argc, char *argv[]){ case 3: scene = make_unique(); break; + case 4: + scene = make_unique(); + break; + case 5: + scene = make_unique(); + break; default: - cout << "Problem in scene selection" << endl; + Logger::LogFatal("Problem in scene selection"); return 1; } - cout << "Building scene " << typeid(*scene.get()).name() << endl; + Logger::LogInfo("Building scene " + string(typeid(*scene.get()).name())); scene->Build(std::move(scene_params)); - cout << "Starting rendering" << endl; - shared_ptr color_buffer = scene->Render(); - - PngExporter png_exporter("output/render.png"); + Logger::LogInfo(format("Starting rendering scene {}", scene_id)); + Logger::LogInfo(format("Using {} samples per pixel, {} max rebounds", aa_sample_per_pixel, max_depth)); uint32_t out_width = scene->GetCamera()->ImageWidth(); uint32_t out_height = scene->GetCamera()->ImageHeight(); + Logger::LogInfo(format("Outputing image of size {}x{} pixels", out_width, out_height)); + + auto renderer = dynamic_cast(scene->GetRenderer().get()); + PathTracingRendererParams params; + params.aa_sample_per_pixel = aa_sample_per_pixel; + params.max_depth = max_depth; + renderer->SetParams(std::move(params)); + + shared_ptr color_buffer = scene->Render(params.aa_sample_per_pixel); + + PngExporter png_exporter("output/render.png"); png_exporter.Export(out_width, out_height, color_buffer); + return 0; } \ No newline at end of file diff --git a/src/material/IMaterial.hpp b/src/material/IMaterial.hpp index e13d7ab..f692655 100644 --- a/src/material/IMaterial.hpp +++ b/src/material/IMaterial.hpp @@ -13,6 +13,7 @@ class IMaterial { public: virtual ~IMaterial() {} + virtual bool Scatter( const Ray& incoming_ray, const HitRecord& rec, @@ -23,5 +24,10 @@ class IMaterial { return false; } + virtual RGBColor Emitted(double u, double v, const Point3& point) const + { + return BLACK; + } + }; \ No newline at end of file diff --git a/src/material/diffuse_light.hpp b/src/material/diffuse_light.hpp new file mode 100644 index 0000000..2751ad2 --- /dev/null +++ b/src/material/diffuse_light.hpp @@ -0,0 +1,27 @@ + +#pragma once + +#include + +#include "material/IMaterial.hpp" +#include "texture/solid_color_texture.hpp" + + +class DiffuseLight : public IMaterial { + +private: + + std::shared_ptr m_albedo; + + +public: + + DiffuseLight(const RGBColor& albedo): m_albedo{std::make_shared(albedo)} {}; + DiffuseLight(std::shared_ptr albedo): m_albedo{albedo} {}; + + RGBColor Emitted(double u, double v, const Point3& point) const override + { + return m_albedo->GetColor(u, v, point); + } + +}; \ No newline at end of file diff --git a/src/math/math_utils.cpp b/src/math/math_utils.cpp index 4ba0483..c936344 100644 --- a/src/math/math_utils.cpp +++ b/src/math/math_utils.cpp @@ -6,24 +6,6 @@ #include -int lerp(int start, int stop, double value) -{ - return int((1-value) * start + value * stop); -} - - -double lerp(double start, double stop, double value) -{ - return (1-value) * start + value * stop; -} - - -Vec3 lerp(const Vec3& start, const Vec3& stop, double value) -{ - return start * (1-value) + stop * value; -} - - double degrees_to_radians(double degrees) { return degrees * M_PI / 180.0; diff --git a/src/math/math_utils.hpp b/src/math/math_utils.hpp index 9590da6..fdef486 100644 --- a/src/math/math_utils.hpp +++ b/src/math/math_utils.hpp @@ -4,10 +4,6 @@ class Vec3; - -int lerp(int start, int stop, double value); -double lerp(double start, double stop, double value); -Vec3 lerp(const Vec3& start, const Vec3& stop, double value); double degrees_to_radians(double degrees); double random_double(); double random_double(double min, double max); diff --git a/src/math/vec.hpp b/src/math/vec.hpp index 076c795..2636869 100644 --- a/src/math/vec.hpp +++ b/src/math/vec.hpp @@ -106,4 +106,6 @@ inline Vec3 operator*(const Vec3& u, const Vec3& v) { return Vec3{u.e[0]*v.e[0], typedef Vec3 Point3; -typedef Vec3 RGBColor; \ No newline at end of file +typedef Vec3 RGBColor; +inline const RGBColor BLACK = RGBColor(0.0, 0.0, 0.0); +inline const RGBColor WHITE = RGBColor(1.0, 1.0, 1.0); \ No newline at end of file diff --git a/src/renderer/IRenderer.hpp b/src/renderer/IRenderer.hpp index f9d1877..35e758b 100644 --- a/src/renderer/IRenderer.hpp +++ b/src/renderer/IRenderer.hpp @@ -16,7 +16,8 @@ class IRenderer { public: virtual ~IRenderer() {}; - virtual void Render() = 0; + virtual void Init() = 0; + virtual void Render(int n_steps) = 0; virtual std::shared_ptr GetBuffer() = 0; }; \ No newline at end of file diff --git a/src/renderer/camera.cpp b/src/renderer/camera.cpp index f1ea723..83a61f8 100644 --- a/src/renderer/camera.cpp +++ b/src/renderer/camera.cpp @@ -6,40 +6,33 @@ Camera::Camera(CameraParams&& params) { - m_aspect_ratio = params.aspect_ratio; - m_image_width = params.image_width; - m_vfov = params.vfov; - m_lookfrom = params.lookfrom; - m_lookat = params.lookat; - m_vup = params.vup; - m_defocus_angle = params.defocus_angle; - m_focus_dist = params.focus_dist; - - m_camera_center = m_lookfrom; - - m_image_height = int(m_image_width / m_aspect_ratio); + m_params = params; + + m_camera_center = m_params.lookfrom; + + m_image_height = int(m_params.image_width / m_params.aspect_ratio); m_image_height = (m_image_height < 1) ? 1 : m_image_height; - double theta = degrees_to_radians(m_vfov); + double theta = degrees_to_radians(m_params.vfov); double h = tan(theta/2.0); - m_viewport_height = 2.0 * h * m_focus_dist; - m_viewport_width = m_viewport_height * (double(m_image_width) / m_image_height); + m_viewport_height = 2.0 * h * m_params.focus_dist; + m_viewport_width = m_viewport_height * (double(m_params.image_width) / m_image_height); - m_w = (m_lookfrom - m_lookat).Normalized(); - m_u = m_vup.Cross(m_w).Normalized(); + m_w = (m_params.lookfrom - m_params.lookat).Normalized(); + m_u = m_params.vup.Cross(m_w).Normalized(); m_v = m_w.Cross(m_u); m_viewport_u = m_viewport_width * m_u; m_viewport_v = m_viewport_height * (-m_v); - m_pixel_delta_u = m_viewport_u / m_image_width; + m_pixel_delta_u = m_viewport_u / m_params.image_width; m_pixel_delta_v = m_viewport_v / m_image_height; m_viewport_upper_left = m_camera_center - - (m_focus_dist * m_w) - m_viewport_u/2 - m_viewport_v/2; + - (m_params.focus_dist * m_w) - m_viewport_u/2 - m_viewport_v/2; m_pixel00_loc = m_viewport_upper_left + (m_pixel_delta_u + m_pixel_delta_v) * 0.5; - double defocus_radius = m_focus_dist * tan(degrees_to_radians(m_defocus_angle / 2)); + double defocus_radius = m_params.focus_dist * tan(degrees_to_radians(m_params.defocus_angle / 2)); m_defocus_disk_u = m_u * defocus_radius; m_defocus_disk_v = m_v * defocus_radius; } @@ -59,7 +52,7 @@ Ray Camera::SampleRayForPixel(int i, int j) const + offset[0] * m_pixel_delta_u + offset[1] * m_pixel_delta_v; - Point3 ray_origin = (m_defocus_angle <= 0) ? m_camera_center : DefocusDiskSample(); + Point3 ray_origin = (m_params.defocus_angle <= 0) ? m_camera_center : DefocusDiskSample(); return Ray(ray_origin, sample_position - ray_origin);; } diff --git a/src/renderer/camera.hpp b/src/renderer/camera.hpp index 213315e..2de4c14 100644 --- a/src/renderer/camera.hpp +++ b/src/renderer/camera.hpp @@ -16,6 +16,7 @@ struct CameraParams { Vec3 vup; double defocus_angle; double focus_dist; + RGBColor background_color; }; @@ -23,10 +24,10 @@ class Camera { private: + CameraParams m_params; + Vec3 m_camera_center; - double m_aspect_ratio; - int m_image_width; int m_image_height; double m_focal_length; @@ -42,18 +43,10 @@ class Camera { Vec3 m_pixel_delta_v; Point3 m_pixel00_loc; - double m_vfov; - - Point3 m_lookfrom; - Point3 m_lookat; - Vec3 m_vup; - Vec3 m_u, m_v ,m_w; - double m_defocus_angle; - double m_focus_dist; - - Vec3 m_defocus_disk_u; // Defocus disk horizontal radius + // Defocus disk horizontal radius + Vec3 m_defocus_disk_u; Vec3 m_defocus_disk_v; @@ -62,8 +55,9 @@ class Camera { Camera(CameraParams&& params); double ImageHeight() const { return m_image_height; } - double ImageWidth() const { return m_image_width; } + double ImageWidth() const { return m_params.image_width; } Point3 CameraCenter() const { return m_camera_center; } + CameraParams GetParams() const { return m_params; } Point3 GetPixelPosition(int i, int j) const; diff --git a/src/renderer/pathtracing_renderer.cpp b/src/renderer/pathtracing_renderer.cpp index ca7acb7..191279a 100644 --- a/src/renderer/pathtracing_renderer.cpp +++ b/src/renderer/pathtracing_renderer.cpp @@ -8,10 +8,13 @@ #include "geometry/hittable_list.hpp" #include "material/IMaterial.hpp" #include "utils.hpp" +#include "logger.hpp" #include -#include #include +#include +#include +#include "pathtracing_renderer.hpp" using namespace std; @@ -25,88 +28,99 @@ PathTracingRenderer::PathTracingRenderer( : m_camera{camera}, m_scene{scene}, m_params{params} { // shared_ptr initialization with size for array is C++20, we are C++17, workaround - m_buffer = (shared_ptr) make_unique(camera->ImageWidth() * camera->ImageHeight()); + m_buffer = make_shared(camera->ImageWidth() * camera->ImageHeight()); } -void PathTracingRenderer::Render() { +void PathTracingRenderer::Render(int n_steps) { + + bool last_pass = false; + if(n_steps > m_params.aa_sample_per_pixel - m_render_step){ + n_steps = m_params.aa_sample_per_pixel - m_render_step; + last_pass = true; + } + const int height = m_camera->ImageHeight(); const int width = m_camera->ImageWidth(); - - #ifdef _OPENMP - // assuming 6-8 physical cores on machine - int num_threads = 12; - string env_num_threads = get_env_var("OMP_NUM_THREAD"); - try { - num_threads = stoi(env_num_threads); - } - catch (const std::exception& e){ - // keep default value - cout << "OMP_NUM_THREAD is undefined" << endl; + const int bar_width = 70; + + for(int sample = 0; sample < n_steps; ++sample){ + + log_progress_bar(m_render_step, m_params.aa_sample_per_pixel, bar_width, false); + ++m_render_step; + + #pragma omp parallel for shared(m_buffer, height, width, m_render_step) + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + Ray sampleRay = m_camera->SampleRayForPixel(i, j); + RGBColor ray_color = GetRayColor(sampleRay, m_params.max_depth); + m_buffer[j * width + i] = m_buffer[j * width + i] + (ray_color - m_buffer[j * width + i])/m_render_step; + } } + } + + if(last_pass){ + // for satisfaction of full bar + log_progress_bar(m_render_step, m_params.aa_sample_per_pixel, bar_width, true); + Logger::LogInfo("Done"); + } +} - omp_set_num_threads(num_threads); - cout << "Using " << num_threads << " CPU threads to render" << endl; - int progress = 0; - #pragma omp parallel for num_threads(num_threads) shared(progress) - #endif +void PathTracingRenderer::Init() +{ + m_render_step = 0; - for (int j = 0; j < height; j++) { + #ifdef _OPENMP + SetupOpenMP(); + #endif - #ifdef _OPENMP - #pragma omp critical - { - clog << "\rLines remaining: " << (height - progress) << ' ' << flush; - } - #else - clog << "\rLines remaining: " << (height - j) << ' ' << flush; - #endif - - for (int i = 0; i < width; i++) { - - RGBColor accumulator(0,0,0); - for(int sample = 0; sample < m_params.aa_sample_per_pixel; ++sample){ - Ray sampleRay = m_camera->SampleRayForPixel(i, j); - accumulator += GetRayColor(sampleRay, m_params.max_depth); - } + memset((void*) m_buffer.get(), 0, sizeof(m_buffer)); +} - accumulator /= m_params.aa_sample_per_pixel; - - m_buffer[j * width + i] = accumulator; - } - #ifdef _OPENMP - #pragma omp critical - { - ++progress; - } - #endif +#ifdef _OPENMP +void PathTracingRenderer::SetupOpenMP() +{ + // assuming 6-8 physical cores on machine + int num_threads = 12; + string env_num_threads = get_env_var("OMP_NUM_THREAD"); + try { + num_threads = stoi(env_num_threads); } - - clog << "\rDone. \n"; + catch (const std::exception& e){ + // keep default value + Logger::LogWarning("OMP_NUM_THREAD is undefined"); + } + + omp_set_num_threads(num_threads); + Logger::LogInfo(format("Using {} CPU threads to render", num_threads)); } +#endif RGBColor PathTracingRenderer::GetRayColor(const Ray& ray, size_t depth) { - if(depth == 0){ - return RGBColor(0, 0, 0); + if(depth <= 0){ + return BLACK; } - HitRecord hitRecord; - Interval interval = Interval(0.001, INFINITY); - if( m_scene->Hit(ray, interval, hitRecord) ){ - Ray scattered_ray; - RGBColor attenuation(0, 0, 0); - if (hitRecord.material->Scatter(ray, hitRecord, attenuation, scattered_ray)){ - return attenuation * GetRayColor(scattered_ray, depth-1); - } - return RGBColor(0, 0, 0); + HitRecord hit_record; + static Interval interval = Interval(0.001, INFINITY); + if(!m_scene->Hit(ray, interval, hit_record)){ + return m_camera->GetParams().background_color; } - Vec3 unitDirection = ray.Direction().Normalized(); - double a = 0.5 * (unitDirection[1] + 1.0); - return lerp(RGBColor(1.0, 1.0, 1.0), RGBColor(0.5, 0.7, 1.0), a); + Ray scattered_ray; + RGBColor attenuation = BLACK; + RGBColor color_from_emission = hit_record.material->Emitted(hit_record.u, hit_record.v, hit_record.hit_point); + + if (!hit_record.material->Scatter(ray, hit_record, attenuation, scattered_ray)){ + return color_from_emission; + } + + RGBColor color_from_scatter = attenuation * GetRayColor(scattered_ray, depth-1); + + return color_from_emission + color_from_scatter; } \ No newline at end of file diff --git a/src/renderer/pathtracing_renderer.hpp b/src/renderer/pathtracing_renderer.hpp index 5529ae4..155d35f 100644 --- a/src/renderer/pathtracing_renderer.hpp +++ b/src/renderer/pathtracing_renderer.hpp @@ -24,7 +24,11 @@ class PathTracingRenderer : public IRenderer { std::shared_ptr m_buffer; PathTracingRendererParams m_params; + int m_render_step = 0; + #ifdef _OPENMP + void SetupOpenMP(); + #endif public: @@ -34,9 +38,13 @@ class PathTracingRenderer : public IRenderer { PathTracingRendererParams&& params ); - void Render() override; + void Init() override; + void Render(int n_steps) override; RGBColor GetRayColor(const Ray& ray, size_t depth); std::shared_ptr GetBuffer() { return m_buffer; } + PathTracingRendererParams GetParams() { return m_params; } + + void SetParams(PathTracingRendererParams&& params) { m_params = params; } }; \ No newline at end of file diff --git a/src/scenes/IScene.hpp b/src/scenes/IScene.hpp index 60ee0a0..9c47a30 100644 --- a/src/scenes/IScene.hpp +++ b/src/scenes/IScene.hpp @@ -19,20 +19,36 @@ class IRenderer; class IScene { -protected: +protected: SceneParams m_params; std::shared_ptr m_objets; std::shared_ptr m_camera; std::shared_ptr m_renderer; + virtual std::shared_ptr InitCamera() = 0; + virtual std::shared_ptr InitObjects() = 0; + virtual std::shared_ptr InitRenderer() = 0; + public: ~IScene() = default; - virtual void Build(SceneParams&& params) = 0; - virtual std::shared_ptr Render() = 0; - - const Camera* GetCamera() { return m_camera.get(); } + virtual void Build(SceneParams&& params) + { + m_params = params; + m_camera = InitCamera(); + m_objets = InitObjects(); + m_renderer = InitRenderer(); + } + + virtual std::shared_ptr Render(int n_steps) + { + m_renderer->Render(n_steps); + return m_renderer->GetBuffer(); + } + + const std::shared_ptr GetCamera() { return m_camera; } + const std::shared_ptr GetRenderer() { return m_renderer; } }; \ No newline at end of file diff --git a/src/scenes/checkered_spheres_scene.cpp b/src/scenes/checkered_spheres_scene.cpp index f300bfa..cb8685c 100644 --- a/src/scenes/checkered_spheres_scene.cpp +++ b/src/scenes/checkered_spheres_scene.cpp @@ -26,6 +26,7 @@ shared_ptr CheckeredSpheresScene::InitCamera() camera_params.vup = Vec3(0,1,0); camera_params.defocus_angle = 0.0; camera_params.focus_dist = 10.0; + camera_params.background_color = RGBColor(0.70, 0.80, 1.00); return make_shared(std::move(camera_params)); } @@ -53,27 +54,12 @@ shared_ptr CheckeredSpheresScene::InitObjects() } -shared_ptr CheckeredSpheresScene::InitRenderer() +shared_ptr CheckeredSpheresScene::InitRenderer() { PathTracingRendererParams params; params.aa_sample_per_pixel = 100; - params.max_depth = 20; - - return make_shared(m_camera, m_objets, move(params)); -} - - -void CheckeredSpheresScene::Build(SceneParams &¶ms) -{ - m_params = params; - m_camera = InitCamera(); - m_objets = InitObjects(); - m_renderer = InitRenderer(); -} - - -shared_ptr CheckeredSpheresScene::Render() -{ - m_renderer->Render(); - return m_renderer->GetBuffer(); -} + params.max_depth = 10; + m_renderer = make_shared(m_camera, m_objets, move(params)); + m_renderer->Init(); + return m_renderer; +} \ No newline at end of file diff --git a/src/scenes/checkered_spheres_scene.hpp b/src/scenes/checkered_spheres_scene.hpp index 9a42ea5..de58183 100644 --- a/src/scenes/checkered_spheres_scene.hpp +++ b/src/scenes/checkered_spheres_scene.hpp @@ -6,20 +6,13 @@ class Camera; class HittableList; -class PathTracingRenderer; class CheckeredSpheresScene: public IScene { private: - std::shared_ptr InitCamera(); - std::shared_ptr InitObjects(); - std::shared_ptr InitRenderer(); - - -public: - - void Build(SceneParams&& params) override; - std::shared_ptr Render() override; + std::shared_ptr InitCamera() override; + std::shared_ptr InitObjects() override; + std::shared_ptr InitRenderer() override; }; \ No newline at end of file diff --git a/src/scenes/cornell_box_scene.cpp b/src/scenes/cornell_box_scene.cpp new file mode 100644 index 0000000..3b3148d --- /dev/null +++ b/src/scenes/cornell_box_scene.cpp @@ -0,0 +1,75 @@ + +#include "cornell_box_scene.hpp" + +#include "geometry/hittable_list.hpp" +#include "geometry/hittable_list.hpp" +#include "geometry/quad.hpp" +#include "geometry/bvh_node.hpp" +#include "material/lambertian.hpp" +#include "material/diffuse_light.hpp" +#include "texture/checker_texture.hpp" +#include "renderer/camera.hpp" +#include "renderer/pathtracing_renderer.hpp" + +#include + + +using namespace std; + + +shared_ptr CornellBoxScene::InitCamera() +{ + CameraParams camera_params; + camera_params.aspect_ratio = 1.0; + camera_params.image_width = m_params.render_width; + camera_params.vfov = 40.0; + camera_params.lookfrom = Point3(278, 278, -800); + camera_params.lookat = Point3(278, 278, 0); + camera_params.vup = Vec3(0,1,0); + camera_params.defocus_angle = 0.0; + camera_params.focus_dist = 10.0; + camera_params.background_color = BLACK; + + return make_shared(std::move(camera_params)); +} + + +shared_ptr CornellBoxScene::InitObjects() +{ + auto hittable_list = make_shared(); + + auto red = make_shared(RGBColor(.65, .05, .05)); + auto white = make_shared(RGBColor(.73, .73, .73)); + auto green = make_shared(RGBColor(.12, .45, .15)); + auto light = make_shared(RGBColor(15, 15, 15)); + + hittable_list->AddObjects({ + make_shared(Point3(555,0,0), Vec3(0,555,0), Vec3(0,0,555), green), + make_shared(Point3(0,0,0), Vec3(0,555,0), Vec3(0,0,555), red), + make_shared(Point3(343, 554, 332), Vec3(-130,0,0), Vec3(0,0,-105), light), + make_shared(Point3(0,0,0), Vec3(555,0,0), Vec3(0,0,555), white), + make_shared(Point3(555,555,555), Vec3(-555,0,0), Vec3(0,0,-555), white), + make_shared(Point3(0,0,555), Vec3(555,0,0), Vec3(0,555,0), white) + }); + + if(m_params.enable_bvh) { + vector> objects = hittable_list->CopyObjects(); + auto root = make_shared(objects, 0, objects.size()); + + hittable_list = make_shared(); + hittable_list->AddObject(root); + } + + return hittable_list; +} + + +shared_ptr CornellBoxScene::InitRenderer() +{ + PathTracingRendererParams params; + params.aa_sample_per_pixel = 100; + params.max_depth = 10; + m_renderer = make_shared(m_camera, m_objets, move(params)); + m_renderer->Init(); + return m_renderer; +} \ No newline at end of file diff --git a/src/scenes/cornell_box_scene.hpp b/src/scenes/cornell_box_scene.hpp new file mode 100644 index 0000000..020129b --- /dev/null +++ b/src/scenes/cornell_box_scene.hpp @@ -0,0 +1,18 @@ + +#pragma once + +#include "scenes/IScene.hpp" + + +class Camera; +class HittableList; + +class CornellBoxScene: public IScene { + +private: + + std::shared_ptr InitCamera() override; + std::shared_ptr InitObjects() override; + std::shared_ptr InitRenderer() override; + +}; \ No newline at end of file diff --git a/src/scenes/quads_scene.cpp b/src/scenes/quads_scene.cpp new file mode 100644 index 0000000..2b5a0c3 --- /dev/null +++ b/src/scenes/quads_scene.cpp @@ -0,0 +1,66 @@ + +#include "quads_scene.hpp" + +#include "geometry/hittable_list.hpp" +#include "geometry/quad.hpp" +#include "material/lambertian.hpp" +#include "renderer/camera.hpp" +#include "renderer/pathtracing_renderer.hpp" + +#include + + +using namespace std; + + +shared_ptr QuadsScene::InitCamera() +{ + CameraParams camera_params; + camera_params.aspect_ratio = 1.0; + camera_params.image_width = m_params.render_width; + camera_params.vfov = 80.0; + camera_params.lookfrom = Point3(0,0,9); + camera_params.lookat = Point3(0,0,0); + camera_params.vup = Vec3(0,1,0); + camera_params.defocus_angle = 0.0; + camera_params.focus_dist = 10.0; + camera_params.background_color = RGBColor(0.70, 0.80, 1.00); + + return make_shared(std::move(camera_params)); +} + + +shared_ptr QuadsScene::InitObjects() +{ + auto hittable_list = make_shared(); + + // Materials + auto left_red = make_shared(RGBColor(1.0, 0.2, 0.2)); + auto back_green = make_shared(RGBColor(0.2, 1.0, 0.2)); + auto right_blue = make_shared(RGBColor(0.2, 0.2, 1.0)); + auto upper_orange = make_shared(RGBColor(1.0, 0.5, 0.0)); + auto lower_teal = make_shared(RGBColor(0.2, 0.8, 0.8)); + + // Quads + hittable_list->AddObjects({ + make_shared(Point3(-3,-2, 5), Vec3(0, 0,-4), Vec3(0, 4, 0), left_red), + make_shared(Point3(-2,-2, 0), Vec3(4, 0, 0), Vec3(0, 4, 0), back_green), + make_shared(Point3( 3,-2, 1), Vec3(0, 0, 4), Vec3(0, 4, 0), right_blue), + make_shared(Point3(-2, 3, 1), Vec3(4, 0, 0), Vec3(0, 0, 4), upper_orange), + make_shared(Point3(-2,-3, 5), Vec3(4, 0, 0), Vec3(0, 0,-4), lower_teal) + }); + + return hittable_list; +} + + +shared_ptr QuadsScene::InitRenderer() +{ + PathTracingRendererParams params; + params.aa_sample_per_pixel = 100; + params.max_depth = 10; + m_renderer = make_shared(m_camera, m_objets, move(params)); + m_renderer->Init(); + return m_renderer; +} + diff --git a/src/scenes/quads_scene.hpp b/src/scenes/quads_scene.hpp new file mode 100644 index 0000000..bf29205 --- /dev/null +++ b/src/scenes/quads_scene.hpp @@ -0,0 +1,18 @@ + +#pragma once + +#include "scenes/IScene.hpp" + + +class Camera; +class HittableList; + +class QuadsScene: public IScene { + +private: + + std::shared_ptr InitCamera(); + std::shared_ptr InitObjects(); + std::shared_ptr InitRenderer(); + +}; \ No newline at end of file diff --git a/src/scenes/solar_system_scene.cpp b/src/scenes/solar_system_scene.cpp index 01c3bb0..8053a03 100644 --- a/src/scenes/solar_system_scene.cpp +++ b/src/scenes/solar_system_scene.cpp @@ -1,7 +1,6 @@ #include "solar_system_scene.hpp" -#include "geometry/hittable_list.hpp" #include "geometry/hittable_list.hpp" #include "geometry/sphere.hpp" #include "material/lambertian.hpp" @@ -27,6 +26,7 @@ shared_ptr SolarSystemScene::InitCamera() camera_params.vup = Vec3(0,1,0); camera_params.defocus_angle = 0.0; camera_params.focus_dist = 10.0; + camera_params.background_color = RGBColor(0.70, 0.80, 1.00); return make_shared(std::move(camera_params)); } @@ -71,27 +71,12 @@ shared_ptr SolarSystemScene::InitObjects() } -shared_ptr SolarSystemScene::InitRenderer() +shared_ptr SolarSystemScene::InitRenderer() { PathTracingRendererParams params; - params.aa_sample_per_pixel = 300; - params.max_depth = 50; - - return make_shared(m_camera, m_objets, move(params)); -} - - -void SolarSystemScene::Build(SceneParams &¶ms) -{ - m_params = params; - m_camera = InitCamera(); - m_objets = InitObjects(); - m_renderer = InitRenderer(); -} - - -shared_ptr SolarSystemScene::Render() -{ - m_renderer->Render(); - return m_renderer->GetBuffer(); -} + params.aa_sample_per_pixel = 100; + params.max_depth = 10; + m_renderer = make_shared(m_camera, m_objets, move(params)); + m_renderer->Init(); + return m_renderer; +} \ No newline at end of file diff --git a/src/scenes/solar_system_scene.hpp b/src/scenes/solar_system_scene.hpp index b08f1e5..f6bd121 100644 --- a/src/scenes/solar_system_scene.hpp +++ b/src/scenes/solar_system_scene.hpp @@ -6,7 +6,6 @@ class Camera; class HittableList; -class PathTracingRenderer; class SolarSystemScene: public IScene { @@ -14,12 +13,6 @@ class SolarSystemScene: public IScene { std::shared_ptr InitCamera(); std::shared_ptr InitObjects(); - std::shared_ptr InitRenderer(); - - -public: - - void Build(SceneParams&& params) override; - std::shared_ptr Render() override; + std::shared_ptr InitRenderer(); }; \ No newline at end of file diff --git a/src/scenes/sparsed_spheres_scene.cpp b/src/scenes/sparsed_spheres_scene.cpp index b8e300c..4814eb2 100644 --- a/src/scenes/sparsed_spheres_scene.cpp +++ b/src/scenes/sparsed_spheres_scene.cpp @@ -25,6 +25,7 @@ shared_ptr SparsedSpheresScene::InitCamera() camera_params.vup = Vec3(0,1,0); camera_params.defocus_angle = 0.6; camera_params.focus_dist = 10.0; + camera_params.background_color = RGBColor(0.70, 0.80, 1.00); return make_shared(std::move(camera_params)); } @@ -100,27 +101,12 @@ shared_ptr SparsedSpheresScene::InitObjects() } -shared_ptr SparsedSpheresScene::InitRenderer() +shared_ptr SparsedSpheresScene::InitRenderer() { PathTracingRendererParams params; params.aa_sample_per_pixel = 100; - params.max_depth = 20; - - return make_shared(m_camera, m_objets, move(params)); -} - - -void SparsedSpheresScene::Build(SceneParams &¶ms) -{ - m_params = params; - m_camera = InitCamera(); - m_objets = InitObjects(); - m_renderer = InitRenderer(); -} - - -shared_ptr SparsedSpheresScene::Render() -{ - m_renderer->Render(); - return m_renderer->GetBuffer(); -} + params.max_depth = 10; + m_renderer = make_shared(m_camera, m_objets, move(params)); + m_renderer->Init(); + return m_renderer; +} \ No newline at end of file diff --git a/src/scenes/sparsed_spheres_scene.hpp b/src/scenes/sparsed_spheres_scene.hpp index c2e0265..c400845 100644 --- a/src/scenes/sparsed_spheres_scene.hpp +++ b/src/scenes/sparsed_spheres_scene.hpp @@ -14,12 +14,6 @@ class SparsedSpheresScene: public IScene { std::shared_ptr InitCamera(); std::shared_ptr InitObjects(); - std::shared_ptr InitRenderer(); - - -public: - - void Build(SceneParams&& params) override; - std::shared_ptr Render() override; + std::shared_ptr InitRenderer(); }; \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp index 0088dba..2030437 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -2,9 +2,11 @@ #include "utils.hpp" #include "math/interval.hpp" +#include "logger.hpp" -#include #include +#include +#include using namespace std; @@ -17,7 +19,7 @@ string get_env_var(const string& key) { char* val = getenv(key.c_str()); if(val == NULL){ - clog << "Could not retrieve environment variable with key '" << key << "'" << endl; + Logger::LogError("Could not retrieve environment variable with key '" + key + "'"); } return val == NULL ? string("") : string(val); } @@ -43,4 +45,29 @@ void rgb_normalized_to_8bits(const RGBColor& color, uint8_t *dest){ uint8_t rgb_normalized_to_8bits(float value){ return static_cast(255.999 * std::clamp(value, 0.f, 1.f)); -} \ No newline at end of file +} + + +void log_progress_bar(int progress, int total, int bar_width, bool is_last) +{ + Logger::SetOverwrite(true); + + stringstream string_builder; + + float progress_ratio = float(progress)/total; + int pos = bar_width * progress_ratio; + + string_builder << format("[{}/{}] [", progress, total); + for (int i = 0; i < bar_width; ++i) { + if (i < pos) string_builder << "="; + else if (i == pos) string_builder << "-"; + else string_builder << "_"; + } + string_builder << format("] {:.2f} %", progress_ratio*100); + + if(is_last) { + Logger::SetOverwrite(false); + } + + Logger::LogInfo(string_builder.str()); +} diff --git a/src/utils.hpp b/src/utils.hpp index 0e16188..52bdcda 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -15,4 +15,6 @@ void rgb_normalized_to_8bits(const RGBColor& color, uint8_t *dest); uint8_t rgb_normalized_to_8bits(float value); +void log_progress_bar(int progress, int total, int bar_width, bool is_last); + extern float MAGENTA[3]; \ No newline at end of file