From 15f0d349a9cc1116f8b4003525f3a2ff8dca0be1 Mon Sep 17 00:00:00 2001 From: Nobuyuki Date: Mon, 3 Jun 2024 10:17:34 +0900 Subject: [PATCH] added task07 --- CMakeLists.txt | 2 +- README.md | 6 +- task06/CMakeLists.txt | 2 +- task07/CMakeLists.txt | 35 ++++ task07/README.md | 41 ++++ task07/main.cpp | 430 ++++++++++++++++++++++++++++++++++++++++++ task07/out_brdf.png | Bin 0 -> 12051 bytes task07/out_light.png | Bin 0 -> 6640 bytes task07/out_mis.png | Bin 0 -> 9577 bytes 9 files changed, 512 insertions(+), 4 deletions(-) create mode 100644 task07/CMakeLists.txt create mode 100644 task07/README.md create mode 100644 task07/main.cpp create mode 100644 task07/out_brdf.png create mode 100644 task07/out_light.png create mode 100644 task07/out_mis.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 38d7e48..4c32b27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,6 @@ add_subdirectory(task03) add_subdirectory(task04) add_subdirectory(task05) add_subdirectory(task06) -# add_subdirectory(task07) +add_subdirectory(task07) # add_subdirectory(task08) # add_subdirectory(task09) diff --git a/README.md b/README.md index 51f45b3..59e3911 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Topics: |(4)
May 13| **Graphics pipeline**
depth buffer method, shading, shadow, anti aliasing | [task04](task04) | [[12]](http://nobuyuki-umetani.com/acg2024s/rasterization_3d.pdf) [[13]](http://nobuyuki-umetani.com/acg2024s/graphics_pipeline.pdf) | |(6)
May 20| **Ray Casting 1**
spatial data structure | [task05](task05) | [[14]](http://nobuyuki-umetani.com/acg2024s/shading.pdf) [[16]](http://nobuyuki-umetani.com/acg2024s/implicit_modeling.pdf) | |(7)
May 27| **Ray Casting 2**
Rendering equation, Monte Carlo integration | [task06](task06) | [[15]](http://nobuyuki-umetani.com/acg2024s/rasterization_subpixel.pdf) [[17]](http://nobuyuki-umetani.com/acg2024s/ray_casting.pdf) [[18]](http://nobuyuki-umetani.com/acg2024s/monte_carlo_integration.pdf) [[19]](http://nobuyuki-umetani.com/acg2024s/ray_triangle_collision.pdf) | -|(8)
June 3| **Character animation**
Linear blend skinning | task07 | | +|(8)
June 3| **Character animation**
Linear blend skinning | [task07](task07) | [[20]](http://nobuyuki-umetani.com/acg2024s/character_deformation.pdf) [[21]](http://nobuyuki-umetani.com/acg2024s/jacobian.pdf) | |(9)
June 10| Guest lecture by Dr. Rex West | | | |(10)
June 17| **Optimization**
Inverse kinematic | task08 | | |(11)
June 24| Laplacian mesh deformation | task09 | | @@ -84,7 +84,7 @@ Look at the following document. | [task04](task04) | **Vertex shader practice**
Rendering pipeline, mirror reflection, OpenGL | | | [task05](task05) | **Fragment shader practice**
Ray marching method, CSG modeling, implicit modeling | | | [task06](task06) | **Monte Carlo integration1**
Ambient occlusion, importance sampling, BVH | | -| task07 | **Monte Carlo integration2**
Multiple importance sampling | | +| [task07](task07) | **Monte Carlo integration2**
Multiple importance sampling | | | task08 | **Skeletal Character Animation**
Linear blend skinning, articulated rigid body | | | task09 | TBD | | | task10 | TBD | | @@ -136,6 +136,8 @@ Look at the following document. - [[18]Monte Carlo Integration](http://nobuyuki-umetani.com/acg2024s/monte_carlo_integration.pdf) - [[19]Ray Triangle Collision](http://nobuyuki-umetani.com/acg2024s/ray_triangle_collision.pdf) +- [[20]Character deformation](http://nobuyuki-umetani.com/acg2024s/character_deformation.pdf) +- [[21]Jacobian&Hessian](http://nobuyuki-umetani.com/acg2024s/jacobian.pdf) diff --git a/task06/CMakeLists.txt b/task06/CMakeLists.txt index 16afabc..10dbd62 100644 --- a/task06/CMakeLists.txt +++ b/task06/CMakeLists.txt @@ -10,7 +10,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # we are using STL library ############################# # set project name -project(task07) +project(task06) ############################# # define macro diff --git a/task07/CMakeLists.txt b/task07/CMakeLists.txt new file mode 100644 index 0000000..16afabc --- /dev/null +++ b/task07/CMakeLists.txt @@ -0,0 +1,35 @@ +# specify the version of cmake (intentionally using old version for those who cannot update CMake) +cmake_minimum_required(VERSION 3.10) + +############################# +# set C++ detail +enable_language(CXX) # we are using C++ +set(CMAKE_CXX_STANDARD 17) # we are using C++ 17 +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # we are using STL library + +############################# +# set project name + +project(task07) + +############################# +# define macro +add_definitions(-DPROJECT_SOURCE_DIR="${PROJECT_SOURCE_DIR}") + +############################# +# specifying libraries to use + +######################## +# include, build, and link + +include_directories( + ${PROJECT_SOURCE_DIR}/../external + ${PROJECT_SOURCE_DIR}/../external/eigen +) + +add_executable(${PROJECT_NAME} + main.cpp +) + +target_link_libraries(${PROJECT_NAME} +) \ No newline at end of file diff --git a/task07/README.md b/task07/README.md new file mode 100644 index 0000000..5f3c3bd --- /dev/null +++ b/task07/README.md @@ -0,0 +1,41 @@ +# Task07: Multiple Importance Sampling (Brdf sampling, Light sampling) + +![preview](preview.png) + +**Deadline: June 6th (Thu) at 15:00pm** + +---- + +## Before Doing Assignment + +If you have not done the [task01](../task01), [task02](../task02) do it first to set up the C++ development environment. + +Follow [this document](../doc/submit.md) to submit the assignment, In a nutshell, before doing the assignment, +- make sure you synchronized the `main ` branch of your local repository to that of remote repository. +- make sure you created branch `task07` from `main` branch. +- make sure you are currently in the `task07` branch (use `git branch -a` command). + +Now you are ready to go! + +--- + +## Problem + +- Implement light sampling by lighting a single line code around `line #364` +- Implement Brdf sampling by lighting a single line code around `line #383` +- Implement MIS sampling by lighting a few lines of code around `line #401` around `line #403` + +Run the program with **Release mode** and it will generate three images that replace the images below. + +| Light sampling | Brdf sampling | MIS sampling | +| ----------------------- | ------------------------ | ------------------------ | +| ![light](out_light.png) | ![brdf](out_brdf.png) | ![mis](out_mis.png) | + +Observe that three image looks similar but the noise is reduced by MIS sampling. + + + + +## After Doing the Assignment + +After modify the code, push the code and submit a pull request. Make sure your pull request only contains the files you edited. Good luck! diff --git a/task07/main.cpp b/task07/main.cpp new file mode 100644 index 0000000..4d649cd --- /dev/null +++ b/task07/main.cpp @@ -0,0 +1,430 @@ +#include +#include +#include +#include +#include +// +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#include "Eigen/Core" +#include "Eigen/Geometry" + +/** + * ray - sphere intersection + * @param ray_src + * @param ray_dir + * @param center center of sphere + * @param rad radius of sphere + * @return if it does not hit return nullopt, if hit return position normal, and depth of hit position + */ +auto intersection_ray_sphere( + const Eigen::Vector3f &ray_src, + const Eigen::Vector3f &ray_dir, + const Eigen::Vector3f ¢er, + float rad) -> std::optional> { + float depth0 = (center - ray_src).dot(ray_dir); + if (depth0 < 0.f) { return {}; } + float sqdist = (ray_src + depth0 * ray_dir - center).squaredNorm(); + if (rad * rad - sqdist < 0.f) { return {}; } + float depth1 = depth0 - sqrt(rad * rad - sqdist); + if (depth1 < 0.f) { return {}; } + auto hit_pos = ray_src + depth1 * ray_dir; + auto hit_normal = (hit_pos - center).normalized(); + return std::make_tuple(hit_pos, hit_normal, depth1); +} + +auto local_to_world_vector_transformation( + const Eigen::Vector3f &nrm) -> Eigen::Matrix3f { + auto basis_x = Eigen::Vector3f(1.f, 0.f, 0.f); + const auto basis_y = nrm.cross(basis_x).normalized(); + basis_x = basis_y.cross(nrm); + Eigen::Matrix3f loc2world; + loc2world << basis_x, basis_y, nrm; // initialization from 3 column vectors + return loc2world; +} + +auto sampling_brdf_lambert( + const Eigen::Vector3f &nrm, + const Eigen::Vector2f &unirand) -> std::pair { + const float r = std::sqrt(unirand.x()); + const float phi = 2.f * float(M_PI) * unirand.y(); + const float z = std::sqrt(1.f - r * r); + const auto dir_loc = Eigen::Vector3f( // direction in normal coordinate + r * std::cos(phi), // this is std::sqrt(u0)*std::cos(phi) + r * std::sin(phi), // this is std::sqrt(u0)*std::sin(phi) + z); // this can be std::sqrt(1-u0) + const Eigen::Matrix3f loc2world = local_to_world_vector_transformation(nrm); + const Eigen::Vector3f dir_out = loc2world * dir_loc; + return {dir_out, 1.f / float(M_PI)}; +} + +auto sampling_brdf_specular( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &dir_in, + float shiness, + const Eigen::Vector2f &unirand) -> std::pair { + const Eigen::Vector3f dir_mirror = dir_in - 2.f * dir_in.dot(nrm) * nrm; + const float phi = 2.f * float(M_PI) * unirand.y(); + const float cos_alpha = std::powf(1.f - unirand.x(), 1.f / (shiness + 1.f)); + const float sin_alpha = std::sqrt(std::max(0.f, 1.f - cos_alpha * cos_alpha)); + const auto dir_loc = Eigen::Vector3f( + sin_alpha * std::cos(phi), + sin_alpha * std::sin(phi), + cos_alpha); + const Eigen::Matrix3f loc2world = local_to_world_vector_transformation(dir_mirror); + const Eigen::Vector3f dir_out = loc2world * dir_loc; + assert(dir_out.dot(nrm) > 0.f); + float brdf = std::powf(cos_alpha, shiness) * (shiness + 1.f) / (2.f * float(M_PI)); + return {dir_out, brdf}; +} + +/** + * PDF in the BRDF sampling for Phong material + * @param nrm normal + * @param dir_in incoming light direction + * @param dir_out outgoing light direction + * @param ratio_diffuse how much incoming light diffuse + * @param ratio_specular how much incoming light reflect as the specular light + * @param shiness + * @return probability density + */ +float pdf_brdf_phong( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &dir_in, + const Eigen::Vector3f &dir_out, + float ratio_diffuse, + float ratio_specular, + float shiness) { + float pdf_diffuse = dir_out.dot(nrm) / float(M_PI); + const Eigen::Vector3f dir_mirror = dir_in - 2.f * dir_in.dot(nrm) * nrm; + float cos_alpha = dir_mirror.dot(dir_out); + float pdf_specular = std::powf(cos_alpha, shiness) * (shiness + 1.f) / (2.f * float(M_PI)); + return (pdf_diffuse * ratio_diffuse + pdf_specular * ratio_specular) / (ratio_diffuse + ratio_specular); +} + +class Sphere { + public: + const Eigen::Vector3f pos; + const float rad; + const float shiness; + const float ratio_specular; + const float ratio_diffuse; + const float emission; + public: + /** + * sampling the incoming light direction based on BRDF + * @param nrm normal of the surface + * @param dir_in outgoing light direction + * @param rdeng random number generator + * @return incoming light direction + */ + [[nodiscard]] auto sample_reflection_based_on_brdf( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &dir_out, + std::mt19937& rdeng) const -> Eigen::Vector3f { + float sum_ratio = ratio_specular + ratio_diffuse; + if (ratio_specular <= 0.f && ratio_diffuse <= 0.f) { return {1., 0., 0,}; } + auto udist01 = std::uniform_real_distribution(0.f,1.f); + const Eigen::Vector2f unirand(udist01(rdeng), udist01(rdeng)); + const float rnd0 = udist01(rdeng); + + Eigen::Vector3f dir_world(0., 0., 0.); + if (rnd0 < ratio_diffuse / sum_ratio) { // diffuse + auto hoge = sampling_brdf_lambert(nrm, unirand); + dir_world = hoge.first; + } else { // specular + auto hoge = sampling_brdf_specular(nrm, dir_out, shiness, unirand); + dir_world = hoge.first; + } + return dir_world; + } + /** + * BRDF value + * @param dir_in incoming light direction + * @param dir_out outgoing light direction + * @param dir_nrm normal direction + * @return brdf value + */ + [[nodiscard]] float brdf( + const Eigen::Vector3f &dir_in, + const Eigen::Vector3f &dir_out, + const Eigen::Vector3f &dir_nrm) const { + if (ratio_specular <= 0.f && ratio_diffuse <= 0.f) { return 0.f; } + const Eigen::Vector3f dir_mirror = dir_in - 2.f * dir_in.dot(dir_nrm) * dir_nrm; + float cos_alpha = dir_mirror.dot(dir_out); + float brdf_specular = std::powf(cos_alpha, shiness) * (shiness + 1.f) / (2.f * float(M_PI)); + float brdf_diffuse = 1.f / float(M_PI); + return brdf_specular * ratio_specular + brdf_diffuse * ratio_diffuse; + } + /** + * Probability density function for BRDF sampling with specified given in/out directions + * @param nrm normal of the surface + * @param dir_in incoming light direction + * @param dir_out outgoing light direction + * @return PDF + */ + [[nodiscard]]float pdf( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &dir_in, + const Eigen::Vector3f &dir_out) const { + return pdf_brdf_phong( + nrm, dir_in, dir_out, + ratio_diffuse, ratio_specular, shiness); + } +}; + +// -------------------------------------- + +const Sphere spheres[4] = { + { + {1.0, 1.0, 0.0}, // position + 0.4, // rad + 2000.f, // shiness + 0.0f, // specular + 0.0f, // diffuse + 1.f // emission + }, + { + {-1.0, -1.0, -1.0}, // position + 1.0, // rad + 2000.f, // shiness + 0.8f, // specular + 0.2f, // diffuse + 0.f // emission + }, + { + {+1.0, -1.0, -1.0}, // position + 1.0, + 2000.f, + 0.5f, + 0.5f, + 0.f + }, + { + {-1.0, +1.0, -1.0}, // position + 1.0, + 2000.f, + 0.2f, + 0.8f, + 0.f + } +}; + +/** + * Search Ray and screen hit + * @param ray_src + * @param ray_dir + * @return if hit return position, normal, and hit object index, otherwise return nuullopt + */ +auto hit_scene( + const Eigen::Vector3f &ray_src, + const Eigen::Vector3f &ray_dir) +-> std::tuple { + float min_depth = std::numeric_limits::max(); + std::optional> hit = std::nullopt; + int i_object = -1; + for( int i_sphere = 0; i_sphere < 4; ++i_sphere ) { + auto hit0 = intersection_ray_sphere( + ray_src, ray_dir, + spheres[i_sphere].pos, spheres[i_sphere].rad); + if (hit0 && std::get<2>(hit0.value()) < min_depth) { + hit = hit0; + min_depth = std::get<2>(hit0.value()); + i_object = i_sphere; + } + } + if (!hit) { return {Eigen::Vector3f::Zero(), Eigen::Vector3f::Zero(), -1}; } + return {std::get<0>(hit.value()), std::get<1>(hit.value()), i_object}; +} + +/** + * Light sampling + * @param nrm normal (not used for light sampling) + * @param pos position + * @param dir_out outgoing light (not used for light sampling) + * @param i_object index of sphere + * @param rndeng random number generator + * @return sampled direction + */ +auto sampling_light( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &pos, + const Eigen::Vector3f &dir_out, + unsigned int i_object, + std::mt19937& rndeng) -> Eigen::Vector3f { + if (i_object == 0) { return {1., 0., 0.,}; } + auto udist01 = std::uniform_real_distribution(0.f,1.f); + const Eigen::Vector2f unirand(udist01(rndeng), udist01(rndeng)); + auto light_center = spheres[0].pos; + float light_rad = spheres[0].rad; + float sin_theta_max_squared = light_rad * light_rad / (light_center - pos).squaredNorm(); + assert(sin_theta_max_squared > 0.f && sin_theta_max_squared < 1.f); + float cos_theta_max = std::sqrt(std::max(0.f, 1.f - sin_theta_max_squared)); + float cos_theta = 1.f - unirand.x() * (1.f - cos_theta_max); + assert(cos_theta > 0.0); + assert(cos_theta > cos_theta_max); + assert(cos_theta <= 1.f); + float sin_theta = std::sqrt(std::max(0.f, 1.f - cos_theta * cos_theta)); + float phi = 2.f * float(M_PI) * unirand.y(); + const auto dir_loc = Eigen::Vector3f( + sin_theta * std::cos(phi), + sin_theta * std::sin(phi), + cos_theta); + const Eigen::Matrix3f loc2world = local_to_world_vector_transformation((light_center - pos).normalized()); + return loc2world * dir_loc; +} + +/** + * PDF of the light sampling + * @param nrm + * @param pos + * @param dir_in + * @param dir_out + * @param hit0_object + * @return probability density function + */ +float pdf_light_sample( + const Eigen::Vector3f &nrm, + const Eigen::Vector3f &pos, + const Eigen::Vector3f &dir_in, + const Eigen::Vector3f &dir_out, + unsigned int hit0_object) { + if (hit0_object == 0) { return 1.0; } + auto light_center = spheres[0].pos; + float light_rad = spheres[0].rad; + float sin_theta_max_squared = light_rad * light_rad / (light_center - pos).squaredNorm(); + assert(sin_theta_max_squared > 0.f && sin_theta_max_squared < 1.f); + float cos_theta_max = std::sqrt(std::max(0.f, 1.f - sin_theta_max_squared)); + float pdf = 1.f / (2.f * float(M_PI) * (1.f - cos_theta_max)); + return pdf; +} + +auto get_ray_from_camera( + unsigned int width, unsigned int height, + unsigned int iw, unsigned int ih) -> std::pair { + auto cam_ray_src = Eigen::Vector3f(0., 0., 2.0); // focus point + float ndc_x = ((float(iw) + 0.5f) * 2.f / float(width) - 1.f); // normalized device x-coordinate [-1, +1] + float ndc_y = (1.f - (float(ih) + 0.5f) * 2.f / float(height)); // normalized device y-coordinate [-1, +1] + float sensor_size = 0.5; + Eigen::Vector3f position_on_sensor(ndc_x * sensor_size, ndc_y * sensor_size, 1.0); + Eigen::Vector3f cam_ray_dir = (position_on_sensor - cam_ray_src).normalized(); + return {cam_ray_src, cam_ray_dir}; +} + +void output_float_image( + const char* fname, + unsigned int img_width, + unsigned int img_height, + const std::vector& img_data) { + std::vector img_u8(img_height * img_width, 0); + for(int i=0;i(std::pow(data,1./2.2)*255.0); // gamma correction + img_u8[i] = c; + } + stbi_write_png( + fname, + img_width, img_height, 1, img_u8.data(), img_width); +} + +int main() { + std::mt19937 rndeng(std::random_device{}()); + const unsigned int img_width = 300; + const unsigned int img_height = 300; + // + std::vector img_brdf(img_height * img_width, 0.0); + std::vector img_light(img_height * img_width, 0.0); + std::vector img_mis(img_height * img_width, 0.0); + // + for (unsigned int iw = 0; iw < img_width; ++iw) { + for (unsigned int ih = 0; ih < img_height; ++ih) { + const auto[cam_ray_src, cam_ray_dir] = get_ray_from_camera(img_width, img_height, iw, ih); + // + const auto[hit0_pos, hit0_normal, hit0_object] = hit_scene(cam_ray_src, cam_ray_dir); + if (hit0_object == -1) {continue;} // does not hit anything + const int nsample = 100; + // ----------------- + // light sampling + img_light[(ih * img_width + iw)] += spheres[hit0_object].emission; + for (int isample = 0; isample < nsample; ++isample) { + // sampling light + auto hit0_refl = sampling_light(hit0_normal, hit0_pos, cam_ray_dir, hit0_object, rndeng); + // BRDF for sampled light direction + float hit0_brdf = spheres[hit0_object].brdf(cam_ray_dir, hit0_refl, hit0_normal); + if (hit0_brdf <= 0.f) { continue; } + // PDF for sampled light direction + float hit0_pdf = pdf_light_sample(hit0_normal, hit0_pos, cam_ray_dir, hit0_refl, hit0_object); + // How much light sampled light direction has + const auto[hit1_pos, hit1_normal, hit1_object] = hit_scene(hit0_pos + hit0_normal * 0.01, hit0_refl); + if (hit1_object == -1){ continue; } + float hit1_rad = spheres[hit1_object].emission; + // compute the contribution for this pixel + float rad = 0.f; // replace this with some code + img_light[ih * img_width + iw] += rad; + } + // ----------------- + // BRDF sampling + img_brdf[(ih * img_width + iw)] += spheres[hit0_object].emission; + for (int isample = 0; isample < nsample; ++isample) { + // direction of reflected ray + auto hit0_refl = spheres[hit0_object].sample_reflection_based_on_brdf(hit0_normal, cam_ray_dir, rndeng); + // Brdf value for reflected ray + float hit0_brdf = spheres[hit0_object].brdf(cam_ray_dir, hit0_refl, hit0_normal); + if (hit0_brdf <= 0.f) { continue; } + // PDF of the reflected ray + const float hit0_pdf = spheres[hit0_object].pdf(hit0_normal, cam_ray_dir, hit0_refl); + // how much light this reflected ray has + const auto[hit1_pos, hit1_normal, hit1_object] = hit_scene(hit0_pos + hit0_normal * 0.01, hit0_refl); + if (hit1_object == -1){ continue; } + float hit1_rad = spheres[hit1_object].emission; + // compute the contribution for this pixel + float rad = 0.f; // replace this with some code + img_brdf[ih * img_width + iw] += rad; + } + // ----------------- + // Multiple importance sampling + img_mis[(ih * img_width + iw)] += spheres[hit0_object].emission; + int num_half_sample = nsample / 2; + for (int isample = 0; isample < num_half_sample; ++isample) { + // reflected ray direction + auto hit0_refl = spheres[hit0_object].sample_reflection_based_on_brdf(hit0_normal, cam_ray_dir, rndeng); + // Brdf of the reflected ray + float hit0_brdf = spheres[hit0_object].brdf(cam_ray_dir, hit0_refl, hit0_normal); + if (hit0_brdf <= 0.f) { continue; } + const auto[hit1_pos, hit1_normal, hit1_object] = hit_scene(hit0_pos + hit0_normal * 0.01, hit0_refl); + if (hit1_object == -1){ continue; } + float hit1_rad = spheres[hit1_object].emission; + float hit0_pdf_brdf_sample = spheres[hit0_object].pdf(hit0_normal, cam_ray_dir, hit0_refl); + float hit0_pdf_light_sample = pdf_light_sample(hit0_normal, hit0_pos, cam_ray_dir, hit0_refl, hit0_object); + float rad = 0.f; // write some code + img_mis[ih * img_width + iw] += rad; + } + for (int isample = 0; isample < nsample / 2; ++isample) { + auto hit0_refl = sampling_light(hit0_normal, hit0_pos, cam_ray_dir, hit0_object, rndeng); + float hit0_brdf = spheres[hit0_object].brdf(cam_ray_dir, hit0_refl, hit0_normal); + if (hit0_brdf <= 0.f) { continue; } + const auto[hit1_pos, hit1_normal, hit1_object] = hit_scene(hit0_pos + hit0_normal * 0.01, hit0_refl); + if (hit1_object == -1){ continue; } + float hit1_rad = spheres[hit1_object].emission; + float hit0_pdf_light_sample = pdf_light_sample(hit0_normal, hit0_pos, cam_ray_dir, hit0_refl, hit0_object); + float hit0_pdf_brdf_sample = spheres[hit0_object].pdf(hit0_normal, cam_ray_dir, hit0_refl); + float rad = 0.f; // write some code + img_mis[ih * img_width + iw] += rad; + } + } + } + + output_float_image( + (std::filesystem::path(PROJECT_SOURCE_DIR) / "out_brdf.png").string().c_str(), + img_width, img_height, img_brdf); + output_float_image( + (std::filesystem::path(PROJECT_SOURCE_DIR) / "out_light.png").string().c_str(), + img_width, img_height, img_light); + output_float_image( + (std::filesystem::path(PROJECT_SOURCE_DIR) / "out_mis.png").string().c_str(), + img_width, img_height, img_mis); + + +} diff --git a/task07/out_brdf.png b/task07/out_brdf.png new file mode 100644 index 0000000000000000000000000000000000000000..c0eca84345e5a878954fce1f090f762114612e18 GIT binary patch literal 12051 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 literal 0 HcmV?d00001 diff --git a/task07/out_light.png b/task07/out_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2bbe6479c9ba77dd46d0112e765047df26058ce0 GIT binary patch literal 6640 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 TduxEG00000NkvXXu0mjfGys%7 literal 0 HcmV?d00001