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)
+
+
+
+**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 |
+| ----------------------- | ------------------------ | ------------------------ |
+|  |  |  |
+
+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 0000000..c0eca84
Binary files /dev/null and b/task07/out_brdf.png differ
diff --git a/task07/out_light.png b/task07/out_light.png
new file mode 100644
index 0000000..2bbe647
Binary files /dev/null and b/task07/out_light.png differ
diff --git a/task07/out_mis.png b/task07/out_mis.png
new file mode 100644
index 0000000..6680934
Binary files /dev/null and b/task07/out_mis.png differ